f7b26e50cc0a999c92b2038d4905619f3f00c9d7
[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
253 extern int tinyLayout, smallLayout;
254 ChessProgramStats programStats;
255 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
256 int endPV = -1;
257 static int exiting = 0; /* [HGM] moved to top */
258 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
259 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
260 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
261 int partnerHighlight[2];
262 Boolean partnerBoardValid = 0;
263 char partnerStatus[MSG_SIZ];
264 Boolean partnerUp;
265 Boolean originalFlip;
266 Boolean twoBoards = 0;
267 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
268 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
269 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
270 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
271 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
272 int opponentKibitzes;
273 int lastSavedGame; /* [HGM] save: ID of game */
274 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
275 extern int chatCount;
276 int chattingPartner;
277 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
278 char lastMsg[MSG_SIZ];
279 ChessSquare pieceSweep = EmptySquare;
280 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
281 int promoDefaultAltered;
282
283 /* States for ics_getting_history */
284 #define H_FALSE 0
285 #define H_REQUESTED 1
286 #define H_GOT_REQ_HEADER 2
287 #define H_GOT_UNREQ_HEADER 3
288 #define H_GETTING_MOVES 4
289 #define H_GOT_UNWANTED_HEADER 5
290
291 /* whosays values for GameEnds */
292 #define GE_ICS 0
293 #define GE_ENGINE 1
294 #define GE_PLAYER 2
295 #define GE_FILE 3
296 #define GE_XBOARD 4
297 #define GE_ENGINE1 5
298 #define GE_ENGINE2 6
299
300 /* Maximum number of games in a cmail message */
301 #define CMAIL_MAX_GAMES 20
302
303 /* Different types of move when calling RegisterMove */
304 #define CMAIL_MOVE   0
305 #define CMAIL_RESIGN 1
306 #define CMAIL_DRAW   2
307 #define CMAIL_ACCEPT 3
308
309 /* Different types of result to remember for each game */
310 #define CMAIL_NOT_RESULT 0
311 #define CMAIL_OLD_RESULT 1
312 #define CMAIL_NEW_RESULT 2
313
314 /* Telnet protocol constants */
315 #define TN_WILL 0373
316 #define TN_WONT 0374
317 #define TN_DO   0375
318 #define TN_DONT 0376
319 #define TN_IAC  0377
320 #define TN_ECHO 0001
321 #define TN_SGA  0003
322 #define TN_PORT 23
323
324 char*
325 safeStrCpy( char *dst, const char *src, size_t count )
326 { // [HGM] made safe
327   int i;
328   assert( dst != NULL );
329   assert( src != NULL );
330   assert( count > 0 );
331
332   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
333   if(  i == count && dst[count-1] != NULLCHAR)
334     {
335       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
336       if(appData.debugMode)
337       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
338     }
339
340   return dst;
341 }
342
343 /* Some compiler can't cast u64 to double
344  * This function do the job for us:
345
346  * We use the highest bit for cast, this only
347  * works if the highest bit is not
348  * in use (This should not happen)
349  *
350  * We used this for all compiler
351  */
352 double
353 u64ToDouble(u64 value)
354 {
355   double r;
356   u64 tmp = value & u64Const(0x7fffffffffffffff);
357   r = (double)(s64)tmp;
358   if (value & u64Const(0x8000000000000000))
359        r +=  9.2233720368547758080e18; /* 2^63 */
360  return r;
361 }
362
363 /* Fake up flags for now, as we aren't keeping track of castling
364    availability yet. [HGM] Change of logic: the flag now only
365    indicates the type of castlings allowed by the rule of the game.
366    The actual rights themselves are maintained in the array
367    castlingRights, as part of the game history, and are not probed
368    by this function.
369  */
370 int
371 PosFlags(index)
372 {
373   int flags = F_ALL_CASTLE_OK;
374   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
375   switch (gameInfo.variant) {
376   case VariantSuicide:
377     flags &= ~F_ALL_CASTLE_OK;
378   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
379     flags |= F_IGNORE_CHECK;
380   case VariantLosers:
381     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
382     break;
383   case VariantAtomic:
384     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
385     break;
386   case VariantKriegspiel:
387     flags |= F_KRIEGSPIEL_CAPTURE;
388     break;
389   case VariantCapaRandom:
390   case VariantFischeRandom:
391     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
392   case VariantNoCastle:
393   case VariantShatranj:
394   case VariantCourier:
395   case VariantMakruk:
396     flags &= ~F_ALL_CASTLE_OK;
397     break;
398   default:
399     break;
400   }
401   return flags;
402 }
403
404 FILE *gameFileFP, *debugFP;
405
406 /*
407     [AS] Note: sometimes, the sscanf() function is used to parse the input
408     into a fixed-size buffer. Because of this, we must be prepared to
409     receive strings as long as the size of the input buffer, which is currently
410     set to 4K for Windows and 8K for the rest.
411     So, we must either allocate sufficiently large buffers here, or
412     reduce the size of the input buffer in the input reading part.
413 */
414
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
418
419 ChessProgramState first, second;
420
421 /* premove variables */
422 int premoveToX = 0;
423 int premoveToY = 0;
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
427 int gotPremove = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
430
431 char *ics_prefix = "$";
432 int ics_type = ICS_GENERIC;
433
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey; // [HGM] set by mouse handler
461
462 int have_sent_ICS_logon = 0;
463 int movesPerSession;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 int shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void CleanupTail P((void));
510
511 ChessSquare  FIDEArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515         BlackKing, BlackBishop, BlackKnight, BlackRook }
516 };
517
518 ChessSquare twoKingsArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522         BlackKing, BlackKing, BlackKnight, BlackRook }
523 };
524
525 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
527         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
528     { BlackRook, BlackMan, BlackBishop, BlackQueen,
529         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
530 };
531
532 ChessSquare SpartanArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
536         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
537 };
538
539 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
543         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
544 };
545
546 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
547     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
548         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
549     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
550         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
551 };
552
553 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
555         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
556     { BlackRook, BlackKnight, BlackMan, BlackFerz,
557         BlackKing, BlackMan, BlackKnight, BlackRook }
558 };
559
560
561 #if (BOARD_FILES>=10)
562 ChessSquare ShogiArray[2][BOARD_FILES] = {
563     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
564         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
565     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
566         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
567 };
568
569 ChessSquare XiangqiArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
571         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
573         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 };
575
576 ChessSquare CapablancaArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
578         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
580         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
581 };
582
583 ChessSquare GreatArray[2][BOARD_FILES] = {
584     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
585         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
586     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
587         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
588 };
589
590 ChessSquare JanusArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
592         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
593     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
594         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
595 };
596
597 #ifdef GOTHIC
598 ChessSquare GothicArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
600         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
602         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
603 };
604 #else // !GOTHIC
605 #define GothicArray CapablancaArray
606 #endif // !GOTHIC
607
608 #ifdef FALCON
609 ChessSquare FalconArray[2][BOARD_FILES] = {
610     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
611         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
612     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
613         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
614 };
615 #else // !FALCON
616 #define FalconArray CapablancaArray
617 #endif // !FALCON
618
619 #else // !(BOARD_FILES>=10)
620 #define XiangqiPosition FIDEArray
621 #define CapablancaArray FIDEArray
622 #define GothicArray FIDEArray
623 #define GreatArray FIDEArray
624 #endif // !(BOARD_FILES>=10)
625
626 #if (BOARD_FILES>=12)
627 ChessSquare CourierArray[2][BOARD_FILES] = {
628     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
629         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
630     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
631         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
632 };
633 #else // !(BOARD_FILES>=12)
634 #define CourierArray CapablancaArray
635 #endif // !(BOARD_FILES>=12)
636
637
638 Board initialPosition;
639
640
641 /* Convert str to a rating. Checks for special cases of "----",
642
643    "++++", etc. Also strips ()'s */
644 int
645 string_to_rating(str)
646   char *str;
647 {
648   while(*str && !isdigit(*str)) ++str;
649   if (!*str)
650     return 0;   /* One of the special "no rating" cases */
651   else
652     return atoi(str);
653 }
654
655 void
656 ClearProgramStats()
657 {
658     /* Init programStats */
659     programStats.movelist[0] = 0;
660     programStats.depth = 0;
661     programStats.nr_moves = 0;
662     programStats.moves_left = 0;
663     programStats.nodes = 0;
664     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
665     programStats.score = 0;
666     programStats.got_only_move = 0;
667     programStats.got_fail = 0;
668     programStats.line_is_book = 0;
669 }
670
671 void
672 CommonEngineInit()
673 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674     if (appData.firstPlaysBlack) {
675         first.twoMachinesColor = "black\n";
676         second.twoMachinesColor = "white\n";
677     } else {
678         first.twoMachinesColor = "white\n";
679         second.twoMachinesColor = "black\n";
680     }
681
682     first.other = &second;
683     second.other = &first;
684
685     { float norm = 1;
686         if(appData.timeOddsMode) {
687             norm = appData.timeOdds[0];
688             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
689         }
690         first.timeOdds  = appData.timeOdds[0]/norm;
691         second.timeOdds = appData.timeOdds[1]/norm;
692     }
693
694     if(programVersion) free(programVersion);
695     if (appData.noChessProgram) {
696         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697         sprintf(programVersion, "%s", PACKAGE_STRING);
698     } else {
699       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
702     }
703 }
704
705 void
706 UnloadEngine(ChessProgramState *cps)
707 {
708         /* Kill off first chess program */
709         if (cps->isr != NULL)
710           RemoveInputSource(cps->isr);
711         cps->isr = NULL;
712
713         if (cps->pr != NoProc) {
714             ExitAnalyzeMode();
715             DoSleep( appData.delayBeforeQuit );
716             SendToProgram("quit\n", cps);
717             DoSleep( appData.delayAfterQuit );
718             DestroyChildProcess(cps->pr, cps->useSigterm);
719         }
720         cps->pr = NoProc;
721         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
722 }
723
724 void
725 ClearOptions(ChessProgramState *cps)
726 {
727     int i;
728     cps->nrOptions = cps->comboCnt = 0;
729     for(i=0; i<MAX_OPTIONS; i++) {
730         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731         cps->option[i].textValue = 0;
732     }
733 }
734
735 char *engineNames[] = {
736 "first",
737 "second"
738 };
739
740 void
741 InitEngine(ChessProgramState *cps, int n)
742 {   // [HGM] all engine initialiation put in a function that does one engine
743
744     ClearOptions(cps);
745
746     cps->which = engineNames[n];
747     cps->maybeThinking = FALSE;
748     cps->pr = NoProc;
749     cps->isr = NULL;
750     cps->sendTime = 2;
751     cps->sendDrawOffers = 1;
752
753     cps->program = appData.chessProgram[n];
754     cps->host = appData.host[n];
755     cps->dir = appData.directory[n];
756     cps->initString = appData.engInitString[n];
757     cps->computerString = appData.computerString[n];
758     cps->useSigint  = TRUE;
759     cps->useSigterm = TRUE;
760     cps->reuse = appData.reuse[n];
761     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
762     cps->useSetboard = FALSE;
763     cps->useSAN = FALSE;
764     cps->usePing = FALSE;
765     cps->lastPing = 0;
766     cps->lastPong = 0;
767     cps->usePlayother = FALSE;
768     cps->useColors = TRUE;
769     cps->useUsermove = FALSE;
770     cps->sendICS = FALSE;
771     cps->sendName = appData.icsActive;
772     cps->sdKludge = FALSE;
773     cps->stKludge = FALSE;
774     TidyProgramName(cps->program, cps->host, cps->tidy);
775     cps->matchWins = 0;
776     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777     cps->analysisSupport = 2; /* detect */
778     cps->analyzing = FALSE;
779     cps->initDone = FALSE;
780
781     /* New features added by Tord: */
782     cps->useFEN960 = FALSE;
783     cps->useOOCastle = TRUE;
784     /* End of new features added by Tord. */
785     cps->fenOverride  = appData.fenOverride[n];
786
787     /* [HGM] time odds: set factor for each machine */
788     cps->timeOdds  = appData.timeOdds[n];
789
790     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791     cps->accumulateTC = appData.accumulateTC[n];
792     cps->maxNrOfSessions = 1;
793
794     /* [HGM] debug */
795     cps->debug = FALSE;
796     cps->supportsNPS = UNKNOWN;
797
798     /* [HGM] options */
799     cps->optionSettings  = appData.engOptions[n];
800
801     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
802     cps->isUCI = appData.isUCI[n]; /* [AS] */
803     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
804
805     if (appData.protocolVersion[n] > PROTOVER
806         || appData.protocolVersion[n] < 1)
807       {
808         char buf[MSG_SIZ];
809         int len;
810
811         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
812                        appData.protocolVersion[n]);
813         if( (len > MSG_SIZ) && appData.debugMode )
814           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
815
816         DisplayFatalError(buf, 0, 2);
817       }
818     else
819       {
820         cps->protocolVersion = appData.protocolVersion[n];
821       }
822
823     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
824 }
825
826 ChessProgramState *savCps;
827
828 void
829 LoadEngine()
830 {
831     int i;
832     if(WaitForEngine(savCps, LoadEngine)) return;
833     CommonEngineInit(); // recalculate time odds
834     if(gameInfo.variant != StringToVariant(appData.variant)) {
835         // we changed variant when loading the engine; this forces us to reset
836         Reset(TRUE, savCps != &first);
837         EditGameEvent(); // for consistency with other path, as Reset changes mode
838     }
839     InitChessProgram(savCps, FALSE);
840     SendToProgram("force\n", savCps);
841     DisplayMessage("", "");
842     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
843     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
844     ThawUI();
845     SetGNUMode();
846 }
847
848 void
849 ReplaceEngine(ChessProgramState *cps, int n)
850 {
851     EditGameEvent();
852     UnloadEngine(cps);
853     appData.noChessProgram = FALSE;
854     appData.clockMode = TRUE;
855     InitEngine(cps, n);
856     if(n) return; // only startup first engine immediately; second can wait
857     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
858     LoadEngine();
859 }
860
861 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName;
862 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
863
864 void Load(ChessProgramState *cps, int i)
865 {
866     char *p, *q, buf[MSG_SIZ];
867     if(engineLine[0]) { // an engine was selected from the combo box
868         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
869         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
870         ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
871         ParseArgsFromString(buf);
872         SwapEngines(i);
873         ReplaceEngine(cps, i);
874         return;
875     }
876     p = engineName;
877     while(q = strchr(p, SLASH)) p = q+1;
878     if(*p== NULLCHAR) return;
879     appData.chessProgram[i] = strdup(p);
880     if(engineDir[0] != NULLCHAR)
881         appData.directory[i] = engineDir;
882     else if(p != engineName) { // derive directory from engine path, when not given
883         p[-1] = 0;
884         appData.directory[i] = strdup(engineName);
885         p[-1] = '/';
886     } else appData.directory[i] = ".";
887     appData.isUCI[i] = isUCI;
888     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
889     appData.hasOwnBookUCI[i] = hasBook;
890     if(addToList) {
891         int len;
892         q = firstChessProgramNames;
893         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
894         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i], 
895                         v1 ? " -firstProtocolVersion 1" : "",
896                         hasBook ? "" : " -fNoOwnBookUCI",
897                         isUCI ? " -fUCI" : "",
898                         storeVariant ? " -variant " : "",
899                         storeVariant ? VariantName(gameInfo.variant) : "");
900 fprintf(debugFP, "new line: %s", buf);
901         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
902         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
903         if(q)   free(q);
904     }
905     ReplaceEngine(cps, i);
906 }
907
908 void
909 InitBackEnd1()
910 {
911     int matched, min, sec;
912
913     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
914     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
915
916     GetTimeMark(&programStartTime);
917     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
918     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
919
920     ClearProgramStats();
921     programStats.ok_to_send = 1;
922     programStats.seen_stat = 0;
923
924     /*
925      * Initialize game list
926      */
927     ListNew(&gameList);
928
929
930     /*
931      * Internet chess server status
932      */
933     if (appData.icsActive) {
934         appData.matchMode = FALSE;
935         appData.matchGames = 0;
936 #if ZIPPY
937         appData.noChessProgram = !appData.zippyPlay;
938 #else
939         appData.zippyPlay = FALSE;
940         appData.zippyTalk = FALSE;
941         appData.noChessProgram = TRUE;
942 #endif
943         if (*appData.icsHelper != NULLCHAR) {
944             appData.useTelnet = TRUE;
945             appData.telnetProgram = appData.icsHelper;
946         }
947     } else {
948         appData.zippyTalk = appData.zippyPlay = FALSE;
949     }
950
951     /* [AS] Initialize pv info list [HGM] and game state */
952     {
953         int i, j;
954
955         for( i=0; i<=framePtr; i++ ) {
956             pvInfoList[i].depth = -1;
957             boards[i][EP_STATUS] = EP_NONE;
958             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
959         }
960     }
961
962     /*
963      * Parse timeControl resource
964      */
965     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
966                           appData.movesPerSession)) {
967         char buf[MSG_SIZ];
968         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
969         DisplayFatalError(buf, 0, 2);
970     }
971
972     /*
973      * Parse searchTime resource
974      */
975     if (*appData.searchTime != NULLCHAR) {
976         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
977         if (matched == 1) {
978             searchTime = min * 60;
979         } else if (matched == 2) {
980             searchTime = min * 60 + sec;
981         } else {
982             char buf[MSG_SIZ];
983             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
984             DisplayFatalError(buf, 0, 2);
985         }
986     }
987
988     /* [AS] Adjudication threshold */
989     adjudicateLossThreshold = appData.adjudicateLossThreshold;
990
991     InitEngine(&first, 0);
992     InitEngine(&second, 1);
993     CommonEngineInit();
994
995     if (appData.icsActive) {
996         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
997     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
998         appData.clockMode = FALSE;
999         first.sendTime = second.sendTime = 0;
1000     }
1001
1002 #if ZIPPY
1003     /* Override some settings from environment variables, for backward
1004        compatibility.  Unfortunately it's not feasible to have the env
1005        vars just set defaults, at least in xboard.  Ugh.
1006     */
1007     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1008       ZippyInit();
1009     }
1010 #endif
1011
1012     if (!appData.icsActive) {
1013       char buf[MSG_SIZ];
1014       int len;
1015
1016       /* Check for variants that are supported only in ICS mode,
1017          or not at all.  Some that are accepted here nevertheless
1018          have bugs; see comments below.
1019       */
1020       VariantClass variant = StringToVariant(appData.variant);
1021       switch (variant) {
1022       case VariantBughouse:     /* need four players and two boards */
1023       case VariantKriegspiel:   /* need to hide pieces and move details */
1024         /* case VariantFischeRandom: (Fabien: moved below) */
1025         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1026         if( (len > MSG_SIZ) && appData.debugMode )
1027           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1028
1029         DisplayFatalError(buf, 0, 2);
1030         return;
1031
1032       case VariantUnknown:
1033       case VariantLoadable:
1034       case Variant29:
1035       case Variant30:
1036       case Variant31:
1037       case Variant32:
1038       case Variant33:
1039       case Variant34:
1040       case Variant35:
1041       case Variant36:
1042       default:
1043         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1044         if( (len > MSG_SIZ) && appData.debugMode )
1045           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1046
1047         DisplayFatalError(buf, 0, 2);
1048         return;
1049
1050       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1051       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1052       case VariantGothic:     /* [HGM] should work */
1053       case VariantCapablanca: /* [HGM] should work */
1054       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1055       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1056       case VariantKnightmate: /* [HGM] should work */
1057       case VariantCylinder:   /* [HGM] untested */
1058       case VariantFalcon:     /* [HGM] untested */
1059       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1060                                  offboard interposition not understood */
1061       case VariantNormal:     /* definitely works! */
1062       case VariantWildCastle: /* pieces not automatically shuffled */
1063       case VariantNoCastle:   /* pieces not automatically shuffled */
1064       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1065       case VariantLosers:     /* should work except for win condition,
1066                                  and doesn't know captures are mandatory */
1067       case VariantSuicide:    /* should work except for win condition,
1068                                  and doesn't know captures are mandatory */
1069       case VariantGiveaway:   /* should work except for win condition,
1070                                  and doesn't know captures are mandatory */
1071       case VariantTwoKings:   /* should work */
1072       case VariantAtomic:     /* should work except for win condition */
1073       case Variant3Check:     /* should work except for win condition */
1074       case VariantShatranj:   /* should work except for all win conditions */
1075       case VariantMakruk:     /* should work except for daw countdown */
1076       case VariantBerolina:   /* might work if TestLegality is off */
1077       case VariantCapaRandom: /* should work */
1078       case VariantJanus:      /* should work */
1079       case VariantSuper:      /* experimental */
1080       case VariantGreat:      /* experimental, requires legality testing to be off */
1081       case VariantSChess:     /* S-Chess, should work */
1082       case VariantSpartan:    /* should work */
1083         break;
1084       }
1085     }
1086
1087 }
1088
1089 int NextIntegerFromString( char ** str, long * value )
1090 {
1091     int result = -1;
1092     char * s = *str;
1093
1094     while( *s == ' ' || *s == '\t' ) {
1095         s++;
1096     }
1097
1098     *value = 0;
1099
1100     if( *s >= '0' && *s <= '9' ) {
1101         while( *s >= '0' && *s <= '9' ) {
1102             *value = *value * 10 + (*s - '0');
1103             s++;
1104         }
1105
1106         result = 0;
1107     }
1108
1109     *str = s;
1110
1111     return result;
1112 }
1113
1114 int NextTimeControlFromString( char ** str, long * value )
1115 {
1116     long temp;
1117     int result = NextIntegerFromString( str, &temp );
1118
1119     if( result == 0 ) {
1120         *value = temp * 60; /* Minutes */
1121         if( **str == ':' ) {
1122             (*str)++;
1123             result = NextIntegerFromString( str, &temp );
1124             *value += temp; /* Seconds */
1125         }
1126     }
1127
1128     return result;
1129 }
1130
1131 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1132 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1133     int result = -1, type = 0; long temp, temp2;
1134
1135     if(**str != ':') return -1; // old params remain in force!
1136     (*str)++;
1137     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1138     if( NextIntegerFromString( str, &temp ) ) return -1;
1139     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1140
1141     if(**str != '/') {
1142         /* time only: incremental or sudden-death time control */
1143         if(**str == '+') { /* increment follows; read it */
1144             (*str)++;
1145             if(**str == '!') type = *(*str)++; // Bronstein TC
1146             if(result = NextIntegerFromString( str, &temp2)) return -1;
1147             *inc = temp2 * 1000;
1148             if(**str == '.') { // read fraction of increment
1149                 char *start = ++(*str);
1150                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1151                 temp2 *= 1000;
1152                 while(start++ < *str) temp2 /= 10;
1153                 *inc += temp2;
1154             }
1155         } else *inc = 0;
1156         *moves = 0; *tc = temp * 1000; *incType = type;
1157         return 0;
1158     }
1159
1160     (*str)++; /* classical time control */
1161     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1162
1163     if(result == 0) {
1164         *moves = temp;
1165         *tc    = temp2 * 1000;
1166         *inc   = 0;
1167         *incType = type;
1168     }
1169     return result;
1170 }
1171
1172 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1173 {   /* [HGM] get time to add from the multi-session time-control string */
1174     int incType, moves=1; /* kludge to force reading of first session */
1175     long time, increment;
1176     char *s = tcString;
1177
1178     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1179     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1180     do {
1181         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1182         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1183         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1184         if(movenr == -1) return time;    /* last move before new session     */
1185         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1186         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1187         if(!moves) return increment;     /* current session is incremental   */
1188         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1189     } while(movenr >= -1);               /* try again for next session       */
1190
1191     return 0; // no new time quota on this move
1192 }
1193
1194 int
1195 ParseTimeControl(tc, ti, mps)
1196      char *tc;
1197      float ti;
1198      int mps;
1199 {
1200   long tc1;
1201   long tc2;
1202   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1203   int min, sec=0;
1204
1205   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1206   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1207       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1208   if(ti > 0) {
1209
1210     if(mps)
1211       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1212     else 
1213       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1214   } else {
1215     if(mps)
1216       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1217     else 
1218       snprintf(buf, MSG_SIZ, ":%s", mytc);
1219   }
1220   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1221   
1222   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1223     return FALSE;
1224   }
1225
1226   if( *tc == '/' ) {
1227     /* Parse second time control */
1228     tc++;
1229
1230     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1231       return FALSE;
1232     }
1233
1234     if( tc2 == 0 ) {
1235       return FALSE;
1236     }
1237
1238     timeControl_2 = tc2 * 1000;
1239   }
1240   else {
1241     timeControl_2 = 0;
1242   }
1243
1244   if( tc1 == 0 ) {
1245     return FALSE;
1246   }
1247
1248   timeControl = tc1 * 1000;
1249
1250   if (ti >= 0) {
1251     timeIncrement = ti * 1000;  /* convert to ms */
1252     movesPerSession = 0;
1253   } else {
1254     timeIncrement = 0;
1255     movesPerSession = mps;
1256   }
1257   return TRUE;
1258 }
1259
1260 void
1261 InitBackEnd2()
1262 {
1263     if (appData.debugMode) {
1264         fprintf(debugFP, "%s\n", programVersion);
1265     }
1266
1267     set_cont_sequence(appData.wrapContSeq);
1268     if (appData.matchGames > 0) {
1269         appData.matchMode = TRUE;
1270     } else if (appData.matchMode) {
1271         appData.matchGames = 1;
1272     }
1273     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1274         appData.matchGames = appData.sameColorGames;
1275     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1276         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1277         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1278     }
1279     Reset(TRUE, FALSE);
1280     if (appData.noChessProgram || first.protocolVersion == 1) {
1281       InitBackEnd3();
1282     } else {
1283       /* kludge: allow timeout for initial "feature" commands */
1284       FreezeUI();
1285       DisplayMessage("", _("Starting chess program"));
1286       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1287     }
1288 }
1289
1290 int
1291 CalculateIndex(int index, int gameNr)
1292 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1293     int res;
1294     if(index > 0) return index; // fixed nmber
1295     if(index == 0) return 1;
1296     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1297     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1298     return res;
1299 }
1300
1301 int
1302 LoadGameOrPosition(int gameNr)
1303 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1304     if (*appData.loadGameFile != NULLCHAR) {
1305         if (!LoadGameFromFile(appData.loadGameFile,
1306                 CalculateIndex(appData.loadGameIndex, gameNr),
1307                               appData.loadGameFile, FALSE)) {
1308             DisplayFatalError(_("Bad game file"), 0, 1);
1309             return 0;
1310         }
1311     } else if (*appData.loadPositionFile != NULLCHAR) {
1312         if (!LoadPositionFromFile(appData.loadPositionFile,
1313                 CalculateIndex(appData.loadPositionIndex, gameNr),
1314                                   appData.loadPositionFile)) {
1315             DisplayFatalError(_("Bad position file"), 0, 1);
1316             return 0;
1317         }
1318     }
1319     return 1;
1320 }
1321
1322 void
1323 ReserveGame(int gameNr, char resChar)
1324 {
1325     FILE *tf = fopen(appData.tourneyFile, "r+");
1326     char *p, *q, c, buf[MSG_SIZ];
1327     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1328     safeStrCpy(buf, lastMsg, MSG_SIZ);
1329     DisplayMessage(_("Pick new game"), "");
1330     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1331     ParseArgsFromFile(tf);
1332     p = q = appData.results;
1333     if(appData.debugMode) {
1334       char *r = appData.participants;
1335       fprintf(debugFP, "results = '%s'\n", p);
1336       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1337       fprintf(debugFP, "\n");
1338     }
1339     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1340     nextGame = q - p;
1341     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1342     safeStrCpy(q, p, strlen(p) + 2);
1343     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1344     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1345     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1346         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1347         q[nextGame] = '*';
1348     }
1349     fseek(tf, -(strlen(p)+4), SEEK_END);
1350     c = fgetc(tf);
1351     if(c != '"') // depending on DOS or Unix line endings we can be one off
1352          fseek(tf, -(strlen(p)+2), SEEK_END);
1353     else fseek(tf, -(strlen(p)+3), SEEK_END);
1354     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1355     DisplayMessage(buf, "");
1356     free(p); appData.results = q;
1357     if(nextGame <= appData.matchGames && resChar != ' ' &&
1358        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1359         UnloadEngine(&first);  // next game belongs to other pairing;
1360         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1361     }
1362 }
1363
1364 void
1365 MatchEvent(int mode)
1366 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1367         int dummy;
1368         if(matchMode) { // already in match mode: switch it off
1369             appData.matchGames = matchGame; // kludge to let match terminate after next game.
1370             ModeHighlight(); // kludgey way to remove checkmark...
1371             return;
1372         }
1373         if(gameMode != BeginningOfGame) {
1374             DisplayError(_("You can only start a match from the initial position."), 0);
1375             return;
1376         }
1377         appData.matchGames = appData.defaultMatchGames;
1378         /* Set up machine vs. machine match */
1379         nextGame = 0;
1380         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1381         if(appData.tourneyFile[0]) {
1382             ReserveGame(-1, 0);
1383             if(nextGame > appData.matchGames) {
1384                 char buf[MSG_SIZ];
1385                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1386                 DisplayError(buf, 0);
1387                 appData.tourneyFile[0] = 0;
1388                 return;
1389             }
1390         } else
1391         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1392             DisplayFatalError(_("Can't have a match with no chess programs"),
1393                               0, 2);
1394             return;
1395         }
1396         matchMode = mode;
1397         matchGame = roundNr = 1;
1398         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1399         NextMatchGame();
1400 }
1401
1402 void
1403 InitBackEnd3 P((void))
1404 {
1405     GameMode initialMode;
1406     char buf[MSG_SIZ];
1407     int err, len;
1408
1409     InitChessProgram(&first, startedFromSetupPosition);
1410
1411     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1412         free(programVersion);
1413         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1414         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1415     }
1416
1417     if (appData.icsActive) {
1418 #ifdef WIN32
1419         /* [DM] Make a console window if needed [HGM] merged ifs */
1420         ConsoleCreate();
1421 #endif
1422         err = establish();
1423         if (err != 0)
1424           {
1425             if (*appData.icsCommPort != NULLCHAR)
1426               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1427                              appData.icsCommPort);
1428             else
1429               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1430                         appData.icsHost, appData.icsPort);
1431
1432             if( (len > MSG_SIZ) && appData.debugMode )
1433               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1434
1435             DisplayFatalError(buf, err, 1);
1436             return;
1437         }
1438         SetICSMode();
1439         telnetISR =
1440           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1441         fromUserISR =
1442           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1443         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1444             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1445     } else if (appData.noChessProgram) {
1446         SetNCPMode();
1447     } else {
1448         SetGNUMode();
1449     }
1450
1451     if (*appData.cmailGameName != NULLCHAR) {
1452         SetCmailMode();
1453         OpenLoopback(&cmailPR);
1454         cmailISR =
1455           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1456     }
1457
1458     ThawUI();
1459     DisplayMessage("", "");
1460     if (StrCaseCmp(appData.initialMode, "") == 0) {
1461       initialMode = BeginningOfGame;
1462       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1463         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1464         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1465         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1466         ModeHighlight();
1467       }
1468     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1469       initialMode = TwoMachinesPlay;
1470     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1471       initialMode = AnalyzeFile;
1472     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1473       initialMode = AnalyzeMode;
1474     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1475       initialMode = MachinePlaysWhite;
1476     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1477       initialMode = MachinePlaysBlack;
1478     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1479       initialMode = EditGame;
1480     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1481       initialMode = EditPosition;
1482     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1483       initialMode = Training;
1484     } else {
1485       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1486       if( (len > MSG_SIZ) && appData.debugMode )
1487         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1488
1489       DisplayFatalError(buf, 0, 2);
1490       return;
1491     }
1492
1493     if (appData.matchMode) {
1494         if(appData.tourneyFile[0]) { // start tourney from command line
1495             FILE *f;
1496             if(f = fopen(appData.tourneyFile, "r")) {
1497                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1498                 fclose(f);
1499             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1500         }
1501         MatchEvent(TRUE);
1502     } else if (*appData.cmailGameName != NULLCHAR) {
1503         /* Set up cmail mode */
1504         ReloadCmailMsgEvent(TRUE);
1505     } else {
1506         /* Set up other modes */
1507         if (initialMode == AnalyzeFile) {
1508           if (*appData.loadGameFile == NULLCHAR) {
1509             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1510             return;
1511           }
1512         }
1513         if (*appData.loadGameFile != NULLCHAR) {
1514             (void) LoadGameFromFile(appData.loadGameFile,
1515                                     appData.loadGameIndex,
1516                                     appData.loadGameFile, TRUE);
1517         } else if (*appData.loadPositionFile != NULLCHAR) {
1518             (void) LoadPositionFromFile(appData.loadPositionFile,
1519                                         appData.loadPositionIndex,
1520                                         appData.loadPositionFile);
1521             /* [HGM] try to make self-starting even after FEN load */
1522             /* to allow automatic setup of fairy variants with wtm */
1523             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1524                 gameMode = BeginningOfGame;
1525                 setboardSpoiledMachineBlack = 1;
1526             }
1527             /* [HGM] loadPos: make that every new game uses the setup */
1528             /* from file as long as we do not switch variant          */
1529             if(!blackPlaysFirst) {
1530                 startedFromPositionFile = TRUE;
1531                 CopyBoard(filePosition, boards[0]);
1532             }
1533         }
1534         if (initialMode == AnalyzeMode) {
1535           if (appData.noChessProgram) {
1536             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1537             return;
1538           }
1539           if (appData.icsActive) {
1540             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1541             return;
1542           }
1543           AnalyzeModeEvent();
1544         } else if (initialMode == AnalyzeFile) {
1545           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1546           ShowThinkingEvent();
1547           AnalyzeFileEvent();
1548           AnalysisPeriodicEvent(1);
1549         } else if (initialMode == MachinePlaysWhite) {
1550           if (appData.noChessProgram) {
1551             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1552                               0, 2);
1553             return;
1554           }
1555           if (appData.icsActive) {
1556             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1557                               0, 2);
1558             return;
1559           }
1560           MachineWhiteEvent();
1561         } else if (initialMode == MachinePlaysBlack) {
1562           if (appData.noChessProgram) {
1563             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1564                               0, 2);
1565             return;
1566           }
1567           if (appData.icsActive) {
1568             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1569                               0, 2);
1570             return;
1571           }
1572           MachineBlackEvent();
1573         } else if (initialMode == TwoMachinesPlay) {
1574           if (appData.noChessProgram) {
1575             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1576                               0, 2);
1577             return;
1578           }
1579           if (appData.icsActive) {
1580             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1581                               0, 2);
1582             return;
1583           }
1584           TwoMachinesEvent();
1585         } else if (initialMode == EditGame) {
1586           EditGameEvent();
1587         } else if (initialMode == EditPosition) {
1588           EditPositionEvent();
1589         } else if (initialMode == Training) {
1590           if (*appData.loadGameFile == NULLCHAR) {
1591             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1592             return;
1593           }
1594           TrainingEvent();
1595         }
1596     }
1597 }
1598
1599 /*
1600  * Establish will establish a contact to a remote host.port.
1601  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1602  *  used to talk to the host.
1603  * Returns 0 if okay, error code if not.
1604  */
1605 int
1606 establish()
1607 {
1608     char buf[MSG_SIZ];
1609
1610     if (*appData.icsCommPort != NULLCHAR) {
1611         /* Talk to the host through a serial comm port */
1612         return OpenCommPort(appData.icsCommPort, &icsPR);
1613
1614     } else if (*appData.gateway != NULLCHAR) {
1615         if (*appData.remoteShell == NULLCHAR) {
1616             /* Use the rcmd protocol to run telnet program on a gateway host */
1617             snprintf(buf, sizeof(buf), "%s %s %s",
1618                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1619             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1620
1621         } else {
1622             /* Use the rsh program to run telnet program on a gateway host */
1623             if (*appData.remoteUser == NULLCHAR) {
1624                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1625                         appData.gateway, appData.telnetProgram,
1626                         appData.icsHost, appData.icsPort);
1627             } else {
1628                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1629                         appData.remoteShell, appData.gateway,
1630                         appData.remoteUser, appData.telnetProgram,
1631                         appData.icsHost, appData.icsPort);
1632             }
1633             return StartChildProcess(buf, "", &icsPR);
1634
1635         }
1636     } else if (appData.useTelnet) {
1637         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1638
1639     } else {
1640         /* TCP socket interface differs somewhat between
1641            Unix and NT; handle details in the front end.
1642            */
1643         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1644     }
1645 }
1646
1647 void EscapeExpand(char *p, char *q)
1648 {       // [HGM] initstring: routine to shape up string arguments
1649         while(*p++ = *q++) if(p[-1] == '\\')
1650             switch(*q++) {
1651                 case 'n': p[-1] = '\n'; break;
1652                 case 'r': p[-1] = '\r'; break;
1653                 case 't': p[-1] = '\t'; break;
1654                 case '\\': p[-1] = '\\'; break;
1655                 case 0: *p = 0; return;
1656                 default: p[-1] = q[-1]; break;
1657             }
1658 }
1659
1660 void
1661 show_bytes(fp, buf, count)
1662      FILE *fp;
1663      char *buf;
1664      int count;
1665 {
1666     while (count--) {
1667         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1668             fprintf(fp, "\\%03o", *buf & 0xff);
1669         } else {
1670             putc(*buf, fp);
1671         }
1672         buf++;
1673     }
1674     fflush(fp);
1675 }
1676
1677 /* Returns an errno value */
1678 int
1679 OutputMaybeTelnet(pr, message, count, outError)
1680      ProcRef pr;
1681      char *message;
1682      int count;
1683      int *outError;
1684 {
1685     char buf[8192], *p, *q, *buflim;
1686     int left, newcount, outcount;
1687
1688     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1689         *appData.gateway != NULLCHAR) {
1690         if (appData.debugMode) {
1691             fprintf(debugFP, ">ICS: ");
1692             show_bytes(debugFP, message, count);
1693             fprintf(debugFP, "\n");
1694         }
1695         return OutputToProcess(pr, message, count, outError);
1696     }
1697
1698     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1699     p = message;
1700     q = buf;
1701     left = count;
1702     newcount = 0;
1703     while (left) {
1704         if (q >= buflim) {
1705             if (appData.debugMode) {
1706                 fprintf(debugFP, ">ICS: ");
1707                 show_bytes(debugFP, buf, newcount);
1708                 fprintf(debugFP, "\n");
1709             }
1710             outcount = OutputToProcess(pr, buf, newcount, outError);
1711             if (outcount < newcount) return -1; /* to be sure */
1712             q = buf;
1713             newcount = 0;
1714         }
1715         if (*p == '\n') {
1716             *q++ = '\r';
1717             newcount++;
1718         } else if (((unsigned char) *p) == TN_IAC) {
1719             *q++ = (char) TN_IAC;
1720             newcount ++;
1721         }
1722         *q++ = *p++;
1723         newcount++;
1724         left--;
1725     }
1726     if (appData.debugMode) {
1727         fprintf(debugFP, ">ICS: ");
1728         show_bytes(debugFP, buf, newcount);
1729         fprintf(debugFP, "\n");
1730     }
1731     outcount = OutputToProcess(pr, buf, newcount, outError);
1732     if (outcount < newcount) return -1; /* to be sure */
1733     return count;
1734 }
1735
1736 void
1737 read_from_player(isr, closure, message, count, error)
1738      InputSourceRef isr;
1739      VOIDSTAR closure;
1740      char *message;
1741      int count;
1742      int error;
1743 {
1744     int outError, outCount;
1745     static int gotEof = 0;
1746
1747     /* Pass data read from player on to ICS */
1748     if (count > 0) {
1749         gotEof = 0;
1750         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1751         if (outCount < count) {
1752             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1753         }
1754     } else if (count < 0) {
1755         RemoveInputSource(isr);
1756         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1757     } else if (gotEof++ > 0) {
1758         RemoveInputSource(isr);
1759         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1760     }
1761 }
1762
1763 void
1764 KeepAlive()
1765 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1766     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1767     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1768     SendToICS("date\n");
1769     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1770 }
1771
1772 /* added routine for printf style output to ics */
1773 void ics_printf(char *format, ...)
1774 {
1775     char buffer[MSG_SIZ];
1776     va_list args;
1777
1778     va_start(args, format);
1779     vsnprintf(buffer, sizeof(buffer), format, args);
1780     buffer[sizeof(buffer)-1] = '\0';
1781     SendToICS(buffer);
1782     va_end(args);
1783 }
1784
1785 void
1786 SendToICS(s)
1787      char *s;
1788 {
1789     int count, outCount, outError;
1790
1791     if (icsPR == NULL) return;
1792
1793     count = strlen(s);
1794     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1795     if (outCount < count) {
1796         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1797     }
1798 }
1799
1800 /* This is used for sending logon scripts to the ICS. Sending
1801    without a delay causes problems when using timestamp on ICC
1802    (at least on my machine). */
1803 void
1804 SendToICSDelayed(s,msdelay)
1805      char *s;
1806      long msdelay;
1807 {
1808     int count, outCount, outError;
1809
1810     if (icsPR == NULL) return;
1811
1812     count = strlen(s);
1813     if (appData.debugMode) {
1814         fprintf(debugFP, ">ICS: ");
1815         show_bytes(debugFP, s, count);
1816         fprintf(debugFP, "\n");
1817     }
1818     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1819                                       msdelay);
1820     if (outCount < count) {
1821         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1822     }
1823 }
1824
1825
1826 /* Remove all highlighting escape sequences in s
1827    Also deletes any suffix starting with '('
1828    */
1829 char *
1830 StripHighlightAndTitle(s)
1831      char *s;
1832 {
1833     static char retbuf[MSG_SIZ];
1834     char *p = retbuf;
1835
1836     while (*s != NULLCHAR) {
1837         while (*s == '\033') {
1838             while (*s != NULLCHAR && !isalpha(*s)) s++;
1839             if (*s != NULLCHAR) s++;
1840         }
1841         while (*s != NULLCHAR && *s != '\033') {
1842             if (*s == '(' || *s == '[') {
1843                 *p = NULLCHAR;
1844                 return retbuf;
1845             }
1846             *p++ = *s++;
1847         }
1848     }
1849     *p = NULLCHAR;
1850     return retbuf;
1851 }
1852
1853 /* Remove all highlighting escape sequences in s */
1854 char *
1855 StripHighlight(s)
1856      char *s;
1857 {
1858     static char retbuf[MSG_SIZ];
1859     char *p = retbuf;
1860
1861     while (*s != NULLCHAR) {
1862         while (*s == '\033') {
1863             while (*s != NULLCHAR && !isalpha(*s)) s++;
1864             if (*s != NULLCHAR) s++;
1865         }
1866         while (*s != NULLCHAR && *s != '\033') {
1867             *p++ = *s++;
1868         }
1869     }
1870     *p = NULLCHAR;
1871     return retbuf;
1872 }
1873
1874 char *variantNames[] = VARIANT_NAMES;
1875 char *
1876 VariantName(v)
1877      VariantClass v;
1878 {
1879     return variantNames[v];
1880 }
1881
1882
1883 /* Identify a variant from the strings the chess servers use or the
1884    PGN Variant tag names we use. */
1885 VariantClass
1886 StringToVariant(e)
1887      char *e;
1888 {
1889     char *p;
1890     int wnum = -1;
1891     VariantClass v = VariantNormal;
1892     int i, found = FALSE;
1893     char buf[MSG_SIZ];
1894     int len;
1895
1896     if (!e) return v;
1897
1898     /* [HGM] skip over optional board-size prefixes */
1899     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1900         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1901         while( *e++ != '_');
1902     }
1903
1904     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1905         v = VariantNormal;
1906         found = TRUE;
1907     } else
1908     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1909       if (StrCaseStr(e, variantNames[i])) {
1910         v = (VariantClass) i;
1911         found = TRUE;
1912         break;
1913       }
1914     }
1915
1916     if (!found) {
1917       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1918           || StrCaseStr(e, "wild/fr")
1919           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1920         v = VariantFischeRandom;
1921       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1922                  (i = 1, p = StrCaseStr(e, "w"))) {
1923         p += i;
1924         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1925         if (isdigit(*p)) {
1926           wnum = atoi(p);
1927         } else {
1928           wnum = -1;
1929         }
1930         switch (wnum) {
1931         case 0: /* FICS only, actually */
1932         case 1:
1933           /* Castling legal even if K starts on d-file */
1934           v = VariantWildCastle;
1935           break;
1936         case 2:
1937         case 3:
1938         case 4:
1939           /* Castling illegal even if K & R happen to start in
1940              normal positions. */
1941           v = VariantNoCastle;
1942           break;
1943         case 5:
1944         case 7:
1945         case 8:
1946         case 10:
1947         case 11:
1948         case 12:
1949         case 13:
1950         case 14:
1951         case 15:
1952         case 18:
1953         case 19:
1954           /* Castling legal iff K & R start in normal positions */
1955           v = VariantNormal;
1956           break;
1957         case 6:
1958         case 20:
1959         case 21:
1960           /* Special wilds for position setup; unclear what to do here */
1961           v = VariantLoadable;
1962           break;
1963         case 9:
1964           /* Bizarre ICC game */
1965           v = VariantTwoKings;
1966           break;
1967         case 16:
1968           v = VariantKriegspiel;
1969           break;
1970         case 17:
1971           v = VariantLosers;
1972           break;
1973         case 22:
1974           v = VariantFischeRandom;
1975           break;
1976         case 23:
1977           v = VariantCrazyhouse;
1978           break;
1979         case 24:
1980           v = VariantBughouse;
1981           break;
1982         case 25:
1983           v = Variant3Check;
1984           break;
1985         case 26:
1986           /* Not quite the same as FICS suicide! */
1987           v = VariantGiveaway;
1988           break;
1989         case 27:
1990           v = VariantAtomic;
1991           break;
1992         case 28:
1993           v = VariantShatranj;
1994           break;
1995
1996         /* Temporary names for future ICC types.  The name *will* change in
1997            the next xboard/WinBoard release after ICC defines it. */
1998         case 29:
1999           v = Variant29;
2000           break;
2001         case 30:
2002           v = Variant30;
2003           break;
2004         case 31:
2005           v = Variant31;
2006           break;
2007         case 32:
2008           v = Variant32;
2009           break;
2010         case 33:
2011           v = Variant33;
2012           break;
2013         case 34:
2014           v = Variant34;
2015           break;
2016         case 35:
2017           v = Variant35;
2018           break;
2019         case 36:
2020           v = Variant36;
2021           break;
2022         case 37:
2023           v = VariantShogi;
2024           break;
2025         case 38:
2026           v = VariantXiangqi;
2027           break;
2028         case 39:
2029           v = VariantCourier;
2030           break;
2031         case 40:
2032           v = VariantGothic;
2033           break;
2034         case 41:
2035           v = VariantCapablanca;
2036           break;
2037         case 42:
2038           v = VariantKnightmate;
2039           break;
2040         case 43:
2041           v = VariantFairy;
2042           break;
2043         case 44:
2044           v = VariantCylinder;
2045           break;
2046         case 45:
2047           v = VariantFalcon;
2048           break;
2049         case 46:
2050           v = VariantCapaRandom;
2051           break;
2052         case 47:
2053           v = VariantBerolina;
2054           break;
2055         case 48:
2056           v = VariantJanus;
2057           break;
2058         case 49:
2059           v = VariantSuper;
2060           break;
2061         case 50:
2062           v = VariantGreat;
2063           break;
2064         case -1:
2065           /* Found "wild" or "w" in the string but no number;
2066              must assume it's normal chess. */
2067           v = VariantNormal;
2068           break;
2069         default:
2070           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2071           if( (len > MSG_SIZ) && appData.debugMode )
2072             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2073
2074           DisplayError(buf, 0);
2075           v = VariantUnknown;
2076           break;
2077         }
2078       }
2079     }
2080     if (appData.debugMode) {
2081       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2082               e, wnum, VariantName(v));
2083     }
2084     return v;
2085 }
2086
2087 static int leftover_start = 0, leftover_len = 0;
2088 char star_match[STAR_MATCH_N][MSG_SIZ];
2089
2090 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2091    advance *index beyond it, and set leftover_start to the new value of
2092    *index; else return FALSE.  If pattern contains the character '*', it
2093    matches any sequence of characters not containing '\r', '\n', or the
2094    character following the '*' (if any), and the matched sequence(s) are
2095    copied into star_match.
2096    */
2097 int
2098 looking_at(buf, index, pattern)
2099      char *buf;
2100      int *index;
2101      char *pattern;
2102 {
2103     char *bufp = &buf[*index], *patternp = pattern;
2104     int star_count = 0;
2105     char *matchp = star_match[0];
2106
2107     for (;;) {
2108         if (*patternp == NULLCHAR) {
2109             *index = leftover_start = bufp - buf;
2110             *matchp = NULLCHAR;
2111             return TRUE;
2112         }
2113         if (*bufp == NULLCHAR) return FALSE;
2114         if (*patternp == '*') {
2115             if (*bufp == *(patternp + 1)) {
2116                 *matchp = NULLCHAR;
2117                 matchp = star_match[++star_count];
2118                 patternp += 2;
2119                 bufp++;
2120                 continue;
2121             } else if (*bufp == '\n' || *bufp == '\r') {
2122                 patternp++;
2123                 if (*patternp == NULLCHAR)
2124                   continue;
2125                 else
2126                   return FALSE;
2127             } else {
2128                 *matchp++ = *bufp++;
2129                 continue;
2130             }
2131         }
2132         if (*patternp != *bufp) return FALSE;
2133         patternp++;
2134         bufp++;
2135     }
2136 }
2137
2138 void
2139 SendToPlayer(data, length)
2140      char *data;
2141      int length;
2142 {
2143     int error, outCount;
2144     outCount = OutputToProcess(NoProc, data, length, &error);
2145     if (outCount < length) {
2146         DisplayFatalError(_("Error writing to display"), error, 1);
2147     }
2148 }
2149
2150 void
2151 PackHolding(packed, holding)
2152      char packed[];
2153      char *holding;
2154 {
2155     char *p = holding;
2156     char *q = packed;
2157     int runlength = 0;
2158     int curr = 9999;
2159     do {
2160         if (*p == curr) {
2161             runlength++;
2162         } else {
2163             switch (runlength) {
2164               case 0:
2165                 break;
2166               case 1:
2167                 *q++ = curr;
2168                 break;
2169               case 2:
2170                 *q++ = curr;
2171                 *q++ = curr;
2172                 break;
2173               default:
2174                 sprintf(q, "%d", runlength);
2175                 while (*q) q++;
2176                 *q++ = curr;
2177                 break;
2178             }
2179             runlength = 1;
2180             curr = *p;
2181         }
2182     } while (*p++);
2183     *q = NULLCHAR;
2184 }
2185
2186 /* Telnet protocol requests from the front end */
2187 void
2188 TelnetRequest(ddww, option)
2189      unsigned char ddww, option;
2190 {
2191     unsigned char msg[3];
2192     int outCount, outError;
2193
2194     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2195
2196     if (appData.debugMode) {
2197         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2198         switch (ddww) {
2199           case TN_DO:
2200             ddwwStr = "DO";
2201             break;
2202           case TN_DONT:
2203             ddwwStr = "DONT";
2204             break;
2205           case TN_WILL:
2206             ddwwStr = "WILL";
2207             break;
2208           case TN_WONT:
2209             ddwwStr = "WONT";
2210             break;
2211           default:
2212             ddwwStr = buf1;
2213             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2214             break;
2215         }
2216         switch (option) {
2217           case TN_ECHO:
2218             optionStr = "ECHO";
2219             break;
2220           default:
2221             optionStr = buf2;
2222             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2223             break;
2224         }
2225         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2226     }
2227     msg[0] = TN_IAC;
2228     msg[1] = ddww;
2229     msg[2] = option;
2230     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2231     if (outCount < 3) {
2232         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2233     }
2234 }
2235
2236 void
2237 DoEcho()
2238 {
2239     if (!appData.icsActive) return;
2240     TelnetRequest(TN_DO, TN_ECHO);
2241 }
2242
2243 void
2244 DontEcho()
2245 {
2246     if (!appData.icsActive) return;
2247     TelnetRequest(TN_DONT, TN_ECHO);
2248 }
2249
2250 void
2251 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2252 {
2253     /* put the holdings sent to us by the server on the board holdings area */
2254     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2255     char p;
2256     ChessSquare piece;
2257
2258     if(gameInfo.holdingsWidth < 2)  return;
2259     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2260         return; // prevent overwriting by pre-board holdings
2261
2262     if( (int)lowestPiece >= BlackPawn ) {
2263         holdingsColumn = 0;
2264         countsColumn = 1;
2265         holdingsStartRow = BOARD_HEIGHT-1;
2266         direction = -1;
2267     } else {
2268         holdingsColumn = BOARD_WIDTH-1;
2269         countsColumn = BOARD_WIDTH-2;
2270         holdingsStartRow = 0;
2271         direction = 1;
2272     }
2273
2274     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2275         board[i][holdingsColumn] = EmptySquare;
2276         board[i][countsColumn]   = (ChessSquare) 0;
2277     }
2278     while( (p=*holdings++) != NULLCHAR ) {
2279         piece = CharToPiece( ToUpper(p) );
2280         if(piece == EmptySquare) continue;
2281         /*j = (int) piece - (int) WhitePawn;*/
2282         j = PieceToNumber(piece);
2283         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2284         if(j < 0) continue;               /* should not happen */
2285         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2286         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2287         board[holdingsStartRow+j*direction][countsColumn]++;
2288     }
2289 }
2290
2291
2292 void
2293 VariantSwitch(Board board, VariantClass newVariant)
2294 {
2295    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2296    static Board oldBoard;
2297
2298    startedFromPositionFile = FALSE;
2299    if(gameInfo.variant == newVariant) return;
2300
2301    /* [HGM] This routine is called each time an assignment is made to
2302     * gameInfo.variant during a game, to make sure the board sizes
2303     * are set to match the new variant. If that means adding or deleting
2304     * holdings, we shift the playing board accordingly
2305     * This kludge is needed because in ICS observe mode, we get boards
2306     * of an ongoing game without knowing the variant, and learn about the
2307     * latter only later. This can be because of the move list we requested,
2308     * in which case the game history is refilled from the beginning anyway,
2309     * but also when receiving holdings of a crazyhouse game. In the latter
2310     * case we want to add those holdings to the already received position.
2311     */
2312
2313
2314    if (appData.debugMode) {
2315      fprintf(debugFP, "Switch board from %s to %s\n",
2316              VariantName(gameInfo.variant), VariantName(newVariant));
2317      setbuf(debugFP, NULL);
2318    }
2319    shuffleOpenings = 0;       /* [HGM] shuffle */
2320    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2321    switch(newVariant)
2322      {
2323      case VariantShogi:
2324        newWidth = 9;  newHeight = 9;
2325        gameInfo.holdingsSize = 7;
2326      case VariantBughouse:
2327      case VariantCrazyhouse:
2328        newHoldingsWidth = 2; break;
2329      case VariantGreat:
2330        newWidth = 10;
2331      case VariantSuper:
2332        newHoldingsWidth = 2;
2333        gameInfo.holdingsSize = 8;
2334        break;
2335      case VariantGothic:
2336      case VariantCapablanca:
2337      case VariantCapaRandom:
2338        newWidth = 10;
2339      default:
2340        newHoldingsWidth = gameInfo.holdingsSize = 0;
2341      };
2342
2343    if(newWidth  != gameInfo.boardWidth  ||
2344       newHeight != gameInfo.boardHeight ||
2345       newHoldingsWidth != gameInfo.holdingsWidth ) {
2346
2347      /* shift position to new playing area, if needed */
2348      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2349        for(i=0; i<BOARD_HEIGHT; i++)
2350          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2351            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2352              board[i][j];
2353        for(i=0; i<newHeight; i++) {
2354          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2355          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2356        }
2357      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2358        for(i=0; i<BOARD_HEIGHT; i++)
2359          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2360            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2361              board[i][j];
2362      }
2363      gameInfo.boardWidth  = newWidth;
2364      gameInfo.boardHeight = newHeight;
2365      gameInfo.holdingsWidth = newHoldingsWidth;
2366      gameInfo.variant = newVariant;
2367      InitDrawingSizes(-2, 0);
2368    } else gameInfo.variant = newVariant;
2369    CopyBoard(oldBoard, board);   // remember correctly formatted board
2370      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2371    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2372 }
2373
2374 static int loggedOn = FALSE;
2375
2376 /*-- Game start info cache: --*/
2377 int gs_gamenum;
2378 char gs_kind[MSG_SIZ];
2379 static char player1Name[128] = "";
2380 static char player2Name[128] = "";
2381 static char cont_seq[] = "\n\\   ";
2382 static int player1Rating = -1;
2383 static int player2Rating = -1;
2384 /*----------------------------*/
2385
2386 ColorClass curColor = ColorNormal;
2387 int suppressKibitz = 0;
2388
2389 // [HGM] seekgraph
2390 Boolean soughtPending = FALSE;
2391 Boolean seekGraphUp;
2392 #define MAX_SEEK_ADS 200
2393 #define SQUARE 0x80
2394 char *seekAdList[MAX_SEEK_ADS];
2395 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2396 float tcList[MAX_SEEK_ADS];
2397 char colorList[MAX_SEEK_ADS];
2398 int nrOfSeekAds = 0;
2399 int minRating = 1010, maxRating = 2800;
2400 int hMargin = 10, vMargin = 20, h, w;
2401 extern int squareSize, lineGap;
2402
2403 void
2404 PlotSeekAd(int i)
2405 {
2406         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2407         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2408         if(r < minRating+100 && r >=0 ) r = minRating+100;
2409         if(r > maxRating) r = maxRating;
2410         if(tc < 1.) tc = 1.;
2411         if(tc > 95.) tc = 95.;
2412         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2413         y = ((double)r - minRating)/(maxRating - minRating)
2414             * (h-vMargin-squareSize/8-1) + vMargin;
2415         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2416         if(strstr(seekAdList[i], " u ")) color = 1;
2417         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2418            !strstr(seekAdList[i], "bullet") &&
2419            !strstr(seekAdList[i], "blitz") &&
2420            !strstr(seekAdList[i], "standard") ) color = 2;
2421         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2422         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2423 }
2424
2425 void
2426 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2427 {
2428         char buf[MSG_SIZ], *ext = "";
2429         VariantClass v = StringToVariant(type);
2430         if(strstr(type, "wild")) {
2431             ext = type + 4; // append wild number
2432             if(v == VariantFischeRandom) type = "chess960"; else
2433             if(v == VariantLoadable) type = "setup"; else
2434             type = VariantName(v);
2435         }
2436         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2437         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2438             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2439             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2440             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2441             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2442             seekNrList[nrOfSeekAds] = nr;
2443             zList[nrOfSeekAds] = 0;
2444             seekAdList[nrOfSeekAds++] = StrSave(buf);
2445             if(plot) PlotSeekAd(nrOfSeekAds-1);
2446         }
2447 }
2448
2449 void
2450 EraseSeekDot(int i)
2451 {
2452     int x = xList[i], y = yList[i], d=squareSize/4, k;
2453     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2454     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2455     // now replot every dot that overlapped
2456     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2457         int xx = xList[k], yy = yList[k];
2458         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2459             DrawSeekDot(xx, yy, colorList[k]);
2460     }
2461 }
2462
2463 void
2464 RemoveSeekAd(int nr)
2465 {
2466         int i;
2467         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2468             EraseSeekDot(i);
2469             if(seekAdList[i]) free(seekAdList[i]);
2470             seekAdList[i] = seekAdList[--nrOfSeekAds];
2471             seekNrList[i] = seekNrList[nrOfSeekAds];
2472             ratingList[i] = ratingList[nrOfSeekAds];
2473             colorList[i]  = colorList[nrOfSeekAds];
2474             tcList[i] = tcList[nrOfSeekAds];
2475             xList[i]  = xList[nrOfSeekAds];
2476             yList[i]  = yList[nrOfSeekAds];
2477             zList[i]  = zList[nrOfSeekAds];
2478             seekAdList[nrOfSeekAds] = NULL;
2479             break;
2480         }
2481 }
2482
2483 Boolean
2484 MatchSoughtLine(char *line)
2485 {
2486     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2487     int nr, base, inc, u=0; char dummy;
2488
2489     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2490        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2491        (u=1) &&
2492        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2493         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2494         // match: compact and save the line
2495         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2496         return TRUE;
2497     }
2498     return FALSE;
2499 }
2500
2501 int
2502 DrawSeekGraph()
2503 {
2504     int i;
2505     if(!seekGraphUp) return FALSE;
2506     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2507     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2508
2509     DrawSeekBackground(0, 0, w, h);
2510     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2511     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2512     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2513         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2514         yy = h-1-yy;
2515         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2516         if(i%500 == 0) {
2517             char buf[MSG_SIZ];
2518             snprintf(buf, MSG_SIZ, "%d", i);
2519             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2520         }
2521     }
2522     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2523     for(i=1; i<100; i+=(i<10?1:5)) {
2524         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2525         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2526         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2527             char buf[MSG_SIZ];
2528             snprintf(buf, MSG_SIZ, "%d", i);
2529             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2530         }
2531     }
2532     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2533     return TRUE;
2534 }
2535
2536 int SeekGraphClick(ClickType click, int x, int y, int moving)
2537 {
2538     static int lastDown = 0, displayed = 0, lastSecond;
2539     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2540         if(click == Release || moving) return FALSE;
2541         nrOfSeekAds = 0;
2542         soughtPending = TRUE;
2543         SendToICS(ics_prefix);
2544         SendToICS("sought\n"); // should this be "sought all"?
2545     } else { // issue challenge based on clicked ad
2546         int dist = 10000; int i, closest = 0, second = 0;
2547         for(i=0; i<nrOfSeekAds; i++) {
2548             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2549             if(d < dist) { dist = d; closest = i; }
2550             second += (d - zList[i] < 120); // count in-range ads
2551             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2552         }
2553         if(dist < 120) {
2554             char buf[MSG_SIZ];
2555             second = (second > 1);
2556             if(displayed != closest || second != lastSecond) {
2557                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2558                 lastSecond = second; displayed = closest;
2559             }
2560             if(click == Press) {
2561                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2562                 lastDown = closest;
2563                 return TRUE;
2564             } // on press 'hit', only show info
2565             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2566             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2567             SendToICS(ics_prefix);
2568             SendToICS(buf);
2569             return TRUE; // let incoming board of started game pop down the graph
2570         } else if(click == Release) { // release 'miss' is ignored
2571             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2572             if(moving == 2) { // right up-click
2573                 nrOfSeekAds = 0; // refresh graph
2574                 soughtPending = TRUE;
2575                 SendToICS(ics_prefix);
2576                 SendToICS("sought\n"); // should this be "sought all"?
2577             }
2578             return TRUE;
2579         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2580         // press miss or release hit 'pop down' seek graph
2581         seekGraphUp = FALSE;
2582         DrawPosition(TRUE, NULL);
2583     }
2584     return TRUE;
2585 }
2586
2587 void
2588 read_from_ics(isr, closure, data, count, error)
2589      InputSourceRef isr;
2590      VOIDSTAR closure;
2591      char *data;
2592      int count;
2593      int error;
2594 {
2595 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2596 #define STARTED_NONE 0
2597 #define STARTED_MOVES 1
2598 #define STARTED_BOARD 2
2599 #define STARTED_OBSERVE 3
2600 #define STARTED_HOLDINGS 4
2601 #define STARTED_CHATTER 5
2602 #define STARTED_COMMENT 6
2603 #define STARTED_MOVES_NOHIDE 7
2604
2605     static int started = STARTED_NONE;
2606     static char parse[20000];
2607     static int parse_pos = 0;
2608     static char buf[BUF_SIZE + 1];
2609     static int firstTime = TRUE, intfSet = FALSE;
2610     static ColorClass prevColor = ColorNormal;
2611     static int savingComment = FALSE;
2612     static int cmatch = 0; // continuation sequence match
2613     char *bp;
2614     char str[MSG_SIZ];
2615     int i, oldi;
2616     int buf_len;
2617     int next_out;
2618     int tkind;
2619     int backup;    /* [DM] For zippy color lines */
2620     char *p;
2621     char talker[MSG_SIZ]; // [HGM] chat
2622     int channel;
2623
2624     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2625
2626     if (appData.debugMode) {
2627       if (!error) {
2628         fprintf(debugFP, "<ICS: ");
2629         show_bytes(debugFP, data, count);
2630         fprintf(debugFP, "\n");
2631       }
2632     }
2633
2634     if (appData.debugMode) { int f = forwardMostMove;
2635         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2636                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2637                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2638     }
2639     if (count > 0) {
2640         /* If last read ended with a partial line that we couldn't parse,
2641            prepend it to the new read and try again. */
2642         if (leftover_len > 0) {
2643             for (i=0; i<leftover_len; i++)
2644               buf[i] = buf[leftover_start + i];
2645         }
2646
2647     /* copy new characters into the buffer */
2648     bp = buf + leftover_len;
2649     buf_len=leftover_len;
2650     for (i=0; i<count; i++)
2651     {
2652         // ignore these
2653         if (data[i] == '\r')
2654             continue;
2655
2656         // join lines split by ICS?
2657         if (!appData.noJoin)
2658         {
2659             /*
2660                 Joining just consists of finding matches against the
2661                 continuation sequence, and discarding that sequence
2662                 if found instead of copying it.  So, until a match
2663                 fails, there's nothing to do since it might be the
2664                 complete sequence, and thus, something we don't want
2665                 copied.
2666             */
2667             if (data[i] == cont_seq[cmatch])
2668             {
2669                 cmatch++;
2670                 if (cmatch == strlen(cont_seq))
2671                 {
2672                     cmatch = 0; // complete match.  just reset the counter
2673
2674                     /*
2675                         it's possible for the ICS to not include the space
2676                         at the end of the last word, making our [correct]
2677                         join operation fuse two separate words.  the server
2678                         does this when the space occurs at the width setting.
2679                     */
2680                     if (!buf_len || buf[buf_len-1] != ' ')
2681                     {
2682                         *bp++ = ' ';
2683                         buf_len++;
2684                     }
2685                 }
2686                 continue;
2687             }
2688             else if (cmatch)
2689             {
2690                 /*
2691                     match failed, so we have to copy what matched before
2692                     falling through and copying this character.  In reality,
2693                     this will only ever be just the newline character, but
2694                     it doesn't hurt to be precise.
2695                 */
2696                 strncpy(bp, cont_seq, cmatch);
2697                 bp += cmatch;
2698                 buf_len += cmatch;
2699                 cmatch = 0;
2700             }
2701         }
2702
2703         // copy this char
2704         *bp++ = data[i];
2705         buf_len++;
2706     }
2707
2708         buf[buf_len] = NULLCHAR;
2709 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2710         next_out = 0;
2711         leftover_start = 0;
2712
2713         i = 0;
2714         while (i < buf_len) {
2715             /* Deal with part of the TELNET option negotiation
2716                protocol.  We refuse to do anything beyond the
2717                defaults, except that we allow the WILL ECHO option,
2718                which ICS uses to turn off password echoing when we are
2719                directly connected to it.  We reject this option
2720                if localLineEditing mode is on (always on in xboard)
2721                and we are talking to port 23, which might be a real
2722                telnet server that will try to keep WILL ECHO on permanently.
2723              */
2724             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2725                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2726                 unsigned char option;
2727                 oldi = i;
2728                 switch ((unsigned char) buf[++i]) {
2729                   case TN_WILL:
2730                     if (appData.debugMode)
2731                       fprintf(debugFP, "\n<WILL ");
2732                     switch (option = (unsigned char) buf[++i]) {
2733                       case TN_ECHO:
2734                         if (appData.debugMode)
2735                           fprintf(debugFP, "ECHO ");
2736                         /* Reply only if this is a change, according
2737                            to the protocol rules. */
2738                         if (remoteEchoOption) break;
2739                         if (appData.localLineEditing &&
2740                             atoi(appData.icsPort) == TN_PORT) {
2741                             TelnetRequest(TN_DONT, TN_ECHO);
2742                         } else {
2743                             EchoOff();
2744                             TelnetRequest(TN_DO, TN_ECHO);
2745                             remoteEchoOption = TRUE;
2746                         }
2747                         break;
2748                       default:
2749                         if (appData.debugMode)
2750                           fprintf(debugFP, "%d ", option);
2751                         /* Whatever this is, we don't want it. */
2752                         TelnetRequest(TN_DONT, option);
2753                         break;
2754                     }
2755                     break;
2756                   case TN_WONT:
2757                     if (appData.debugMode)
2758                       fprintf(debugFP, "\n<WONT ");
2759                     switch (option = (unsigned char) buf[++i]) {
2760                       case TN_ECHO:
2761                         if (appData.debugMode)
2762                           fprintf(debugFP, "ECHO ");
2763                         /* Reply only if this is a change, according
2764                            to the protocol rules. */
2765                         if (!remoteEchoOption) break;
2766                         EchoOn();
2767                         TelnetRequest(TN_DONT, TN_ECHO);
2768                         remoteEchoOption = FALSE;
2769                         break;
2770                       default:
2771                         if (appData.debugMode)
2772                           fprintf(debugFP, "%d ", (unsigned char) option);
2773                         /* Whatever this is, it must already be turned
2774                            off, because we never agree to turn on
2775                            anything non-default, so according to the
2776                            protocol rules, we don't reply. */
2777                         break;
2778                     }
2779                     break;
2780                   case TN_DO:
2781                     if (appData.debugMode)
2782                       fprintf(debugFP, "\n<DO ");
2783                     switch (option = (unsigned char) buf[++i]) {
2784                       default:
2785                         /* Whatever this is, we refuse to do it. */
2786                         if (appData.debugMode)
2787                           fprintf(debugFP, "%d ", option);
2788                         TelnetRequest(TN_WONT, option);
2789                         break;
2790                     }
2791                     break;
2792                   case TN_DONT:
2793                     if (appData.debugMode)
2794                       fprintf(debugFP, "\n<DONT ");
2795                     switch (option = (unsigned char) buf[++i]) {
2796                       default:
2797                         if (appData.debugMode)
2798                           fprintf(debugFP, "%d ", option);
2799                         /* Whatever this is, we are already not doing
2800                            it, because we never agree to do anything
2801                            non-default, so according to the protocol
2802                            rules, we don't reply. */
2803                         break;
2804                     }
2805                     break;
2806                   case TN_IAC:
2807                     if (appData.debugMode)
2808                       fprintf(debugFP, "\n<IAC ");
2809                     /* Doubled IAC; pass it through */
2810                     i--;
2811                     break;
2812                   default:
2813                     if (appData.debugMode)
2814                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2815                     /* Drop all other telnet commands on the floor */
2816                     break;
2817                 }
2818                 if (oldi > next_out)
2819                   SendToPlayer(&buf[next_out], oldi - next_out);
2820                 if (++i > next_out)
2821                   next_out = i;
2822                 continue;
2823             }
2824
2825             /* OK, this at least will *usually* work */
2826             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2827                 loggedOn = TRUE;
2828             }
2829
2830             if (loggedOn && !intfSet) {
2831                 if (ics_type == ICS_ICC) {
2832                   snprintf(str, MSG_SIZ,
2833                           "/set-quietly interface %s\n/set-quietly style 12\n",
2834                           programVersion);
2835                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2836                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2837                 } else if (ics_type == ICS_CHESSNET) {
2838                   snprintf(str, MSG_SIZ, "/style 12\n");
2839                 } else {
2840                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2841                   strcat(str, programVersion);
2842                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2843                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2844                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2845 #ifdef WIN32
2846                   strcat(str, "$iset nohighlight 1\n");
2847 #endif
2848                   strcat(str, "$iset lock 1\n$style 12\n");
2849                 }
2850                 SendToICS(str);
2851                 NotifyFrontendLogin();
2852                 intfSet = TRUE;
2853             }
2854
2855             if (started == STARTED_COMMENT) {
2856                 /* Accumulate characters in comment */
2857                 parse[parse_pos++] = buf[i];
2858                 if (buf[i] == '\n') {
2859                     parse[parse_pos] = NULLCHAR;
2860                     if(chattingPartner>=0) {
2861                         char mess[MSG_SIZ];
2862                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2863                         OutputChatMessage(chattingPartner, mess);
2864                         chattingPartner = -1;
2865                         next_out = i+1; // [HGM] suppress printing in ICS window
2866                     } else
2867                     if(!suppressKibitz) // [HGM] kibitz
2868                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2869                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2870                         int nrDigit = 0, nrAlph = 0, j;
2871                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2872                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2873                         parse[parse_pos] = NULLCHAR;
2874                         // try to be smart: if it does not look like search info, it should go to
2875                         // ICS interaction window after all, not to engine-output window.
2876                         for(j=0; j<parse_pos; j++) { // count letters and digits
2877                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2878                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2879                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2880                         }
2881                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2882                             int depth=0; float score;
2883                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2884                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2885                                 pvInfoList[forwardMostMove-1].depth = depth;
2886                                 pvInfoList[forwardMostMove-1].score = 100*score;
2887                             }
2888                             OutputKibitz(suppressKibitz, parse);
2889                         } else {
2890                             char tmp[MSG_SIZ];
2891                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2892                             SendToPlayer(tmp, strlen(tmp));
2893                         }
2894                         next_out = i+1; // [HGM] suppress printing in ICS window
2895                     }
2896                     started = STARTED_NONE;
2897                 } else {
2898                     /* Don't match patterns against characters in comment */
2899                     i++;
2900                     continue;
2901                 }
2902             }
2903             if (started == STARTED_CHATTER) {
2904                 if (buf[i] != '\n') {
2905                     /* Don't match patterns against characters in chatter */
2906                     i++;
2907                     continue;
2908                 }
2909                 started = STARTED_NONE;
2910                 if(suppressKibitz) next_out = i+1;
2911             }
2912
2913             /* Kludge to deal with rcmd protocol */
2914             if (firstTime && looking_at(buf, &i, "\001*")) {
2915                 DisplayFatalError(&buf[1], 0, 1);
2916                 continue;
2917             } else {
2918                 firstTime = FALSE;
2919             }
2920
2921             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2922                 ics_type = ICS_ICC;
2923                 ics_prefix = "/";
2924                 if (appData.debugMode)
2925                   fprintf(debugFP, "ics_type %d\n", ics_type);
2926                 continue;
2927             }
2928             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2929                 ics_type = ICS_FICS;
2930                 ics_prefix = "$";
2931                 if (appData.debugMode)
2932                   fprintf(debugFP, "ics_type %d\n", ics_type);
2933                 continue;
2934             }
2935             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2936                 ics_type = ICS_CHESSNET;
2937                 ics_prefix = "/";
2938                 if (appData.debugMode)
2939                   fprintf(debugFP, "ics_type %d\n", ics_type);
2940                 continue;
2941             }
2942
2943             if (!loggedOn &&
2944                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2945                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2946                  looking_at(buf, &i, "will be \"*\""))) {
2947               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2948               continue;
2949             }
2950
2951             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2952               char buf[MSG_SIZ];
2953               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2954               DisplayIcsInteractionTitle(buf);
2955               have_set_title = TRUE;
2956             }
2957
2958             /* skip finger notes */
2959             if (started == STARTED_NONE &&
2960                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2961                  (buf[i] == '1' && buf[i+1] == '0')) &&
2962                 buf[i+2] == ':' && buf[i+3] == ' ') {
2963               started = STARTED_CHATTER;
2964               i += 3;
2965               continue;
2966             }
2967
2968             oldi = i;
2969             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2970             if(appData.seekGraph) {
2971                 if(soughtPending && MatchSoughtLine(buf+i)) {
2972                     i = strstr(buf+i, "rated") - buf;
2973                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2974                     next_out = leftover_start = i;
2975                     started = STARTED_CHATTER;
2976                     suppressKibitz = TRUE;
2977                     continue;
2978                 }
2979                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2980                         && looking_at(buf, &i, "* ads displayed")) {
2981                     soughtPending = FALSE;
2982                     seekGraphUp = TRUE;
2983                     DrawSeekGraph();
2984                     continue;
2985                 }
2986                 if(appData.autoRefresh) {
2987                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2988                         int s = (ics_type == ICS_ICC); // ICC format differs
2989                         if(seekGraphUp)
2990                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2991                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2992                         looking_at(buf, &i, "*% "); // eat prompt
2993                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2994                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2995                         next_out = i; // suppress
2996                         continue;
2997                     }
2998                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2999                         char *p = star_match[0];
3000                         while(*p) {
3001                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3002                             while(*p && *p++ != ' '); // next
3003                         }
3004                         looking_at(buf, &i, "*% "); // eat prompt
3005                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3006                         next_out = i;
3007                         continue;
3008                     }
3009                 }
3010             }
3011
3012             /* skip formula vars */
3013             if (started == STARTED_NONE &&
3014                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3015               started = STARTED_CHATTER;
3016               i += 3;
3017               continue;
3018             }
3019
3020             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3021             if (appData.autoKibitz && started == STARTED_NONE &&
3022                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3023                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3024                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3025                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3026                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3027                         suppressKibitz = TRUE;
3028                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3029                         next_out = i;
3030                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3031                                 && (gameMode == IcsPlayingWhite)) ||
3032                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3033                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3034                             started = STARTED_CHATTER; // own kibitz we simply discard
3035                         else {
3036                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3037                             parse_pos = 0; parse[0] = NULLCHAR;
3038                             savingComment = TRUE;
3039                             suppressKibitz = gameMode != IcsObserving ? 2 :
3040                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3041                         }
3042                         continue;
3043                 } else
3044                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3045                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3046                          && atoi(star_match[0])) {
3047                     // suppress the acknowledgements of our own autoKibitz
3048                     char *p;
3049                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3051                     SendToPlayer(star_match[0], strlen(star_match[0]));
3052                     if(looking_at(buf, &i, "*% ")) // eat prompt
3053                         suppressKibitz = FALSE;
3054                     next_out = i;
3055                     continue;
3056                 }
3057             } // [HGM] kibitz: end of patch
3058
3059             // [HGM] chat: intercept tells by users for which we have an open chat window
3060             channel = -1;
3061             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3062                                            looking_at(buf, &i, "* whispers:") ||
3063                                            looking_at(buf, &i, "* kibitzes:") ||
3064                                            looking_at(buf, &i, "* shouts:") ||
3065                                            looking_at(buf, &i, "* c-shouts:") ||
3066                                            looking_at(buf, &i, "--> * ") ||
3067                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3068                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3069                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3070                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3071                 int p;
3072                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3073                 chattingPartner = -1;
3074
3075                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3076                 for(p=0; p<MAX_CHAT; p++) {
3077                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3078                     talker[0] = '['; strcat(talker, "] ");
3079                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3080                     chattingPartner = p; break;
3081                     }
3082                 } else
3083                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3084                 for(p=0; p<MAX_CHAT; p++) {
3085                     if(!strcmp("kibitzes", chatPartner[p])) {
3086                         talker[0] = '['; strcat(talker, "] ");
3087                         chattingPartner = p; break;
3088                     }
3089                 } else
3090                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3091                 for(p=0; p<MAX_CHAT; p++) {
3092                     if(!strcmp("whispers", chatPartner[p])) {
3093                         talker[0] = '['; strcat(talker, "] ");
3094                         chattingPartner = p; break;
3095                     }
3096                 } else
3097                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3098                   if(buf[i-8] == '-' && buf[i-3] == 't')
3099                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3100                     if(!strcmp("c-shouts", chatPartner[p])) {
3101                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3102                         chattingPartner = p; break;
3103                     }
3104                   }
3105                   if(chattingPartner < 0)
3106                   for(p=0; p<MAX_CHAT; p++) {
3107                     if(!strcmp("shouts", chatPartner[p])) {
3108                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3109                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3110                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3111                         chattingPartner = p; break;
3112                     }
3113                   }
3114                 }
3115                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3116                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3117                     talker[0] = 0; Colorize(ColorTell, FALSE);
3118                     chattingPartner = p; break;
3119                 }
3120                 if(chattingPartner<0) i = oldi; else {
3121                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3122                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3123                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3124                     started = STARTED_COMMENT;
3125                     parse_pos = 0; parse[0] = NULLCHAR;
3126                     savingComment = 3 + chattingPartner; // counts as TRUE
3127                     suppressKibitz = TRUE;
3128                     continue;
3129                 }
3130             } // [HGM] chat: end of patch
3131
3132           backup = i;
3133             if (appData.zippyTalk || appData.zippyPlay) {
3134                 /* [DM] Backup address for color zippy lines */
3135 #if ZIPPY
3136                if (loggedOn == TRUE)
3137                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3138                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3139 #endif
3140             } // [DM] 'else { ' deleted
3141                 if (
3142                     /* Regular tells and says */
3143                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3144                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3145                     looking_at(buf, &i, "* says: ") ||
3146                     /* Don't color "message" or "messages" output */
3147                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3148                     looking_at(buf, &i, "*. * at *:*: ") ||
3149                     looking_at(buf, &i, "--* (*:*): ") ||
3150                     /* Message notifications (same color as tells) */
3151                     looking_at(buf, &i, "* has left a message ") ||
3152                     looking_at(buf, &i, "* just sent you a message:\n") ||
3153                     /* Whispers and kibitzes */
3154                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3155                     looking_at(buf, &i, "* kibitzes: ") ||
3156                     /* Channel tells */
3157                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3158
3159                   if (tkind == 1 && strchr(star_match[0], ':')) {
3160                       /* Avoid "tells you:" spoofs in channels */
3161                      tkind = 3;
3162                   }
3163                   if (star_match[0][0] == NULLCHAR ||
3164                       strchr(star_match[0], ' ') ||
3165                       (tkind == 3 && strchr(star_match[1], ' '))) {
3166                     /* Reject bogus matches */
3167                     i = oldi;
3168                   } else {
3169                     if (appData.colorize) {
3170                       if (oldi > next_out) {
3171                         SendToPlayer(&buf[next_out], oldi - next_out);
3172                         next_out = oldi;
3173                       }
3174                       switch (tkind) {
3175                       case 1:
3176                         Colorize(ColorTell, FALSE);
3177                         curColor = ColorTell;
3178                         break;
3179                       case 2:
3180                         Colorize(ColorKibitz, FALSE);
3181                         curColor = ColorKibitz;
3182                         break;
3183                       case 3:
3184                         p = strrchr(star_match[1], '(');
3185                         if (p == NULL) {
3186                           p = star_match[1];
3187                         } else {
3188                           p++;
3189                         }
3190                         if (atoi(p) == 1) {
3191                           Colorize(ColorChannel1, FALSE);
3192                           curColor = ColorChannel1;
3193                         } else {
3194                           Colorize(ColorChannel, FALSE);
3195                           curColor = ColorChannel;
3196                         }
3197                         break;
3198                       case 5:
3199                         curColor = ColorNormal;
3200                         break;
3201                       }
3202                     }
3203                     if (started == STARTED_NONE && appData.autoComment &&
3204                         (gameMode == IcsObserving ||
3205                          gameMode == IcsPlayingWhite ||
3206                          gameMode == IcsPlayingBlack)) {
3207                       parse_pos = i - oldi;
3208                       memcpy(parse, &buf[oldi], parse_pos);
3209                       parse[parse_pos] = NULLCHAR;
3210                       started = STARTED_COMMENT;
3211                       savingComment = TRUE;
3212                     } else {
3213                       started = STARTED_CHATTER;
3214                       savingComment = FALSE;
3215                     }
3216                     loggedOn = TRUE;
3217                     continue;
3218                   }
3219                 }
3220
3221                 if (looking_at(buf, &i, "* s-shouts: ") ||
3222                     looking_at(buf, &i, "* c-shouts: ")) {
3223                     if (appData.colorize) {
3224                         if (oldi > next_out) {
3225                             SendToPlayer(&buf[next_out], oldi - next_out);
3226                             next_out = oldi;
3227                         }
3228                         Colorize(ColorSShout, FALSE);
3229                         curColor = ColorSShout;
3230                     }
3231                     loggedOn = TRUE;
3232                     started = STARTED_CHATTER;
3233                     continue;
3234                 }
3235
3236                 if (looking_at(buf, &i, "--->")) {
3237                     loggedOn = TRUE;
3238                     continue;
3239                 }
3240
3241                 if (looking_at(buf, &i, "* shouts: ") ||
3242                     looking_at(buf, &i, "--> ")) {
3243                     if (appData.colorize) {
3244                         if (oldi > next_out) {
3245                             SendToPlayer(&buf[next_out], oldi - next_out);
3246                             next_out = oldi;
3247                         }
3248                         Colorize(ColorShout, FALSE);
3249                         curColor = ColorShout;
3250                     }
3251                     loggedOn = TRUE;
3252                     started = STARTED_CHATTER;
3253                     continue;
3254                 }
3255
3256                 if (looking_at( buf, &i, "Challenge:")) {
3257                     if (appData.colorize) {
3258                         if (oldi > next_out) {
3259                             SendToPlayer(&buf[next_out], oldi - next_out);
3260                             next_out = oldi;
3261                         }
3262                         Colorize(ColorChallenge, FALSE);
3263                         curColor = ColorChallenge;
3264                     }
3265                     loggedOn = TRUE;
3266                     continue;
3267                 }
3268
3269                 if (looking_at(buf, &i, "* offers you") ||
3270                     looking_at(buf, &i, "* offers to be") ||
3271                     looking_at(buf, &i, "* would like to") ||
3272                     looking_at(buf, &i, "* requests to") ||
3273                     looking_at(buf, &i, "Your opponent offers") ||
3274                     looking_at(buf, &i, "Your opponent requests")) {
3275
3276                     if (appData.colorize) {
3277                         if (oldi > next_out) {
3278                             SendToPlayer(&buf[next_out], oldi - next_out);
3279                             next_out = oldi;
3280                         }
3281                         Colorize(ColorRequest, FALSE);
3282                         curColor = ColorRequest;
3283                     }
3284                     continue;
3285                 }
3286
3287                 if (looking_at(buf, &i, "* (*) seeking")) {
3288                     if (appData.colorize) {
3289                         if (oldi > next_out) {
3290                             SendToPlayer(&buf[next_out], oldi - next_out);
3291                             next_out = oldi;
3292                         }
3293                         Colorize(ColorSeek, FALSE);
3294                         curColor = ColorSeek;
3295                     }
3296                     continue;
3297             }
3298
3299           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3300
3301             if (looking_at(buf, &i, "\\   ")) {
3302                 if (prevColor != ColorNormal) {
3303                     if (oldi > next_out) {
3304                         SendToPlayer(&buf[next_out], oldi - next_out);
3305                         next_out = oldi;
3306                     }
3307                     Colorize(prevColor, TRUE);
3308                     curColor = prevColor;
3309                 }
3310                 if (savingComment) {
3311                     parse_pos = i - oldi;
3312                     memcpy(parse, &buf[oldi], parse_pos);
3313                     parse[parse_pos] = NULLCHAR;
3314                     started = STARTED_COMMENT;
3315                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3316                         chattingPartner = savingComment - 3; // kludge to remember the box
3317                 } else {
3318                     started = STARTED_CHATTER;
3319                 }
3320                 continue;
3321             }
3322
3323             if (looking_at(buf, &i, "Black Strength :") ||
3324                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3325                 looking_at(buf, &i, "<10>") ||
3326                 looking_at(buf, &i, "#@#")) {
3327                 /* Wrong board style */
3328                 loggedOn = TRUE;
3329                 SendToICS(ics_prefix);
3330                 SendToICS("set style 12\n");
3331                 SendToICS(ics_prefix);
3332                 SendToICS("refresh\n");
3333                 continue;
3334             }
3335
3336             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3337                 ICSInitScript();
3338                 have_sent_ICS_logon = 1;
3339                 continue;
3340             }
3341
3342             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3343                 (looking_at(buf, &i, "\n<12> ") ||
3344                  looking_at(buf, &i, "<12> "))) {
3345                 loggedOn = TRUE;
3346                 if (oldi > next_out) {
3347                     SendToPlayer(&buf[next_out], oldi - next_out);
3348                 }
3349                 next_out = i;
3350                 started = STARTED_BOARD;
3351                 parse_pos = 0;
3352                 continue;
3353             }
3354
3355             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3356                 looking_at(buf, &i, "<b1> ")) {
3357                 if (oldi > next_out) {
3358                     SendToPlayer(&buf[next_out], oldi - next_out);
3359                 }
3360                 next_out = i;
3361                 started = STARTED_HOLDINGS;
3362                 parse_pos = 0;
3363                 continue;
3364             }
3365
3366             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3367                 loggedOn = TRUE;
3368                 /* Header for a move list -- first line */
3369
3370                 switch (ics_getting_history) {
3371                   case H_FALSE:
3372                     switch (gameMode) {
3373                       case IcsIdle:
3374                       case BeginningOfGame:
3375                         /* User typed "moves" or "oldmoves" while we
3376                            were idle.  Pretend we asked for these
3377                            moves and soak them up so user can step
3378                            through them and/or save them.
3379                            */
3380                         Reset(FALSE, TRUE);
3381                         gameMode = IcsObserving;
3382                         ModeHighlight();
3383                         ics_gamenum = -1;
3384                         ics_getting_history = H_GOT_UNREQ_HEADER;
3385                         break;
3386                       case EditGame: /*?*/
3387                       case EditPosition: /*?*/
3388                         /* Should above feature work in these modes too? */
3389                         /* For now it doesn't */
3390                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3391                         break;
3392                       default:
3393                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3394                         break;
3395                     }
3396                     break;
3397                   case H_REQUESTED:
3398                     /* Is this the right one? */
3399                     if (gameInfo.white && gameInfo.black &&
3400                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3401                         strcmp(gameInfo.black, star_match[2]) == 0) {
3402                         /* All is well */
3403                         ics_getting_history = H_GOT_REQ_HEADER;
3404                     }
3405                     break;
3406                   case H_GOT_REQ_HEADER:
3407                   case H_GOT_UNREQ_HEADER:
3408                   case H_GOT_UNWANTED_HEADER:
3409                   case H_GETTING_MOVES:
3410                     /* Should not happen */
3411                     DisplayError(_("Error gathering move list: two headers"), 0);
3412                     ics_getting_history = H_FALSE;
3413                     break;
3414                 }
3415
3416                 /* Save player ratings into gameInfo if needed */
3417                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3418                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3419                     (gameInfo.whiteRating == -1 ||
3420                      gameInfo.blackRating == -1)) {
3421
3422                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3423                     gameInfo.blackRating = string_to_rating(star_match[3]);
3424                     if (appData.debugMode)
3425                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3426                               gameInfo.whiteRating, gameInfo.blackRating);
3427                 }
3428                 continue;
3429             }
3430
3431             if (looking_at(buf, &i,
3432               "* * match, initial time: * minute*, increment: * second")) {
3433                 /* Header for a move list -- second line */
3434                 /* Initial board will follow if this is a wild game */
3435                 if (gameInfo.event != NULL) free(gameInfo.event);
3436                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3437                 gameInfo.event = StrSave(str);
3438                 /* [HGM] we switched variant. Translate boards if needed. */
3439                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3440                 continue;
3441             }
3442
3443             if (looking_at(buf, &i, "Move  ")) {
3444                 /* Beginning of a move list */
3445                 switch (ics_getting_history) {
3446                   case H_FALSE:
3447                     /* Normally should not happen */
3448                     /* Maybe user hit reset while we were parsing */
3449                     break;
3450                   case H_REQUESTED:
3451                     /* Happens if we are ignoring a move list that is not
3452                      * the one we just requested.  Common if the user
3453                      * tries to observe two games without turning off
3454                      * getMoveList */
3455                     break;
3456                   case H_GETTING_MOVES:
3457                     /* Should not happen */
3458                     DisplayError(_("Error gathering move list: nested"), 0);
3459                     ics_getting_history = H_FALSE;
3460                     break;
3461                   case H_GOT_REQ_HEADER:
3462                     ics_getting_history = H_GETTING_MOVES;
3463                     started = STARTED_MOVES;
3464                     parse_pos = 0;
3465                     if (oldi > next_out) {
3466                         SendToPlayer(&buf[next_out], oldi - next_out);
3467                     }
3468                     break;
3469                   case H_GOT_UNREQ_HEADER:
3470                     ics_getting_history = H_GETTING_MOVES;
3471                     started = STARTED_MOVES_NOHIDE;
3472                     parse_pos = 0;
3473                     break;
3474                   case H_GOT_UNWANTED_HEADER:
3475                     ics_getting_history = H_FALSE;
3476                     break;
3477                 }
3478                 continue;
3479             }
3480
3481             if (looking_at(buf, &i, "% ") ||
3482                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3483                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3484                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3485                     soughtPending = FALSE;
3486                     seekGraphUp = TRUE;
3487                     DrawSeekGraph();
3488                 }
3489                 if(suppressKibitz) next_out = i;
3490                 savingComment = FALSE;
3491                 suppressKibitz = 0;
3492                 switch (started) {
3493                   case STARTED_MOVES:
3494                   case STARTED_MOVES_NOHIDE:
3495                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3496                     parse[parse_pos + i - oldi] = NULLCHAR;
3497                     ParseGameHistory(parse);
3498 #if ZIPPY
3499                     if (appData.zippyPlay && first.initDone) {
3500                         FeedMovesToProgram(&first, forwardMostMove);
3501                         if (gameMode == IcsPlayingWhite) {
3502                             if (WhiteOnMove(forwardMostMove)) {
3503                                 if (first.sendTime) {
3504                                   if (first.useColors) {
3505                                     SendToProgram("black\n", &first);
3506                                   }
3507                                   SendTimeRemaining(&first, TRUE);
3508                                 }
3509                                 if (first.useColors) {
3510                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3511                                 }
3512                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3513                                 first.maybeThinking = TRUE;
3514                             } else {
3515                                 if (first.usePlayother) {
3516                                   if (first.sendTime) {
3517                                     SendTimeRemaining(&first, TRUE);
3518                                   }
3519                                   SendToProgram("playother\n", &first);
3520                                   firstMove = FALSE;
3521                                 } else {
3522                                   firstMove = TRUE;
3523                                 }
3524                             }
3525                         } else if (gameMode == IcsPlayingBlack) {
3526                             if (!WhiteOnMove(forwardMostMove)) {
3527                                 if (first.sendTime) {
3528                                   if (first.useColors) {
3529                                     SendToProgram("white\n", &first);
3530                                   }
3531                                   SendTimeRemaining(&first, FALSE);
3532                                 }
3533                                 if (first.useColors) {
3534                                   SendToProgram("black\n", &first);
3535                                 }
3536                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3537                                 first.maybeThinking = TRUE;
3538                             } else {
3539                                 if (first.usePlayother) {
3540                                   if (first.sendTime) {
3541                                     SendTimeRemaining(&first, FALSE);
3542                                   }
3543                                   SendToProgram("playother\n", &first);
3544                                   firstMove = FALSE;
3545                                 } else {
3546                                   firstMove = TRUE;
3547                                 }
3548                             }
3549                         }
3550                     }
3551 #endif
3552                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3553                         /* Moves came from oldmoves or moves command
3554                            while we weren't doing anything else.
3555                            */
3556                         currentMove = forwardMostMove;
3557                         ClearHighlights();/*!!could figure this out*/
3558                         flipView = appData.flipView;
3559                         DrawPosition(TRUE, boards[currentMove]);
3560                         DisplayBothClocks();
3561                         snprintf(str, MSG_SIZ, "%s vs. %s",
3562                                 gameInfo.white, gameInfo.black);
3563                         DisplayTitle(str);
3564                         gameMode = IcsIdle;
3565                     } else {
3566                         /* Moves were history of an active game */
3567                         if (gameInfo.resultDetails != NULL) {
3568                             free(gameInfo.resultDetails);
3569                             gameInfo.resultDetails = NULL;
3570                         }
3571                     }
3572                     HistorySet(parseList, backwardMostMove,
3573                                forwardMostMove, currentMove-1);
3574                     DisplayMove(currentMove - 1);
3575                     if (started == STARTED_MOVES) next_out = i;
3576                     started = STARTED_NONE;
3577                     ics_getting_history = H_FALSE;
3578                     break;
3579
3580                   case STARTED_OBSERVE:
3581                     started = STARTED_NONE;
3582                     SendToICS(ics_prefix);
3583                     SendToICS("refresh\n");
3584                     break;
3585
3586                   default:
3587                     break;
3588                 }
3589                 if(bookHit) { // [HGM] book: simulate book reply
3590                     static char bookMove[MSG_SIZ]; // a bit generous?
3591
3592                     programStats.nodes = programStats.depth = programStats.time =
3593                     programStats.score = programStats.got_only_move = 0;
3594                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3595
3596                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3597                     strcat(bookMove, bookHit);
3598                     HandleMachineMove(bookMove, &first);
3599                 }
3600                 continue;
3601             }
3602
3603             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3604                  started == STARTED_HOLDINGS ||
3605                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3606                 /* Accumulate characters in move list or board */
3607                 parse[parse_pos++] = buf[i];
3608             }
3609
3610             /* Start of game messages.  Mostly we detect start of game
3611                when the first board image arrives.  On some versions
3612                of the ICS, though, we need to do a "refresh" after starting
3613                to observe in order to get the current board right away. */
3614             if (looking_at(buf, &i, "Adding game * to observation list")) {
3615                 started = STARTED_OBSERVE;
3616                 continue;
3617             }
3618
3619             /* Handle auto-observe */
3620             if (appData.autoObserve &&
3621                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3622                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3623                 char *player;
3624                 /* Choose the player that was highlighted, if any. */
3625                 if (star_match[0][0] == '\033' ||
3626                     star_match[1][0] != '\033') {
3627                     player = star_match[0];
3628                 } else {
3629                     player = star_match[2];
3630                 }
3631                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3632                         ics_prefix, StripHighlightAndTitle(player));
3633                 SendToICS(str);
3634
3635                 /* Save ratings from notify string */
3636                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3637                 player1Rating = string_to_rating(star_match[1]);
3638                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3639                 player2Rating = string_to_rating(star_match[3]);
3640
3641                 if (appData.debugMode)
3642                   fprintf(debugFP,
3643                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3644                           player1Name, player1Rating,
3645                           player2Name, player2Rating);
3646
3647                 continue;
3648             }
3649
3650             /* Deal with automatic examine mode after a game,
3651                and with IcsObserving -> IcsExamining transition */
3652             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3653                 looking_at(buf, &i, "has made you an examiner of game *")) {
3654
3655                 int gamenum = atoi(star_match[0]);
3656                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3657                     gamenum == ics_gamenum) {
3658                     /* We were already playing or observing this game;
3659                        no need to refetch history */
3660                     gameMode = IcsExamining;
3661                     if (pausing) {
3662                         pauseExamForwardMostMove = forwardMostMove;
3663                     } else if (currentMove < forwardMostMove) {
3664                         ForwardInner(forwardMostMove);
3665                     }
3666                 } else {
3667                     /* I don't think this case really can happen */
3668                     SendToICS(ics_prefix);
3669                     SendToICS("refresh\n");
3670                 }
3671                 continue;
3672             }
3673
3674             /* Error messages */
3675 //          if (ics_user_moved) {
3676             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3677                 if (looking_at(buf, &i, "Illegal move") ||
3678                     looking_at(buf, &i, "Not a legal move") ||
3679                     looking_at(buf, &i, "Your king is in check") ||
3680                     looking_at(buf, &i, "It isn't your turn") ||
3681                     looking_at(buf, &i, "It is not your move")) {
3682                     /* Illegal move */
3683                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3684                         currentMove = forwardMostMove-1;
3685                         DisplayMove(currentMove - 1); /* before DMError */
3686                         DrawPosition(FALSE, boards[currentMove]);
3687                         SwitchClocks(forwardMostMove-1); // [HGM] race
3688                         DisplayBothClocks();
3689                     }
3690                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3691                     ics_user_moved = 0;
3692                     continue;
3693                 }
3694             }
3695
3696             if (looking_at(buf, &i, "still have time") ||
3697                 looking_at(buf, &i, "not out of time") ||
3698                 looking_at(buf, &i, "either player is out of time") ||
3699                 looking_at(buf, &i, "has timeseal; checking")) {
3700                 /* We must have called his flag a little too soon */
3701                 whiteFlag = blackFlag = FALSE;
3702                 continue;
3703             }
3704
3705             if (looking_at(buf, &i, "added * seconds to") ||
3706                 looking_at(buf, &i, "seconds were added to")) {
3707                 /* Update the clocks */
3708                 SendToICS(ics_prefix);
3709                 SendToICS("refresh\n");
3710                 continue;
3711             }
3712
3713             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3714                 ics_clock_paused = TRUE;
3715                 StopClocks();
3716                 continue;
3717             }
3718
3719             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3720                 ics_clock_paused = FALSE;
3721                 StartClocks();
3722                 continue;
3723             }
3724
3725             /* Grab player ratings from the Creating: message.
3726                Note we have to check for the special case when
3727                the ICS inserts things like [white] or [black]. */
3728             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3729                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3730                 /* star_matches:
3731                    0    player 1 name (not necessarily white)
3732                    1    player 1 rating
3733                    2    empty, white, or black (IGNORED)
3734                    3    player 2 name (not necessarily black)
3735                    4    player 2 rating
3736
3737                    The names/ratings are sorted out when the game
3738                    actually starts (below).
3739                 */
3740                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3741                 player1Rating = string_to_rating(star_match[1]);
3742                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3743                 player2Rating = string_to_rating(star_match[4]);
3744
3745                 if (appData.debugMode)
3746                   fprintf(debugFP,
3747                           "Ratings from 'Creating:' %s %d, %s %d\n",
3748                           player1Name, player1Rating,
3749                           player2Name, player2Rating);
3750
3751                 continue;
3752             }
3753
3754             /* Improved generic start/end-of-game messages */
3755             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3756                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3757                 /* If tkind == 0: */
3758                 /* star_match[0] is the game number */
3759                 /*           [1] is the white player's name */
3760                 /*           [2] is the black player's name */
3761                 /* For end-of-game: */
3762                 /*           [3] is the reason for the game end */
3763                 /*           [4] is a PGN end game-token, preceded by " " */
3764                 /* For start-of-game: */
3765                 /*           [3] begins with "Creating" or "Continuing" */
3766                 /*           [4] is " *" or empty (don't care). */
3767                 int gamenum = atoi(star_match[0]);
3768                 char *whitename, *blackname, *why, *endtoken;
3769                 ChessMove endtype = EndOfFile;
3770
3771                 if (tkind == 0) {
3772                   whitename = star_match[1];
3773                   blackname = star_match[2];
3774                   why = star_match[3];
3775                   endtoken = star_match[4];
3776                 } else {
3777                   whitename = star_match[1];
3778                   blackname = star_match[3];
3779                   why = star_match[5];
3780                   endtoken = star_match[6];
3781                 }
3782
3783                 /* Game start messages */
3784                 if (strncmp(why, "Creating ", 9) == 0 ||
3785                     strncmp(why, "Continuing ", 11) == 0) {
3786                     gs_gamenum = gamenum;
3787                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3788                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3789 #if ZIPPY
3790                     if (appData.zippyPlay) {
3791                         ZippyGameStart(whitename, blackname);
3792                     }
3793 #endif /*ZIPPY*/
3794                     partnerBoardValid = FALSE; // [HGM] bughouse
3795                     continue;
3796                 }
3797
3798                 /* Game end messages */
3799                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3800                     ics_gamenum != gamenum) {
3801                     continue;
3802                 }
3803                 while (endtoken[0] == ' ') endtoken++;
3804                 switch (endtoken[0]) {
3805                   case '*':
3806                   default:
3807                     endtype = GameUnfinished;
3808                     break;
3809                   case '0':
3810                     endtype = BlackWins;
3811                     break;
3812                   case '1':
3813                     if (endtoken[1] == '/')
3814                       endtype = GameIsDrawn;
3815                     else
3816                       endtype = WhiteWins;
3817                     break;
3818                 }
3819                 GameEnds(endtype, why, GE_ICS);
3820 #if ZIPPY
3821                 if (appData.zippyPlay && first.initDone) {
3822                     ZippyGameEnd(endtype, why);
3823                     if (first.pr == NULL) {
3824                       /* Start the next process early so that we'll
3825                          be ready for the next challenge */
3826                       StartChessProgram(&first);
3827                     }
3828                     /* Send "new" early, in case this command takes
3829                        a long time to finish, so that we'll be ready
3830                        for the next challenge. */
3831                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3832                     Reset(TRUE, TRUE);
3833                 }
3834 #endif /*ZIPPY*/
3835                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3836                 continue;
3837             }
3838
3839             if (looking_at(buf, &i, "Removing game * from observation") ||
3840                 looking_at(buf, &i, "no longer observing game *") ||
3841                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3842                 if (gameMode == IcsObserving &&
3843                     atoi(star_match[0]) == ics_gamenum)
3844                   {
3845                       /* icsEngineAnalyze */
3846                       if (appData.icsEngineAnalyze) {
3847                             ExitAnalyzeMode();
3848                             ModeHighlight();
3849                       }
3850                       StopClocks();
3851                       gameMode = IcsIdle;
3852                       ics_gamenum = -1;
3853                       ics_user_moved = FALSE;
3854                   }
3855                 continue;
3856             }
3857
3858             if (looking_at(buf, &i, "no longer examining game *")) {
3859                 if (gameMode == IcsExamining &&
3860                     atoi(star_match[0]) == ics_gamenum)
3861                   {
3862                       gameMode = IcsIdle;
3863                       ics_gamenum = -1;
3864                       ics_user_moved = FALSE;
3865                   }
3866                 continue;
3867             }
3868
3869             /* Advance leftover_start past any newlines we find,
3870                so only partial lines can get reparsed */
3871             if (looking_at(buf, &i, "\n")) {
3872                 prevColor = curColor;
3873                 if (curColor != ColorNormal) {
3874                     if (oldi > next_out) {
3875                         SendToPlayer(&buf[next_out], oldi - next_out);
3876                         next_out = oldi;
3877                     }
3878                     Colorize(ColorNormal, FALSE);
3879                     curColor = ColorNormal;
3880                 }
3881                 if (started == STARTED_BOARD) {
3882                     started = STARTED_NONE;
3883                     parse[parse_pos] = NULLCHAR;
3884                     ParseBoard12(parse);
3885                     ics_user_moved = 0;
3886
3887                     /* Send premove here */
3888                     if (appData.premove) {
3889                       char str[MSG_SIZ];
3890                       if (currentMove == 0 &&
3891                           gameMode == IcsPlayingWhite &&
3892                           appData.premoveWhite) {
3893                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3894                         if (appData.debugMode)
3895                           fprintf(debugFP, "Sending premove:\n");
3896                         SendToICS(str);
3897                       } else if (currentMove == 1 &&
3898                                  gameMode == IcsPlayingBlack &&
3899                                  appData.premoveBlack) {
3900                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3901                         if (appData.debugMode)
3902                           fprintf(debugFP, "Sending premove:\n");
3903                         SendToICS(str);
3904                       } else if (gotPremove) {
3905                         gotPremove = 0;
3906                         ClearPremoveHighlights();
3907                         if (appData.debugMode)
3908                           fprintf(debugFP, "Sending premove:\n");
3909                           UserMoveEvent(premoveFromX, premoveFromY,
3910                                         premoveToX, premoveToY,
3911                                         premovePromoChar);
3912                       }
3913                     }
3914
3915                     /* Usually suppress following prompt */
3916                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3917                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3918                         if (looking_at(buf, &i, "*% ")) {
3919                             savingComment = FALSE;
3920                             suppressKibitz = 0;
3921                         }
3922                     }
3923                     next_out = i;
3924                 } else if (started == STARTED_HOLDINGS) {
3925                     int gamenum;
3926                     char new_piece[MSG_SIZ];
3927                     started = STARTED_NONE;
3928                     parse[parse_pos] = NULLCHAR;
3929                     if (appData.debugMode)
3930                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3931                                                         parse, currentMove);
3932                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3933                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3934                         if (gameInfo.variant == VariantNormal) {
3935                           /* [HGM] We seem to switch variant during a game!
3936                            * Presumably no holdings were displayed, so we have
3937                            * to move the position two files to the right to
3938                            * create room for them!
3939                            */
3940                           VariantClass newVariant;
3941                           switch(gameInfo.boardWidth) { // base guess on board width
3942                                 case 9:  newVariant = VariantShogi; break;
3943                                 case 10: newVariant = VariantGreat; break;
3944                                 default: newVariant = VariantCrazyhouse; break;
3945                           }
3946                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3947                           /* Get a move list just to see the header, which
3948                              will tell us whether this is really bug or zh */
3949                           if (ics_getting_history == H_FALSE) {
3950                             ics_getting_history = H_REQUESTED;
3951                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3952                             SendToICS(str);
3953                           }
3954                         }
3955                         new_piece[0] = NULLCHAR;
3956                         sscanf(parse, "game %d white [%s black [%s <- %s",
3957                                &gamenum, white_holding, black_holding,
3958                                new_piece);
3959                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3960                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3961                         /* [HGM] copy holdings to board holdings area */
3962                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3963                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3964                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3965 #if ZIPPY
3966                         if (appData.zippyPlay && first.initDone) {
3967                             ZippyHoldings(white_holding, black_holding,
3968                                           new_piece);
3969                         }
3970 #endif /*ZIPPY*/
3971                         if (tinyLayout || smallLayout) {
3972                             char wh[16], bh[16];
3973                             PackHolding(wh, white_holding);
3974                             PackHolding(bh, black_holding);
3975                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3976                                     gameInfo.white, gameInfo.black);
3977                         } else {
3978                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3979                                     gameInfo.white, white_holding,
3980                                     gameInfo.black, black_holding);
3981                         }
3982                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3983                         DrawPosition(FALSE, boards[currentMove]);
3984                         DisplayTitle(str);
3985                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3986                         sscanf(parse, "game %d white [%s black [%s <- %s",
3987                                &gamenum, white_holding, black_holding,
3988                                new_piece);
3989                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3990                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3991                         /* [HGM] copy holdings to partner-board holdings area */
3992                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3993                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3994                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3995                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3996                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3997                       }
3998                     }
3999                     /* Suppress following prompt */
4000                     if (looking_at(buf, &i, "*% ")) {
4001                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4002                         savingComment = FALSE;
4003                         suppressKibitz = 0;
4004                     }
4005                     next_out = i;
4006                 }
4007                 continue;
4008             }
4009
4010             i++;                /* skip unparsed character and loop back */
4011         }
4012
4013         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4014 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4015 //          SendToPlayer(&buf[next_out], i - next_out);
4016             started != STARTED_HOLDINGS && leftover_start > next_out) {
4017             SendToPlayer(&buf[next_out], leftover_start - next_out);
4018             next_out = i;
4019         }
4020
4021         leftover_len = buf_len - leftover_start;
4022         /* if buffer ends with something we couldn't parse,
4023            reparse it after appending the next read */
4024
4025     } else if (count == 0) {
4026         RemoveInputSource(isr);
4027         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4028     } else {
4029         DisplayFatalError(_("Error reading from ICS"), error, 1);
4030     }
4031 }
4032
4033
4034 /* Board style 12 looks like this:
4035
4036    <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
4037
4038  * The "<12> " is stripped before it gets to this routine.  The two
4039  * trailing 0's (flip state and clock ticking) are later addition, and
4040  * some chess servers may not have them, or may have only the first.
4041  * Additional trailing fields may be added in the future.
4042  */
4043
4044 #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"
4045
4046 #define RELATION_OBSERVING_PLAYED    0
4047 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4048 #define RELATION_PLAYING_MYMOVE      1
4049 #define RELATION_PLAYING_NOTMYMOVE  -1
4050 #define RELATION_EXAMINING           2
4051 #define RELATION_ISOLATED_BOARD     -3
4052 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4053
4054 void
4055 ParseBoard12(string)
4056      char *string;
4057 {
4058     GameMode newGameMode;
4059     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4060     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4061     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4062     char to_play, board_chars[200];
4063     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4064     char black[32], white[32];
4065     Board board;
4066     int prevMove = currentMove;
4067     int ticking = 2;
4068     ChessMove moveType;
4069     int fromX, fromY, toX, toY;
4070     char promoChar;
4071     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4072     char *bookHit = NULL; // [HGM] book
4073     Boolean weird = FALSE, reqFlag = FALSE;
4074
4075     fromX = fromY = toX = toY = -1;
4076
4077     newGame = FALSE;
4078
4079     if (appData.debugMode)
4080       fprintf(debugFP, _("Parsing board: %s\n"), string);
4081
4082     move_str[0] = NULLCHAR;
4083     elapsed_time[0] = NULLCHAR;
4084     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4085         int  i = 0, j;
4086         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4087             if(string[i] == ' ') { ranks++; files = 0; }
4088             else files++;
4089             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4090             i++;
4091         }
4092         for(j = 0; j <i; j++) board_chars[j] = string[j];
4093         board_chars[i] = '\0';
4094         string += i + 1;
4095     }
4096     n = sscanf(string, PATTERN, &to_play, &double_push,
4097                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4098                &gamenum, white, black, &relation, &basetime, &increment,
4099                &white_stren, &black_stren, &white_time, &black_time,
4100                &moveNum, str, elapsed_time, move_str, &ics_flip,
4101                &ticking);
4102
4103     if (n < 21) {
4104         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4105         DisplayError(str, 0);
4106         return;
4107     }
4108
4109     /* Convert the move number to internal form */
4110     moveNum = (moveNum - 1) * 2;
4111     if (to_play == 'B') moveNum++;
4112     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4113       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4114                         0, 1);
4115       return;
4116     }
4117
4118     switch (relation) {
4119       case RELATION_OBSERVING_PLAYED:
4120       case RELATION_OBSERVING_STATIC:
4121         if (gamenum == -1) {
4122             /* Old ICC buglet */
4123             relation = RELATION_OBSERVING_STATIC;
4124         }
4125         newGameMode = IcsObserving;
4126         break;
4127       case RELATION_PLAYING_MYMOVE:
4128       case RELATION_PLAYING_NOTMYMOVE:
4129         newGameMode =
4130           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4131             IcsPlayingWhite : IcsPlayingBlack;
4132         break;
4133       case RELATION_EXAMINING:
4134         newGameMode = IcsExamining;
4135         break;
4136       case RELATION_ISOLATED_BOARD:
4137       default:
4138         /* Just display this board.  If user was doing something else,
4139            we will forget about it until the next board comes. */
4140         newGameMode = IcsIdle;
4141         break;
4142       case RELATION_STARTING_POSITION:
4143         newGameMode = gameMode;
4144         break;
4145     }
4146
4147     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4148          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4149       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4150       char *toSqr;
4151       for (k = 0; k < ranks; k++) {
4152         for (j = 0; j < files; j++)
4153           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4154         if(gameInfo.holdingsWidth > 1) {
4155              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4156              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4157         }
4158       }
4159       CopyBoard(partnerBoard, board);
4160       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4161         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4162         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4163       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4164       if(toSqr = strchr(str, '-')) {
4165         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4166         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4167       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4168       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4169       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4170       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4171       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4172       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4173                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4174       DisplayMessage(partnerStatus, "");
4175         partnerBoardValid = TRUE;
4176       return;
4177     }
4178
4179     /* Modify behavior for initial board display on move listing
4180        of wild games.
4181        */
4182     switch (ics_getting_history) {
4183       case H_FALSE:
4184       case H_REQUESTED:
4185         break;
4186       case H_GOT_REQ_HEADER:
4187       case H_GOT_UNREQ_HEADER:
4188         /* This is the initial position of the current game */
4189         gamenum = ics_gamenum;
4190         moveNum = 0;            /* old ICS bug workaround */
4191         if (to_play == 'B') {
4192           startedFromSetupPosition = TRUE;
4193           blackPlaysFirst = TRUE;
4194           moveNum = 1;
4195           if (forwardMostMove == 0) forwardMostMove = 1;
4196           if (backwardMostMove == 0) backwardMostMove = 1;
4197           if (currentMove == 0) currentMove = 1;
4198         }
4199         newGameMode = gameMode;
4200         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4201         break;
4202       case H_GOT_UNWANTED_HEADER:
4203         /* This is an initial board that we don't want */
4204         return;
4205       case H_GETTING_MOVES:
4206         /* Should not happen */
4207         DisplayError(_("Error gathering move list: extra board"), 0);
4208         ics_getting_history = H_FALSE;
4209         return;
4210     }
4211
4212    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4213                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4214      /* [HGM] We seem to have switched variant unexpectedly
4215       * Try to guess new variant from board size
4216       */
4217           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4218           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4219           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4220           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4221           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4222           if(!weird) newVariant = VariantNormal;
4223           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4224           /* Get a move list just to see the header, which
4225              will tell us whether this is really bug or zh */
4226           if (ics_getting_history == H_FALSE) {
4227             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4228             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4229             SendToICS(str);
4230           }
4231     }
4232
4233     /* Take action if this is the first board of a new game, or of a
4234        different game than is currently being displayed.  */
4235     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4236         relation == RELATION_ISOLATED_BOARD) {
4237
4238         /* Forget the old game and get the history (if any) of the new one */
4239         if (gameMode != BeginningOfGame) {
4240           Reset(TRUE, TRUE);
4241         }
4242         newGame = TRUE;
4243         if (appData.autoRaiseBoard) BoardToTop();
4244         prevMove = -3;
4245         if (gamenum == -1) {
4246             newGameMode = IcsIdle;
4247         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4248                    appData.getMoveList && !reqFlag) {
4249             /* Need to get game history */
4250             ics_getting_history = H_REQUESTED;
4251             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4252             SendToICS(str);
4253         }
4254
4255         /* Initially flip the board to have black on the bottom if playing
4256            black or if the ICS flip flag is set, but let the user change
4257            it with the Flip View button. */
4258         flipView = appData.autoFlipView ?
4259           (newGameMode == IcsPlayingBlack) || ics_flip :
4260           appData.flipView;
4261
4262         /* Done with values from previous mode; copy in new ones */
4263         gameMode = newGameMode;
4264         ModeHighlight();
4265         ics_gamenum = gamenum;
4266         if (gamenum == gs_gamenum) {
4267             int klen = strlen(gs_kind);
4268             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4269             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4270             gameInfo.event = StrSave(str);
4271         } else {
4272             gameInfo.event = StrSave("ICS game");
4273         }
4274         gameInfo.site = StrSave(appData.icsHost);
4275         gameInfo.date = PGNDate();
4276         gameInfo.round = StrSave("-");
4277         gameInfo.white = StrSave(white);
4278         gameInfo.black = StrSave(black);
4279         timeControl = basetime * 60 * 1000;
4280         timeControl_2 = 0;
4281         timeIncrement = increment * 1000;
4282         movesPerSession = 0;
4283         gameInfo.timeControl = TimeControlTagValue();
4284         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4285   if (appData.debugMode) {
4286     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4287     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4288     setbuf(debugFP, NULL);
4289   }
4290
4291         gameInfo.outOfBook = NULL;
4292
4293         /* Do we have the ratings? */
4294         if (strcmp(player1Name, white) == 0 &&
4295             strcmp(player2Name, black) == 0) {
4296             if (appData.debugMode)
4297               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4298                       player1Rating, player2Rating);
4299             gameInfo.whiteRating = player1Rating;
4300             gameInfo.blackRating = player2Rating;
4301         } else if (strcmp(player2Name, white) == 0 &&
4302                    strcmp(player1Name, black) == 0) {
4303             if (appData.debugMode)
4304               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4305                       player2Rating, player1Rating);
4306             gameInfo.whiteRating = player2Rating;
4307             gameInfo.blackRating = player1Rating;
4308         }
4309         player1Name[0] = player2Name[0] = NULLCHAR;
4310
4311         /* Silence shouts if requested */
4312         if (appData.quietPlay &&
4313             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4314             SendToICS(ics_prefix);
4315             SendToICS("set shout 0\n");
4316         }
4317     }
4318
4319     /* Deal with midgame name changes */
4320     if (!newGame) {
4321         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4322             if (gameInfo.white) free(gameInfo.white);
4323             gameInfo.white = StrSave(white);
4324         }
4325         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4326             if (gameInfo.black) free(gameInfo.black);
4327             gameInfo.black = StrSave(black);
4328         }
4329     }
4330
4331     /* Throw away game result if anything actually changes in examine mode */
4332     if (gameMode == IcsExamining && !newGame) {
4333         gameInfo.result = GameUnfinished;
4334         if (gameInfo.resultDetails != NULL) {
4335             free(gameInfo.resultDetails);
4336             gameInfo.resultDetails = NULL;
4337         }
4338     }
4339
4340     /* In pausing && IcsExamining mode, we ignore boards coming
4341        in if they are in a different variation than we are. */
4342     if (pauseExamInvalid) return;
4343     if (pausing && gameMode == IcsExamining) {
4344         if (moveNum <= pauseExamForwardMostMove) {
4345             pauseExamInvalid = TRUE;
4346             forwardMostMove = pauseExamForwardMostMove;
4347             return;
4348         }
4349     }
4350
4351   if (appData.debugMode) {
4352     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4353   }
4354     /* Parse the board */
4355     for (k = 0; k < ranks; k++) {
4356       for (j = 0; j < files; j++)
4357         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4358       if(gameInfo.holdingsWidth > 1) {
4359            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4360            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4361       }
4362     }
4363     CopyBoard(boards[moveNum], board);
4364     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4365     if (moveNum == 0) {
4366         startedFromSetupPosition =
4367           !CompareBoards(board, initialPosition);
4368         if(startedFromSetupPosition)
4369             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4370     }
4371
4372     /* [HGM] Set castling rights. Take the outermost Rooks,
4373        to make it also work for FRC opening positions. Note that board12
4374        is really defective for later FRC positions, as it has no way to
4375        indicate which Rook can castle if they are on the same side of King.
4376        For the initial position we grant rights to the outermost Rooks,
4377        and remember thos rights, and we then copy them on positions
4378        later in an FRC game. This means WB might not recognize castlings with
4379        Rooks that have moved back to their original position as illegal,
4380        but in ICS mode that is not its job anyway.
4381     */
4382     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4383     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4384
4385         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4386             if(board[0][i] == WhiteRook) j = i;
4387         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4388         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4389             if(board[0][i] == WhiteRook) j = i;
4390         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4391         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4392             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4393         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4394         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4395             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4396         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4397
4398         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4399         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4400             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4401         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4402             if(board[BOARD_HEIGHT-1][k] == bKing)
4403                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4404         if(gameInfo.variant == VariantTwoKings) {
4405             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4406             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4407             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4408         }
4409     } else { int r;
4410         r = boards[moveNum][CASTLING][0] = initialRights[0];
4411         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4412         r = boards[moveNum][CASTLING][1] = initialRights[1];
4413         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4414         r = boards[moveNum][CASTLING][3] = initialRights[3];
4415         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4416         r = boards[moveNum][CASTLING][4] = initialRights[4];
4417         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4418         /* wildcastle kludge: always assume King has rights */
4419         r = boards[moveNum][CASTLING][2] = initialRights[2];
4420         r = boards[moveNum][CASTLING][5] = initialRights[5];
4421     }
4422     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4423     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4424
4425
4426     if (ics_getting_history == H_GOT_REQ_HEADER ||
4427         ics_getting_history == H_GOT_UNREQ_HEADER) {
4428         /* This was an initial position from a move list, not
4429            the current position */
4430         return;
4431     }
4432
4433     /* Update currentMove and known move number limits */
4434     newMove = newGame || moveNum > forwardMostMove;
4435
4436     if (newGame) {
4437         forwardMostMove = backwardMostMove = currentMove = moveNum;
4438         if (gameMode == IcsExamining && moveNum == 0) {
4439           /* Workaround for ICS limitation: we are not told the wild
4440              type when starting to examine a game.  But if we ask for
4441              the move list, the move list header will tell us */
4442             ics_getting_history = H_REQUESTED;
4443             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4444             SendToICS(str);
4445         }
4446     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4447                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4448 #if ZIPPY
4449         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4450         /* [HGM] applied this also to an engine that is silently watching        */
4451         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4452             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4453             gameInfo.variant == currentlyInitializedVariant) {
4454           takeback = forwardMostMove - moveNum;
4455           for (i = 0; i < takeback; i++) {
4456             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4457             SendToProgram("undo\n", &first);
4458           }
4459         }
4460 #endif
4461
4462         forwardMostMove = moveNum;
4463         if (!pausing || currentMove > forwardMostMove)
4464           currentMove = forwardMostMove;
4465     } else {
4466         /* New part of history that is not contiguous with old part */
4467         if (pausing && gameMode == IcsExamining) {
4468             pauseExamInvalid = TRUE;
4469             forwardMostMove = pauseExamForwardMostMove;
4470             return;
4471         }
4472         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4473 #if ZIPPY
4474             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4475                 // [HGM] when we will receive the move list we now request, it will be
4476                 // fed to the engine from the first move on. So if the engine is not
4477                 // in the initial position now, bring it there.
4478                 InitChessProgram(&first, 0);
4479             }
4480 #endif
4481             ics_getting_history = H_REQUESTED;
4482             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4483             SendToICS(str);
4484         }
4485         forwardMostMove = backwardMostMove = currentMove = moveNum;
4486     }
4487
4488     /* Update the clocks */
4489     if (strchr(elapsed_time, '.')) {
4490       /* Time is in ms */
4491       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4492       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4493     } else {
4494       /* Time is in seconds */
4495       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4496       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4497     }
4498
4499
4500 #if ZIPPY
4501     if (appData.zippyPlay && newGame &&
4502         gameMode != IcsObserving && gameMode != IcsIdle &&
4503         gameMode != IcsExamining)
4504       ZippyFirstBoard(moveNum, basetime, increment);
4505 #endif
4506
4507     /* Put the move on the move list, first converting
4508        to canonical algebraic form. */
4509     if (moveNum > 0) {
4510   if (appData.debugMode) {
4511     if (appData.debugMode) { int f = forwardMostMove;
4512         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4513                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4514                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4515     }
4516     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4517     fprintf(debugFP, "moveNum = %d\n", moveNum);
4518     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4519     setbuf(debugFP, NULL);
4520   }
4521         if (moveNum <= backwardMostMove) {
4522             /* We don't know what the board looked like before
4523                this move.  Punt. */
4524           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4525             strcat(parseList[moveNum - 1], " ");
4526             strcat(parseList[moveNum - 1], elapsed_time);
4527             moveList[moveNum - 1][0] = NULLCHAR;
4528         } else if (strcmp(move_str, "none") == 0) {
4529             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4530             /* Again, we don't know what the board looked like;
4531                this is really the start of the game. */
4532             parseList[moveNum - 1][0] = NULLCHAR;
4533             moveList[moveNum - 1][0] = NULLCHAR;
4534             backwardMostMove = moveNum;
4535             startedFromSetupPosition = TRUE;
4536             fromX = fromY = toX = toY = -1;
4537         } else {
4538           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4539           //                 So we parse the long-algebraic move string in stead of the SAN move
4540           int valid; char buf[MSG_SIZ], *prom;
4541
4542           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4543                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4544           // str looks something like "Q/a1-a2"; kill the slash
4545           if(str[1] == '/')
4546             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4547           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4548           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4549                 strcat(buf, prom); // long move lacks promo specification!
4550           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4551                 if(appData.debugMode)
4552                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4553                 safeStrCpy(move_str, buf, MSG_SIZ);
4554           }
4555           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4556                                 &fromX, &fromY, &toX, &toY, &promoChar)
4557                || ParseOneMove(buf, moveNum - 1, &moveType,
4558                                 &fromX, &fromY, &toX, &toY, &promoChar);
4559           // end of long SAN patch
4560           if (valid) {
4561             (void) CoordsToAlgebraic(boards[moveNum - 1],
4562                                      PosFlags(moveNum - 1),
4563                                      fromY, fromX, toY, toX, promoChar,
4564                                      parseList[moveNum-1]);
4565             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4566               case MT_NONE:
4567               case MT_STALEMATE:
4568               default:
4569                 break;
4570               case MT_CHECK:
4571                 if(gameInfo.variant != VariantShogi)
4572                     strcat(parseList[moveNum - 1], "+");
4573                 break;
4574               case MT_CHECKMATE:
4575               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4576                 strcat(parseList[moveNum - 1], "#");
4577                 break;
4578             }
4579             strcat(parseList[moveNum - 1], " ");
4580             strcat(parseList[moveNum - 1], elapsed_time);
4581             /* currentMoveString is set as a side-effect of ParseOneMove */
4582             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4583             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4584             strcat(moveList[moveNum - 1], "\n");
4585
4586             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4587                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4588               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4589                 ChessSquare old, new = boards[moveNum][k][j];
4590                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4591                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4592                   if(old == new) continue;
4593                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4594                   else if(new == WhiteWazir || new == BlackWazir) {
4595                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4596                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4597                       else boards[moveNum][k][j] = old; // preserve type of Gold
4598                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4599                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4600               }
4601           } else {
4602             /* Move from ICS was illegal!?  Punt. */
4603             if (appData.debugMode) {
4604               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4605               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4606             }
4607             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4608             strcat(parseList[moveNum - 1], " ");
4609             strcat(parseList[moveNum - 1], elapsed_time);
4610             moveList[moveNum - 1][0] = NULLCHAR;
4611             fromX = fromY = toX = toY = -1;
4612           }
4613         }
4614   if (appData.debugMode) {
4615     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4616     setbuf(debugFP, NULL);
4617   }
4618
4619 #if ZIPPY
4620         /* Send move to chess program (BEFORE animating it). */
4621         if (appData.zippyPlay && !newGame && newMove &&
4622            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4623
4624             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4625                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4626                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4627                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4628                             move_str);
4629                     DisplayError(str, 0);
4630                 } else {
4631                     if (first.sendTime) {
4632                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4633                     }
4634                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4635                     if (firstMove && !bookHit) {
4636                         firstMove = FALSE;
4637                         if (first.useColors) {
4638                           SendToProgram(gameMode == IcsPlayingWhite ?
4639                                         "white\ngo\n" :
4640                                         "black\ngo\n", &first);
4641                         } else {
4642                           SendToProgram("go\n", &first);
4643                         }
4644                         first.maybeThinking = TRUE;
4645                     }
4646                 }
4647             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4648               if (moveList[moveNum - 1][0] == NULLCHAR) {
4649                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4650                 DisplayError(str, 0);
4651               } else {
4652                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4653                 SendMoveToProgram(moveNum - 1, &first);
4654               }
4655             }
4656         }
4657 #endif
4658     }
4659
4660     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4661         /* If move comes from a remote source, animate it.  If it
4662            isn't remote, it will have already been animated. */
4663         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4664             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4665         }
4666         if (!pausing && appData.highlightLastMove) {
4667             SetHighlights(fromX, fromY, toX, toY);
4668         }
4669     }
4670
4671     /* Start the clocks */
4672     whiteFlag = blackFlag = FALSE;
4673     appData.clockMode = !(basetime == 0 && increment == 0);
4674     if (ticking == 0) {
4675       ics_clock_paused = TRUE;
4676       StopClocks();
4677     } else if (ticking == 1) {
4678       ics_clock_paused = FALSE;
4679     }
4680     if (gameMode == IcsIdle ||
4681         relation == RELATION_OBSERVING_STATIC ||
4682         relation == RELATION_EXAMINING ||
4683         ics_clock_paused)
4684       DisplayBothClocks();
4685     else
4686       StartClocks();
4687
4688     /* Display opponents and material strengths */
4689     if (gameInfo.variant != VariantBughouse &&
4690         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4691         if (tinyLayout || smallLayout) {
4692             if(gameInfo.variant == VariantNormal)
4693               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4694                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4695                     basetime, increment);
4696             else
4697               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4698                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4699                     basetime, increment, (int) gameInfo.variant);
4700         } else {
4701             if(gameInfo.variant == VariantNormal)
4702               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4703                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4704                     basetime, increment);
4705             else
4706               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4707                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4708                     basetime, increment, VariantName(gameInfo.variant));
4709         }
4710         DisplayTitle(str);
4711   if (appData.debugMode) {
4712     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4713   }
4714     }
4715
4716
4717     /* Display the board */
4718     if (!pausing && !appData.noGUI) {
4719
4720       if (appData.premove)
4721           if (!gotPremove ||
4722              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4723              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4724               ClearPremoveHighlights();
4725
4726       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4727         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4728       DrawPosition(j, boards[currentMove]);
4729
4730       DisplayMove(moveNum - 1);
4731       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4732             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4733               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4734         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4735       }
4736     }
4737
4738     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4739 #if ZIPPY
4740     if(bookHit) { // [HGM] book: simulate book reply
4741         static char bookMove[MSG_SIZ]; // a bit generous?
4742
4743         programStats.nodes = programStats.depth = programStats.time =
4744         programStats.score = programStats.got_only_move = 0;
4745         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4746
4747         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4748         strcat(bookMove, bookHit);
4749         HandleMachineMove(bookMove, &first);
4750     }
4751 #endif
4752 }
4753
4754 void
4755 GetMoveListEvent()
4756 {
4757     char buf[MSG_SIZ];
4758     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4759         ics_getting_history = H_REQUESTED;
4760         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4761         SendToICS(buf);
4762     }
4763 }
4764
4765 void
4766 AnalysisPeriodicEvent(force)
4767      int force;
4768 {
4769     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4770          && !force) || !appData.periodicUpdates)
4771       return;
4772
4773     /* Send . command to Crafty to collect stats */
4774     SendToProgram(".\n", &first);
4775
4776     /* Don't send another until we get a response (this makes
4777        us stop sending to old Crafty's which don't understand
4778        the "." command (sending illegal cmds resets node count & time,
4779        which looks bad)) */
4780     programStats.ok_to_send = 0;
4781 }
4782
4783 void ics_update_width(new_width)
4784         int new_width;
4785 {
4786         ics_printf("set width %d\n", new_width);
4787 }
4788
4789 void
4790 SendMoveToProgram(moveNum, cps)
4791      int moveNum;
4792      ChessProgramState *cps;
4793 {
4794     char buf[MSG_SIZ];
4795
4796     if (cps->useUsermove) {
4797       SendToProgram("usermove ", cps);
4798     }
4799     if (cps->useSAN) {
4800       char *space;
4801       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4802         int len = space - parseList[moveNum];
4803         memcpy(buf, parseList[moveNum], len);
4804         buf[len++] = '\n';
4805         buf[len] = NULLCHAR;
4806       } else {
4807         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4808       }
4809       SendToProgram(buf, cps);
4810     } else {
4811       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4812         AlphaRank(moveList[moveNum], 4);
4813         SendToProgram(moveList[moveNum], cps);
4814         AlphaRank(moveList[moveNum], 4); // and back
4815       } else
4816       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4817        * the engine. It would be nice to have a better way to identify castle
4818        * moves here. */
4819       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4820                                                                          && cps->useOOCastle) {
4821         int fromX = moveList[moveNum][0] - AAA;
4822         int fromY = moveList[moveNum][1] - ONE;
4823         int toX = moveList[moveNum][2] - AAA;
4824         int toY = moveList[moveNum][3] - ONE;
4825         if((boards[moveNum][fromY][fromX] == WhiteKing
4826             && boards[moveNum][toY][toX] == WhiteRook)
4827            || (boards[moveNum][fromY][fromX] == BlackKing
4828                && boards[moveNum][toY][toX] == BlackRook)) {
4829           if(toX > fromX) SendToProgram("O-O\n", cps);
4830           else SendToProgram("O-O-O\n", cps);
4831         }
4832         else SendToProgram(moveList[moveNum], cps);
4833       }
4834       else SendToProgram(moveList[moveNum], cps);
4835       /* End of additions by Tord */
4836     }
4837
4838     /* [HGM] setting up the opening has brought engine in force mode! */
4839     /*       Send 'go' if we are in a mode where machine should play. */
4840     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4841         (gameMode == TwoMachinesPlay   ||
4842 #if ZIPPY
4843          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4844 #endif
4845          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4846         SendToProgram("go\n", cps);
4847   if (appData.debugMode) {
4848     fprintf(debugFP, "(extra)\n");
4849   }
4850     }
4851     setboardSpoiledMachineBlack = 0;
4852 }
4853
4854 void
4855 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4856      ChessMove moveType;
4857      int fromX, fromY, toX, toY;
4858      char promoChar;
4859 {
4860     char user_move[MSG_SIZ];
4861
4862     switch (moveType) {
4863       default:
4864         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4865                 (int)moveType, fromX, fromY, toX, toY);
4866         DisplayError(user_move + strlen("say "), 0);
4867         break;
4868       case WhiteKingSideCastle:
4869       case BlackKingSideCastle:
4870       case WhiteQueenSideCastleWild:
4871       case BlackQueenSideCastleWild:
4872       /* PUSH Fabien */
4873       case WhiteHSideCastleFR:
4874       case BlackHSideCastleFR:
4875       /* POP Fabien */
4876         snprintf(user_move, MSG_SIZ, "o-o\n");
4877         break;
4878       case WhiteQueenSideCastle:
4879       case BlackQueenSideCastle:
4880       case WhiteKingSideCastleWild:
4881       case BlackKingSideCastleWild:
4882       /* PUSH Fabien */
4883       case WhiteASideCastleFR:
4884       case BlackASideCastleFR:
4885       /* POP Fabien */
4886         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4887         break;
4888       case WhiteNonPromotion:
4889       case BlackNonPromotion:
4890         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4891         break;
4892       case WhitePromotion:
4893       case BlackPromotion:
4894         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4895           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4896                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4897                 PieceToChar(WhiteFerz));
4898         else if(gameInfo.variant == VariantGreat)
4899           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4900                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4901                 PieceToChar(WhiteMan));
4902         else
4903           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4904                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4905                 promoChar);
4906         break;
4907       case WhiteDrop:
4908       case BlackDrop:
4909       drop:
4910         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4911                  ToUpper(PieceToChar((ChessSquare) fromX)),
4912                  AAA + toX, ONE + toY);
4913         break;
4914       case IllegalMove:  /* could be a variant we don't quite understand */
4915         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4916       case NormalMove:
4917       case WhiteCapturesEnPassant:
4918       case BlackCapturesEnPassant:
4919         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4920                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4921         break;
4922     }
4923     SendToICS(user_move);
4924     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4925         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4926 }
4927
4928 void
4929 UploadGameEvent()
4930 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4931     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4932     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4933     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4934         DisplayError("You cannot do this while you are playing or observing", 0);
4935         return;
4936     }
4937     if(gameMode != IcsExamining) { // is this ever not the case?
4938         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4939
4940         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4941           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4942         } else { // on FICS we must first go to general examine mode
4943           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4944         }
4945         if(gameInfo.variant != VariantNormal) {
4946             // try figure out wild number, as xboard names are not always valid on ICS
4947             for(i=1; i<=36; i++) {
4948               snprintf(buf, MSG_SIZ, "wild/%d", i);
4949                 if(StringToVariant(buf) == gameInfo.variant) break;
4950             }
4951             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4952             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4953             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4954         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4955         SendToICS(ics_prefix);
4956         SendToICS(buf);
4957         if(startedFromSetupPosition || backwardMostMove != 0) {
4958           fen = PositionToFEN(backwardMostMove, NULL);
4959           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4960             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4961             SendToICS(buf);
4962           } else { // FICS: everything has to set by separate bsetup commands
4963             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4964             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4965             SendToICS(buf);
4966             if(!WhiteOnMove(backwardMostMove)) {
4967                 SendToICS("bsetup tomove black\n");
4968             }
4969             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4970             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4971             SendToICS(buf);
4972             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4973             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4974             SendToICS(buf);
4975             i = boards[backwardMostMove][EP_STATUS];
4976             if(i >= 0) { // set e.p.
4977               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4978                 SendToICS(buf);
4979             }
4980             bsetup++;
4981           }
4982         }
4983       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4984             SendToICS("bsetup done\n"); // switch to normal examining.
4985     }
4986     for(i = backwardMostMove; i<last; i++) {
4987         char buf[20];
4988         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4989         SendToICS(buf);
4990     }
4991     SendToICS(ics_prefix);
4992     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4993 }
4994
4995 void
4996 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4997      int rf, ff, rt, ft;
4998      char promoChar;
4999      char move[7];
5000 {
5001     if (rf == DROP_RANK) {
5002       sprintf(move, "%c@%c%c\n",
5003                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5004     } else {
5005         if (promoChar == 'x' || promoChar == NULLCHAR) {
5006           sprintf(move, "%c%c%c%c\n",
5007                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5008         } else {
5009             sprintf(move, "%c%c%c%c%c\n",
5010                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5011         }
5012     }
5013 }
5014
5015 void
5016 ProcessICSInitScript(f)
5017      FILE *f;
5018 {
5019     char buf[MSG_SIZ];
5020
5021     while (fgets(buf, MSG_SIZ, f)) {
5022         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5023     }
5024
5025     fclose(f);
5026 }
5027
5028
5029 static int lastX, lastY, selectFlag, dragging;
5030
5031 void
5032 Sweep(int step)
5033 {
5034     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5035     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5036     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5037     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5038     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5039     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5040     do {
5041         promoSweep -= step;
5042         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5043         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5044         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5045         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5046         if(!step) step = 1;
5047     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5048             appData.testLegality && (promoSweep == king ||
5049             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5050     ChangeDragPiece(promoSweep);
5051 }
5052
5053 int PromoScroll(int x, int y)
5054 {
5055   int step = 0;
5056
5057   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5058   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5059   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5060   if(!step) return FALSE;
5061   lastX = x; lastY = y;
5062   if((promoSweep < BlackPawn) == flipView) step = -step;
5063   if(step > 0) selectFlag = 1;
5064   if(!selectFlag) Sweep(step);
5065   return FALSE;
5066 }
5067
5068 void
5069 NextPiece(int step)
5070 {
5071     ChessSquare piece = boards[currentMove][toY][toX];
5072     do {
5073         pieceSweep -= step;
5074         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5075         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5076         if(!step) step = -1;
5077     } while(PieceToChar(pieceSweep) == '.');
5078     boards[currentMove][toY][toX] = pieceSweep;
5079     DrawPosition(FALSE, boards[currentMove]);
5080     boards[currentMove][toY][toX] = piece;
5081 }
5082 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5083 void
5084 AlphaRank(char *move, int n)
5085 {
5086 //    char *p = move, c; int x, y;
5087
5088     if (appData.debugMode) {
5089         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5090     }
5091
5092     if(move[1]=='*' &&
5093        move[2]>='0' && move[2]<='9' &&
5094        move[3]>='a' && move[3]<='x'    ) {
5095         move[1] = '@';
5096         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5097         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5098     } else
5099     if(move[0]>='0' && move[0]<='9' &&
5100        move[1]>='a' && move[1]<='x' &&
5101        move[2]>='0' && move[2]<='9' &&
5102        move[3]>='a' && move[3]<='x'    ) {
5103         /* input move, Shogi -> normal */
5104         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5105         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5106         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5107         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5108     } else
5109     if(move[1]=='@' &&
5110        move[3]>='0' && move[3]<='9' &&
5111        move[2]>='a' && move[2]<='x'    ) {
5112         move[1] = '*';
5113         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5114         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5115     } else
5116     if(
5117        move[0]>='a' && move[0]<='x' &&
5118        move[3]>='0' && move[3]<='9' &&
5119        move[2]>='a' && move[2]<='x'    ) {
5120          /* output move, normal -> Shogi */
5121         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5122         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5123         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5124         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5125         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5126     }
5127     if (appData.debugMode) {
5128         fprintf(debugFP, "   out = '%s'\n", move);
5129     }
5130 }
5131
5132 char yy_textstr[8000];
5133
5134 /* Parser for moves from gnuchess, ICS, or user typein box */
5135 Boolean
5136 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5137      char *move;
5138      int moveNum;
5139      ChessMove *moveType;
5140      int *fromX, *fromY, *toX, *toY;
5141      char *promoChar;
5142 {
5143     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5144
5145     switch (*moveType) {
5146       case WhitePromotion:
5147       case BlackPromotion:
5148       case WhiteNonPromotion:
5149       case BlackNonPromotion:
5150       case NormalMove:
5151       case WhiteCapturesEnPassant:
5152       case BlackCapturesEnPassant:
5153       case WhiteKingSideCastle:
5154       case WhiteQueenSideCastle:
5155       case BlackKingSideCastle:
5156       case BlackQueenSideCastle:
5157       case WhiteKingSideCastleWild:
5158       case WhiteQueenSideCastleWild:
5159       case BlackKingSideCastleWild:
5160       case BlackQueenSideCastleWild:
5161       /* Code added by Tord: */
5162       case WhiteHSideCastleFR:
5163       case WhiteASideCastleFR:
5164       case BlackHSideCastleFR:
5165       case BlackASideCastleFR:
5166       /* End of code added by Tord */
5167       case IllegalMove:         /* bug or odd chess variant */
5168         *fromX = currentMoveString[0] - AAA;
5169         *fromY = currentMoveString[1] - ONE;
5170         *toX = currentMoveString[2] - AAA;
5171         *toY = currentMoveString[3] - ONE;
5172         *promoChar = currentMoveString[4];
5173         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5174             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5175     if (appData.debugMode) {
5176         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5177     }
5178             *fromX = *fromY = *toX = *toY = 0;
5179             return FALSE;
5180         }
5181         if (appData.testLegality) {
5182           return (*moveType != IllegalMove);
5183         } else {
5184           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5185                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5186         }
5187
5188       case WhiteDrop:
5189       case BlackDrop:
5190         *fromX = *moveType == WhiteDrop ?
5191           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5192           (int) CharToPiece(ToLower(currentMoveString[0]));
5193         *fromY = DROP_RANK;
5194         *toX = currentMoveString[2] - AAA;
5195         *toY = currentMoveString[3] - ONE;
5196         *promoChar = NULLCHAR;
5197         return TRUE;
5198
5199       case AmbiguousMove:
5200       case ImpossibleMove:
5201       case EndOfFile:
5202       case ElapsedTime:
5203       case Comment:
5204       case PGNTag:
5205       case NAG:
5206       case WhiteWins:
5207       case BlackWins:
5208       case GameIsDrawn:
5209       default:
5210     if (appData.debugMode) {
5211         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5212     }
5213         /* bug? */
5214         *fromX = *fromY = *toX = *toY = 0;
5215         *promoChar = NULLCHAR;
5216         return FALSE;
5217     }
5218 }
5219
5220
5221 void
5222 ParsePV(char *pv, Boolean storeComments)
5223 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5224   int fromX, fromY, toX, toY; char promoChar;
5225   ChessMove moveType;
5226   Boolean valid;
5227   int nr = 0;
5228
5229   endPV = forwardMostMove;
5230   do {
5231     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5232     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5233     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5234 if(appData.debugMode){
5235 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);
5236 }
5237     if(!valid && nr == 0 &&
5238        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5239         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5240         // Hande case where played move is different from leading PV move
5241         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5242         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5243         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5244         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5245           endPV += 2; // if position different, keep this
5246           moveList[endPV-1][0] = fromX + AAA;
5247           moveList[endPV-1][1] = fromY + ONE;
5248           moveList[endPV-1][2] = toX + AAA;
5249           moveList[endPV-1][3] = toY + ONE;
5250           parseList[endPV-1][0] = NULLCHAR;
5251           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5252         }
5253       }
5254     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5255     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5256     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5257     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5258         valid++; // allow comments in PV
5259         continue;
5260     }
5261     nr++;
5262     if(endPV+1 > framePtr) break; // no space, truncate
5263     if(!valid) break;
5264     endPV++;
5265     CopyBoard(boards[endPV], boards[endPV-1]);
5266     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5267     moveList[endPV-1][0] = fromX + AAA;
5268     moveList[endPV-1][1] = fromY + ONE;
5269     moveList[endPV-1][2] = toX + AAA;
5270     moveList[endPV-1][3] = toY + ONE;
5271     moveList[endPV-1][4] = promoChar;
5272     moveList[endPV-1][5] = NULLCHAR;
5273     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5274     if(storeComments)
5275         CoordsToAlgebraic(boards[endPV - 1],
5276                              PosFlags(endPV - 1),
5277                              fromY, fromX, toY, toX, promoChar,
5278                              parseList[endPV - 1]);
5279     else
5280         parseList[endPV-1][0] = NULLCHAR;
5281   } while(valid);
5282   currentMove = endPV;
5283   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5284   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5285                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5286   DrawPosition(TRUE, boards[currentMove]);
5287 }
5288
5289 Boolean
5290 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5291 {
5292         int startPV;
5293         char *p;
5294
5295         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5296         lastX = x; lastY = y;
5297         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5298         startPV = index;
5299         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5300         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5301         index = startPV;
5302         do{ while(buf[index] && buf[index] != '\n') index++;
5303         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5304         buf[index] = 0;
5305         ParsePV(buf+startPV, FALSE);
5306         *start = startPV; *end = index-1;
5307         return TRUE;
5308 }
5309
5310 Boolean
5311 LoadPV(int x, int y)
5312 { // called on right mouse click to load PV
5313   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5314   lastX = x; lastY = y;
5315   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5316   return TRUE;
5317 }
5318
5319 void
5320 UnLoadPV()
5321 {
5322   if(endPV < 0) return;
5323   endPV = -1;
5324   currentMove = forwardMostMove;
5325   ClearPremoveHighlights();
5326   DrawPosition(TRUE, boards[currentMove]);
5327 }
5328
5329 void
5330 MovePV(int x, int y, int h)
5331 { // step through PV based on mouse coordinates (called on mouse move)
5332   int margin = h>>3, step = 0;
5333
5334   // we must somehow check if right button is still down (might be released off board!)
5335   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5336   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5337   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5338   if(!step) return;
5339   lastX = x; lastY = y;
5340
5341   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5342   if(endPV < 0) return;
5343   if(y < margin) step = 1; else
5344   if(y > h - margin) step = -1;
5345   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5346   currentMove += step;
5347   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5348   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5349                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5350   DrawPosition(FALSE, boards[currentMove]);
5351 }
5352
5353
5354 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5355 // All positions will have equal probability, but the current method will not provide a unique
5356 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5357 #define DARK 1
5358 #define LITE 2
5359 #define ANY 3
5360
5361 int squaresLeft[4];
5362 int piecesLeft[(int)BlackPawn];
5363 int seed, nrOfShuffles;
5364
5365 void GetPositionNumber()
5366 {       // sets global variable seed
5367         int i;
5368
5369         seed = appData.defaultFrcPosition;
5370         if(seed < 0) { // randomize based on time for negative FRC position numbers
5371                 for(i=0; i<50; i++) seed += random();
5372                 seed = random() ^ random() >> 8 ^ random() << 8;
5373                 if(seed<0) seed = -seed;
5374         }
5375 }
5376
5377 int put(Board board, int pieceType, int rank, int n, int shade)
5378 // put the piece on the (n-1)-th empty squares of the given shade
5379 {
5380         int i;
5381
5382         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5383                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5384                         board[rank][i] = (ChessSquare) pieceType;
5385                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5386                         squaresLeft[ANY]--;
5387                         piecesLeft[pieceType]--;
5388                         return i;
5389                 }
5390         }
5391         return -1;
5392 }
5393
5394
5395 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5396 // calculate where the next piece goes, (any empty square), and put it there
5397 {
5398         int i;
5399
5400         i = seed % squaresLeft[shade];
5401         nrOfShuffles *= squaresLeft[shade];
5402         seed /= squaresLeft[shade];
5403         put(board, pieceType, rank, i, shade);
5404 }
5405
5406 void AddTwoPieces(Board board, int pieceType, int rank)
5407 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5408 {
5409         int i, n=squaresLeft[ANY], j=n-1, k;
5410
5411         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5412         i = seed % k;  // pick one
5413         nrOfShuffles *= k;
5414         seed /= k;
5415         while(i >= j) i -= j--;
5416         j = n - 1 - j; i += j;
5417         put(board, pieceType, rank, j, ANY);
5418         put(board, pieceType, rank, i, ANY);
5419 }
5420
5421 void SetUpShuffle(Board board, int number)
5422 {
5423         int i, p, first=1;
5424
5425         GetPositionNumber(); nrOfShuffles = 1;
5426
5427         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5428         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5429         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5430
5431         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5432
5433         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5434             p = (int) board[0][i];
5435             if(p < (int) BlackPawn) piecesLeft[p] ++;
5436             board[0][i] = EmptySquare;
5437         }
5438
5439         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5440             // shuffles restricted to allow normal castling put KRR first
5441             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5442                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5443             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5444                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5445             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5446                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5447             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5448                 put(board, WhiteRook, 0, 0, ANY);
5449             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5450         }
5451
5452         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5453             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5454             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5455                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5456                 while(piecesLeft[p] >= 2) {
5457                     AddOnePiece(board, p, 0, LITE);
5458                     AddOnePiece(board, p, 0, DARK);
5459                 }
5460                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5461             }
5462
5463         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5464             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5465             // but we leave King and Rooks for last, to possibly obey FRC restriction
5466             if(p == (int)WhiteRook) continue;
5467             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5468             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5469         }
5470
5471         // now everything is placed, except perhaps King (Unicorn) and Rooks
5472
5473         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5474             // Last King gets castling rights
5475             while(piecesLeft[(int)WhiteUnicorn]) {
5476                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5477                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5478             }
5479
5480             while(piecesLeft[(int)WhiteKing]) {
5481                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5482                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5483             }
5484
5485
5486         } else {
5487             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5488             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5489         }
5490
5491         // Only Rooks can be left; simply place them all
5492         while(piecesLeft[(int)WhiteRook]) {
5493                 i = put(board, WhiteRook, 0, 0, ANY);
5494                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5495                         if(first) {
5496                                 first=0;
5497                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5498                         }
5499                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5500                 }
5501         }
5502         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5503             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5504         }
5505
5506         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5507 }
5508
5509 int SetCharTable( char *table, const char * map )
5510 /* [HGM] moved here from winboard.c because of its general usefulness */
5511 /*       Basically a safe strcpy that uses the last character as King */
5512 {
5513     int result = FALSE; int NrPieces;
5514
5515     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5516                     && NrPieces >= 12 && !(NrPieces&1)) {
5517         int i; /* [HGM] Accept even length from 12 to 34 */
5518
5519         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5520         for( i=0; i<NrPieces/2-1; i++ ) {
5521             table[i] = map[i];
5522             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5523         }
5524         table[(int) WhiteKing]  = map[NrPieces/2-1];
5525         table[(int) BlackKing]  = map[NrPieces-1];
5526
5527         result = TRUE;
5528     }
5529
5530     return result;
5531 }
5532
5533 void Prelude(Board board)
5534 {       // [HGM] superchess: random selection of exo-pieces
5535         int i, j, k; ChessSquare p;
5536         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5537
5538         GetPositionNumber(); // use FRC position number
5539
5540         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5541             SetCharTable(pieceToChar, appData.pieceToCharTable);
5542             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5543                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5544         }
5545
5546         j = seed%4;                 seed /= 4;
5547         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5548         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5549         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5550         j = seed%3 + (seed%3 >= j); seed /= 3;
5551         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5552         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5553         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5554         j = seed%3;                 seed /= 3;
5555         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5556         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5557         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5558         j = seed%2 + (seed%2 >= j); seed /= 2;
5559         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5560         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5561         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5562         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5563         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5564         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5565         put(board, exoPieces[0],    0, 0, ANY);
5566         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5567 }
5568
5569 void
5570 InitPosition(redraw)
5571      int redraw;
5572 {
5573     ChessSquare (* pieces)[BOARD_FILES];
5574     int i, j, pawnRow, overrule,
5575     oldx = gameInfo.boardWidth,
5576     oldy = gameInfo.boardHeight,
5577     oldh = gameInfo.holdingsWidth;
5578     static int oldv;
5579
5580     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5581
5582     /* [AS] Initialize pv info list [HGM] and game status */
5583     {
5584         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5585             pvInfoList[i].depth = 0;
5586             boards[i][EP_STATUS] = EP_NONE;
5587             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5588         }
5589
5590         initialRulePlies = 0; /* 50-move counter start */
5591
5592         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5593         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5594     }
5595
5596
5597     /* [HGM] logic here is completely changed. In stead of full positions */
5598     /* the initialized data only consist of the two backranks. The switch */
5599     /* selects which one we will use, which is than copied to the Board   */
5600     /* initialPosition, which for the rest is initialized by Pawns and    */
5601     /* empty squares. This initial position is then copied to boards[0],  */
5602     /* possibly after shuffling, so that it remains available.            */
5603
5604     gameInfo.holdingsWidth = 0; /* default board sizes */
5605     gameInfo.boardWidth    = 8;
5606     gameInfo.boardHeight   = 8;
5607     gameInfo.holdingsSize  = 0;
5608     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5609     for(i=0; i<BOARD_FILES-2; i++)
5610       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5611     initialPosition[EP_STATUS] = EP_NONE;
5612     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5613     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5614          SetCharTable(pieceNickName, appData.pieceNickNames);
5615     else SetCharTable(pieceNickName, "............");
5616     pieces = FIDEArray;
5617
5618     switch (gameInfo.variant) {
5619     case VariantFischeRandom:
5620       shuffleOpenings = TRUE;
5621     default:
5622       break;
5623     case VariantShatranj:
5624       pieces = ShatranjArray;
5625       nrCastlingRights = 0;
5626       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5627       break;
5628     case VariantMakruk:
5629       pieces = makrukArray;
5630       nrCastlingRights = 0;
5631       startedFromSetupPosition = TRUE;
5632       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5633       break;
5634     case VariantTwoKings:
5635       pieces = twoKingsArray;
5636       break;
5637     case VariantCapaRandom:
5638       shuffleOpenings = TRUE;
5639     case VariantCapablanca:
5640       pieces = CapablancaArray;
5641       gameInfo.boardWidth = 10;
5642       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5643       break;
5644     case VariantGothic:
5645       pieces = GothicArray;
5646       gameInfo.boardWidth = 10;
5647       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5648       break;
5649     case VariantSChess:
5650       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5651       gameInfo.holdingsSize = 7;
5652       break;
5653     case VariantJanus:
5654       pieces = JanusArray;
5655       gameInfo.boardWidth = 10;
5656       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5657       nrCastlingRights = 6;
5658         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5659         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5660         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5661         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5662         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5663         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5664       break;
5665     case VariantFalcon:
5666       pieces = FalconArray;
5667       gameInfo.boardWidth = 10;
5668       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5669       break;
5670     case VariantXiangqi:
5671       pieces = XiangqiArray;
5672       gameInfo.boardWidth  = 9;
5673       gameInfo.boardHeight = 10;
5674       nrCastlingRights = 0;
5675       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5676       break;
5677     case VariantShogi:
5678       pieces = ShogiArray;
5679       gameInfo.boardWidth  = 9;
5680       gameInfo.boardHeight = 9;
5681       gameInfo.holdingsSize = 7;
5682       nrCastlingRights = 0;
5683       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5684       break;
5685     case VariantCourier:
5686       pieces = CourierArray;
5687       gameInfo.boardWidth  = 12;
5688       nrCastlingRights = 0;
5689       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5690       break;
5691     case VariantKnightmate:
5692       pieces = KnightmateArray;
5693       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5694       break;
5695     case VariantSpartan:
5696       pieces = SpartanArray;
5697       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5698       break;
5699     case VariantFairy:
5700       pieces = fairyArray;
5701       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5702       break;
5703     case VariantGreat:
5704       pieces = GreatArray;
5705       gameInfo.boardWidth = 10;
5706       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5707       gameInfo.holdingsSize = 8;
5708       break;
5709     case VariantSuper:
5710       pieces = FIDEArray;
5711       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5712       gameInfo.holdingsSize = 8;
5713       startedFromSetupPosition = TRUE;
5714       break;
5715     case VariantCrazyhouse:
5716     case VariantBughouse:
5717       pieces = FIDEArray;
5718       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5719       gameInfo.holdingsSize = 5;
5720       break;
5721     case VariantWildCastle:
5722       pieces = FIDEArray;
5723       /* !!?shuffle with kings guaranteed to be on d or e file */
5724       shuffleOpenings = 1;
5725       break;
5726     case VariantNoCastle:
5727       pieces = FIDEArray;
5728       nrCastlingRights = 0;
5729       /* !!?unconstrained back-rank shuffle */
5730       shuffleOpenings = 1;
5731       break;
5732     }
5733
5734     overrule = 0;
5735     if(appData.NrFiles >= 0) {
5736         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5737         gameInfo.boardWidth = appData.NrFiles;
5738     }
5739     if(appData.NrRanks >= 0) {
5740         gameInfo.boardHeight = appData.NrRanks;
5741     }
5742     if(appData.holdingsSize >= 0) {
5743         i = appData.holdingsSize;
5744         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5745         gameInfo.holdingsSize = i;
5746     }
5747     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5748     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5749         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5750
5751     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5752     if(pawnRow < 1) pawnRow = 1;
5753     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5754
5755     /* User pieceToChar list overrules defaults */
5756     if(appData.pieceToCharTable != NULL)
5757         SetCharTable(pieceToChar, appData.pieceToCharTable);
5758
5759     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5760
5761         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5762             s = (ChessSquare) 0; /* account holding counts in guard band */
5763         for( i=0; i<BOARD_HEIGHT; i++ )
5764             initialPosition[i][j] = s;
5765
5766         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5767         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5768         initialPosition[pawnRow][j] = WhitePawn;
5769         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5770         if(gameInfo.variant == VariantXiangqi) {
5771             if(j&1) {
5772                 initialPosition[pawnRow][j] =
5773                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5774                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5775                    initialPosition[2][j] = WhiteCannon;
5776                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5777                 }
5778             }
5779         }
5780         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5781     }
5782     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5783
5784             j=BOARD_LEFT+1;
5785             initialPosition[1][j] = WhiteBishop;
5786             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5787             j=BOARD_RGHT-2;
5788             initialPosition[1][j] = WhiteRook;
5789             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5790     }
5791
5792     if( nrCastlingRights == -1) {
5793         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5794         /*       This sets default castling rights from none to normal corners   */
5795         /* Variants with other castling rights must set them themselves above    */
5796         nrCastlingRights = 6;
5797
5798         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5799         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5800         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5801         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5802         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5803         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5804      }
5805
5806      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5807      if(gameInfo.variant == VariantGreat) { // promotion commoners
5808         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5809         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5810         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5811         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5812      }
5813      if( gameInfo.variant == VariantSChess ) {
5814       initialPosition[1][0] = BlackMarshall;
5815       initialPosition[2][0] = BlackAngel;
5816       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5817       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5818       initialPosition[1][1] = initialPosition[2][1] = 
5819       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5820      }
5821   if (appData.debugMode) {
5822     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5823   }
5824     if(shuffleOpenings) {
5825         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5826         startedFromSetupPosition = TRUE;
5827     }
5828     if(startedFromPositionFile) {
5829       /* [HGM] loadPos: use PositionFile for every new game */
5830       CopyBoard(initialPosition, filePosition);
5831       for(i=0; i<nrCastlingRights; i++)
5832           initialRights[i] = filePosition[CASTLING][i];
5833       startedFromSetupPosition = TRUE;
5834     }
5835
5836     CopyBoard(boards[0], initialPosition);
5837
5838     if(oldx != gameInfo.boardWidth ||
5839        oldy != gameInfo.boardHeight ||
5840        oldv != gameInfo.variant ||
5841        oldh != gameInfo.holdingsWidth
5842                                          )
5843             InitDrawingSizes(-2 ,0);
5844
5845     oldv = gameInfo.variant;
5846     if (redraw)
5847       DrawPosition(TRUE, boards[currentMove]);
5848 }
5849
5850 void
5851 SendBoard(cps, moveNum)
5852      ChessProgramState *cps;
5853      int moveNum;
5854 {
5855     char message[MSG_SIZ];
5856
5857     if (cps->useSetboard) {
5858       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5859       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5860       SendToProgram(message, cps);
5861       free(fen);
5862
5863     } else {
5864       ChessSquare *bp;
5865       int i, j;
5866       /* Kludge to set black to move, avoiding the troublesome and now
5867        * deprecated "black" command.
5868        */
5869       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5870         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5871
5872       SendToProgram("edit\n", cps);
5873       SendToProgram("#\n", cps);
5874       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5875         bp = &boards[moveNum][i][BOARD_LEFT];
5876         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5877           if ((int) *bp < (int) BlackPawn) {
5878             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5879                     AAA + j, ONE + i);
5880             if(message[0] == '+' || message[0] == '~') {
5881               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5882                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5883                         AAA + j, ONE + i);
5884             }
5885             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5886                 message[1] = BOARD_RGHT   - 1 - j + '1';
5887                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5888             }
5889             SendToProgram(message, cps);
5890           }
5891         }
5892       }
5893
5894       SendToProgram("c\n", cps);
5895       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5896         bp = &boards[moveNum][i][BOARD_LEFT];
5897         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5898           if (((int) *bp != (int) EmptySquare)
5899               && ((int) *bp >= (int) BlackPawn)) {
5900             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5901                     AAA + j, ONE + i);
5902             if(message[0] == '+' || message[0] == '~') {
5903               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5904                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5905                         AAA + j, ONE + i);
5906             }
5907             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5908                 message[1] = BOARD_RGHT   - 1 - j + '1';
5909                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5910             }
5911             SendToProgram(message, cps);
5912           }
5913         }
5914       }
5915
5916       SendToProgram(".\n", cps);
5917     }
5918     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5919 }
5920
5921 ChessSquare
5922 DefaultPromoChoice(int white)
5923 {
5924     ChessSquare result;
5925     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5926         result = WhiteFerz; // no choice
5927     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5928         result= WhiteKing; // in Suicide Q is the last thing we want
5929     else if(gameInfo.variant == VariantSpartan)
5930         result = white ? WhiteQueen : WhiteAngel;
5931     else result = WhiteQueen;
5932     if(!white) result = WHITE_TO_BLACK result;
5933     return result;
5934 }
5935
5936 static int autoQueen; // [HGM] oneclick
5937
5938 int
5939 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5940 {
5941     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5942     /* [HGM] add Shogi promotions */
5943     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5944     ChessSquare piece;
5945     ChessMove moveType;
5946     Boolean premove;
5947
5948     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5949     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5950
5951     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5952       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5953         return FALSE;
5954
5955     piece = boards[currentMove][fromY][fromX];
5956     if(gameInfo.variant == VariantShogi) {
5957         promotionZoneSize = BOARD_HEIGHT/3;
5958         highestPromotingPiece = (int)WhiteFerz;
5959     } else if(gameInfo.variant == VariantMakruk) {
5960         promotionZoneSize = 3;
5961     }
5962
5963     // Treat Lance as Pawn when it is not representing Amazon
5964     if(gameInfo.variant != VariantSuper) {
5965         if(piece == WhiteLance) piece = WhitePawn; else
5966         if(piece == BlackLance) piece = BlackPawn;
5967     }
5968
5969     // next weed out all moves that do not touch the promotion zone at all
5970     if((int)piece >= BlackPawn) {
5971         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5972              return FALSE;
5973         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5974     } else {
5975         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5976            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5977     }
5978
5979     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5980
5981     // weed out mandatory Shogi promotions
5982     if(gameInfo.variant == VariantShogi) {
5983         if(piece >= BlackPawn) {
5984             if(toY == 0 && piece == BlackPawn ||
5985                toY == 0 && piece == BlackQueen ||
5986                toY <= 1 && piece == BlackKnight) {
5987                 *promoChoice = '+';
5988                 return FALSE;
5989             }
5990         } else {
5991             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5992                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5993                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5994                 *promoChoice = '+';
5995                 return FALSE;
5996             }
5997         }
5998     }
5999
6000     // weed out obviously illegal Pawn moves
6001     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6002         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6003         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6004         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6005         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6006         // note we are not allowed to test for valid (non-)capture, due to premove
6007     }
6008
6009     // we either have a choice what to promote to, or (in Shogi) whether to promote
6010     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6011         *promoChoice = PieceToChar(BlackFerz);  // no choice
6012         return FALSE;
6013     }
6014     // no sense asking what we must promote to if it is going to explode...
6015     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6016         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6017         return FALSE;
6018     }
6019     // give caller the default choice even if we will not make it
6020     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6021     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6022     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6023                            && gameInfo.variant != VariantShogi
6024                            && gameInfo.variant != VariantSuper) return FALSE;
6025     if(autoQueen) return FALSE; // predetermined
6026
6027     // suppress promotion popup on illegal moves that are not premoves
6028     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6029               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6030     if(appData.testLegality && !premove) {
6031         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6032                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6033         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6034             return FALSE;
6035     }
6036
6037     return TRUE;
6038 }
6039
6040 int
6041 InPalace(row, column)
6042      int row, column;
6043 {   /* [HGM] for Xiangqi */
6044     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6045          column < (BOARD_WIDTH + 4)/2 &&
6046          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6047     return FALSE;
6048 }
6049
6050 int
6051 PieceForSquare (x, y)
6052      int x;
6053      int y;
6054 {
6055   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6056      return -1;
6057   else
6058      return boards[currentMove][y][x];
6059 }
6060
6061 int
6062 OKToStartUserMove(x, y)
6063      int x, y;
6064 {
6065     ChessSquare from_piece;
6066     int white_piece;
6067
6068     if (matchMode) return FALSE;
6069     if (gameMode == EditPosition) return TRUE;
6070
6071     if (x >= 0 && y >= 0)
6072       from_piece = boards[currentMove][y][x];
6073     else
6074       from_piece = EmptySquare;
6075
6076     if (from_piece == EmptySquare) return FALSE;
6077
6078     white_piece = (int)from_piece >= (int)WhitePawn &&
6079       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6080
6081     switch (gameMode) {
6082       case PlayFromGameFile:
6083       case AnalyzeFile:
6084       case TwoMachinesPlay:
6085       case EndOfGame:
6086         return FALSE;
6087
6088       case IcsObserving:
6089       case IcsIdle:
6090         return FALSE;
6091
6092       case MachinePlaysWhite:
6093       case IcsPlayingBlack:
6094         if (appData.zippyPlay) return FALSE;
6095         if (white_piece) {
6096             DisplayMoveError(_("You are playing Black"));
6097             return FALSE;
6098         }
6099         break;
6100
6101       case MachinePlaysBlack:
6102       case IcsPlayingWhite:
6103         if (appData.zippyPlay) return FALSE;
6104         if (!white_piece) {
6105             DisplayMoveError(_("You are playing White"));
6106             return FALSE;
6107         }
6108         break;
6109
6110       case EditGame:
6111         if (!white_piece && WhiteOnMove(currentMove)) {
6112             DisplayMoveError(_("It is White's turn"));
6113             return FALSE;
6114         }
6115         if (white_piece && !WhiteOnMove(currentMove)) {
6116             DisplayMoveError(_("It is Black's turn"));
6117             return FALSE;
6118         }
6119         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6120             /* Editing correspondence game history */
6121             /* Could disallow this or prompt for confirmation */
6122             cmailOldMove = -1;
6123         }
6124         break;
6125
6126       case BeginningOfGame:
6127         if (appData.icsActive) return FALSE;
6128         if (!appData.noChessProgram) {
6129             if (!white_piece) {
6130                 DisplayMoveError(_("You are playing White"));
6131                 return FALSE;
6132             }
6133         }
6134         break;
6135
6136       case Training:
6137         if (!white_piece && WhiteOnMove(currentMove)) {
6138             DisplayMoveError(_("It is White's turn"));
6139             return FALSE;
6140         }
6141         if (white_piece && !WhiteOnMove(currentMove)) {
6142             DisplayMoveError(_("It is Black's turn"));
6143             return FALSE;
6144         }
6145         break;
6146
6147       default:
6148       case IcsExamining:
6149         break;
6150     }
6151     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6152         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6153         && gameMode != AnalyzeFile && gameMode != Training) {
6154         DisplayMoveError(_("Displayed position is not current"));
6155         return FALSE;
6156     }
6157     return TRUE;
6158 }
6159
6160 Boolean
6161 OnlyMove(int *x, int *y, Boolean captures) {
6162     DisambiguateClosure cl;
6163     if (appData.zippyPlay) return FALSE;
6164     switch(gameMode) {
6165       case MachinePlaysBlack:
6166       case IcsPlayingWhite:
6167       case BeginningOfGame:
6168         if(!WhiteOnMove(currentMove)) return FALSE;
6169         break;
6170       case MachinePlaysWhite:
6171       case IcsPlayingBlack:
6172         if(WhiteOnMove(currentMove)) return FALSE;
6173         break;
6174       case EditGame:
6175         break;
6176       default:
6177         return FALSE;
6178     }
6179     cl.pieceIn = EmptySquare;
6180     cl.rfIn = *y;
6181     cl.ffIn = *x;
6182     cl.rtIn = -1;
6183     cl.ftIn = -1;
6184     cl.promoCharIn = NULLCHAR;
6185     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6186     if( cl.kind == NormalMove ||
6187         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6188         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6189         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6190       fromX = cl.ff;
6191       fromY = cl.rf;
6192       *x = cl.ft;
6193       *y = cl.rt;
6194       return TRUE;
6195     }
6196     if(cl.kind != ImpossibleMove) return FALSE;
6197     cl.pieceIn = EmptySquare;
6198     cl.rfIn = -1;
6199     cl.ffIn = -1;
6200     cl.rtIn = *y;
6201     cl.ftIn = *x;
6202     cl.promoCharIn = NULLCHAR;
6203     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6204     if( cl.kind == NormalMove ||
6205         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6206         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6207         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6208       fromX = cl.ff;
6209       fromY = cl.rf;
6210       *x = cl.ft;
6211       *y = cl.rt;
6212       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6213       return TRUE;
6214     }
6215     return FALSE;
6216 }
6217
6218 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6219 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6220 int lastLoadGameUseList = FALSE;
6221 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6222 ChessMove lastLoadGameStart = EndOfFile;
6223
6224 void
6225 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6226      int fromX, fromY, toX, toY;
6227      int promoChar;
6228 {
6229     ChessMove moveType;
6230     ChessSquare pdown, pup;
6231
6232     /* Check if the user is playing in turn.  This is complicated because we
6233        let the user "pick up" a piece before it is his turn.  So the piece he
6234        tried to pick up may have been captured by the time he puts it down!
6235        Therefore we use the color the user is supposed to be playing in this
6236        test, not the color of the piece that is currently on the starting
6237        square---except in EditGame mode, where the user is playing both
6238        sides; fortunately there the capture race can't happen.  (It can
6239        now happen in IcsExamining mode, but that's just too bad.  The user
6240        will get a somewhat confusing message in that case.)
6241        */
6242
6243     switch (gameMode) {
6244       case PlayFromGameFile:
6245       case AnalyzeFile:
6246       case TwoMachinesPlay:
6247       case EndOfGame:
6248       case IcsObserving:
6249       case IcsIdle:
6250         /* We switched into a game mode where moves are not accepted,
6251            perhaps while the mouse button was down. */
6252         return;
6253
6254       case MachinePlaysWhite:
6255         /* User is moving for Black */
6256         if (WhiteOnMove(currentMove)) {
6257             DisplayMoveError(_("It is White's turn"));
6258             return;
6259         }
6260         break;
6261
6262       case MachinePlaysBlack:
6263         /* User is moving for White */
6264         if (!WhiteOnMove(currentMove)) {
6265             DisplayMoveError(_("It is Black's turn"));
6266             return;
6267         }
6268         break;
6269
6270       case EditGame:
6271       case IcsExamining:
6272       case BeginningOfGame:
6273       case AnalyzeMode:
6274       case Training:
6275         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6276         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6277             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6278             /* User is moving for Black */
6279             if (WhiteOnMove(currentMove)) {
6280                 DisplayMoveError(_("It is White's turn"));
6281                 return;
6282             }
6283         } else {
6284             /* User is moving for White */
6285             if (!WhiteOnMove(currentMove)) {
6286                 DisplayMoveError(_("It is Black's turn"));
6287                 return;
6288             }
6289         }
6290         break;
6291
6292       case IcsPlayingBlack:
6293         /* User is moving for Black */
6294         if (WhiteOnMove(currentMove)) {
6295             if (!appData.premove) {
6296                 DisplayMoveError(_("It is White's turn"));
6297             } else if (toX >= 0 && toY >= 0) {
6298                 premoveToX = toX;
6299                 premoveToY = toY;
6300                 premoveFromX = fromX;
6301                 premoveFromY = fromY;
6302                 premovePromoChar = promoChar;
6303                 gotPremove = 1;
6304                 if (appData.debugMode)
6305                     fprintf(debugFP, "Got premove: fromX %d,"
6306                             "fromY %d, toX %d, toY %d\n",
6307                             fromX, fromY, toX, toY);
6308             }
6309             return;
6310         }
6311         break;
6312
6313       case IcsPlayingWhite:
6314         /* User is moving for White */
6315         if (!WhiteOnMove(currentMove)) {
6316             if (!appData.premove) {
6317                 DisplayMoveError(_("It is Black's turn"));
6318             } else if (toX >= 0 && toY >= 0) {
6319                 premoveToX = toX;
6320                 premoveToY = toY;
6321                 premoveFromX = fromX;
6322                 premoveFromY = fromY;
6323                 premovePromoChar = promoChar;
6324                 gotPremove = 1;
6325                 if (appData.debugMode)
6326                     fprintf(debugFP, "Got premove: fromX %d,"
6327                             "fromY %d, toX %d, toY %d\n",
6328                             fromX, fromY, toX, toY);
6329             }
6330             return;
6331         }
6332         break;
6333
6334       default:
6335         break;
6336
6337       case EditPosition:
6338         /* EditPosition, empty square, or different color piece;
6339            click-click move is possible */
6340         if (toX == -2 || toY == -2) {
6341             boards[0][fromY][fromX] = EmptySquare;
6342             DrawPosition(FALSE, boards[currentMove]);
6343             return;
6344         } else if (toX >= 0 && toY >= 0) {
6345             boards[0][toY][toX] = boards[0][fromY][fromX];
6346             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6347                 if(boards[0][fromY][0] != EmptySquare) {
6348                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6349                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6350                 }
6351             } else
6352             if(fromX == BOARD_RGHT+1) {
6353                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6354                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6355                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6356                 }
6357             } else
6358             boards[0][fromY][fromX] = EmptySquare;
6359             DrawPosition(FALSE, boards[currentMove]);
6360             return;
6361         }
6362         return;
6363     }
6364
6365     if(toX < 0 || toY < 0) return;
6366     pdown = boards[currentMove][fromY][fromX];
6367     pup = boards[currentMove][toY][toX];
6368
6369     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6370     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6371          if( pup != EmptySquare ) return;
6372          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6373            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6374                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6375            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6376            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6377            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6378            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6379          fromY = DROP_RANK;
6380     }
6381
6382     /* [HGM] always test for legality, to get promotion info */
6383     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6384                                          fromY, fromX, toY, toX, promoChar);
6385     /* [HGM] but possibly ignore an IllegalMove result */
6386     if (appData.testLegality) {
6387         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6388             DisplayMoveError(_("Illegal move"));
6389             return;
6390         }
6391     }
6392
6393     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6394 }
6395
6396 /* Common tail of UserMoveEvent and DropMenuEvent */
6397 int
6398 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6399      ChessMove moveType;
6400      int fromX, fromY, toX, toY;
6401      /*char*/int promoChar;
6402 {
6403     char *bookHit = 0;
6404
6405     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6406         // [HGM] superchess: suppress promotions to non-available piece
6407         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6408         if(WhiteOnMove(currentMove)) {
6409             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6410         } else {
6411             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6412         }
6413     }
6414
6415     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6416        move type in caller when we know the move is a legal promotion */
6417     if(moveType == NormalMove && promoChar)
6418         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6419
6420     /* [HGM] <popupFix> The following if has been moved here from
6421        UserMoveEvent(). Because it seemed to belong here (why not allow
6422        piece drops in training games?), and because it can only be
6423        performed after it is known to what we promote. */
6424     if (gameMode == Training) {
6425       /* compare the move played on the board to the next move in the
6426        * game. If they match, display the move and the opponent's response.
6427        * If they don't match, display an error message.
6428        */
6429       int saveAnimate;
6430       Board testBoard;
6431       CopyBoard(testBoard, boards[currentMove]);
6432       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6433
6434       if (CompareBoards(testBoard, boards[currentMove+1])) {
6435         ForwardInner(currentMove+1);
6436
6437         /* Autoplay the opponent's response.
6438          * if appData.animate was TRUE when Training mode was entered,
6439          * the response will be animated.
6440          */
6441         saveAnimate = appData.animate;
6442         appData.animate = animateTraining;
6443         ForwardInner(currentMove+1);
6444         appData.animate = saveAnimate;
6445
6446         /* check for the end of the game */
6447         if (currentMove >= forwardMostMove) {
6448           gameMode = PlayFromGameFile;
6449           ModeHighlight();
6450           SetTrainingModeOff();
6451           DisplayInformation(_("End of game"));
6452         }
6453       } else {
6454         DisplayError(_("Incorrect move"), 0);
6455       }
6456       return 1;
6457     }
6458
6459   /* Ok, now we know that the move is good, so we can kill
6460      the previous line in Analysis Mode */
6461   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6462                                 && currentMove < forwardMostMove) {
6463     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6464     else forwardMostMove = currentMove;
6465   }
6466
6467   /* If we need the chess program but it's dead, restart it */
6468   ResurrectChessProgram();
6469
6470   /* A user move restarts a paused game*/
6471   if (pausing)
6472     PauseEvent();
6473
6474   thinkOutput[0] = NULLCHAR;
6475
6476   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6477
6478   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6479     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6480     return 1;
6481   }
6482
6483   if (gameMode == BeginningOfGame) {
6484     if (appData.noChessProgram) {
6485       gameMode = EditGame;
6486       SetGameInfo();
6487     } else {
6488       char buf[MSG_SIZ];
6489       gameMode = MachinePlaysBlack;
6490       StartClocks();
6491       SetGameInfo();
6492       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6493       DisplayTitle(buf);
6494       if (first.sendName) {
6495         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6496         SendToProgram(buf, &first);
6497       }
6498       StartClocks();
6499     }
6500     ModeHighlight();
6501   }
6502
6503   /* Relay move to ICS or chess engine */
6504   if (appData.icsActive) {
6505     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6506         gameMode == IcsExamining) {
6507       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6508         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6509         SendToICS("draw ");
6510         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6511       }
6512       // also send plain move, in case ICS does not understand atomic claims
6513       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6514       ics_user_moved = 1;
6515     }
6516   } else {
6517     if (first.sendTime && (gameMode == BeginningOfGame ||
6518                            gameMode == MachinePlaysWhite ||
6519                            gameMode == MachinePlaysBlack)) {
6520       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6521     }
6522     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6523          // [HGM] book: if program might be playing, let it use book
6524         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6525         first.maybeThinking = TRUE;
6526     } else SendMoveToProgram(forwardMostMove-1, &first);
6527     if (currentMove == cmailOldMove + 1) {
6528       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6529     }
6530   }
6531
6532   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6533
6534   switch (gameMode) {
6535   case EditGame:
6536     if(appData.testLegality)
6537     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6538     case MT_NONE:
6539     case MT_CHECK:
6540       break;
6541     case MT_CHECKMATE:
6542     case MT_STAINMATE:
6543       if (WhiteOnMove(currentMove)) {
6544         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6545       } else {
6546         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6547       }
6548       break;
6549     case MT_STALEMATE:
6550       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6551       break;
6552     }
6553     break;
6554
6555   case MachinePlaysBlack:
6556   case MachinePlaysWhite:
6557     /* disable certain menu options while machine is thinking */
6558     SetMachineThinkingEnables();
6559     break;
6560
6561   default:
6562     break;
6563   }
6564
6565   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6566   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6567
6568   if(bookHit) { // [HGM] book: simulate book reply
6569         static char bookMove[MSG_SIZ]; // a bit generous?
6570
6571         programStats.nodes = programStats.depth = programStats.time =
6572         programStats.score = programStats.got_only_move = 0;
6573         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6574
6575         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6576         strcat(bookMove, bookHit);
6577         HandleMachineMove(bookMove, &first);
6578   }
6579   return 1;
6580 }
6581
6582 void
6583 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6584      Board board;
6585      int flags;
6586      ChessMove kind;
6587      int rf, ff, rt, ft;
6588      VOIDSTAR closure;
6589 {
6590     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6591     Markers *m = (Markers *) closure;
6592     if(rf == fromY && ff == fromX)
6593         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6594                          || kind == WhiteCapturesEnPassant
6595                          || kind == BlackCapturesEnPassant);
6596     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6597 }
6598
6599 void
6600 MarkTargetSquares(int clear)
6601 {
6602   int x, y;
6603   if(!appData.markers || !appData.highlightDragging ||
6604      !appData.testLegality || gameMode == EditPosition) return;
6605   if(clear) {
6606     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6607   } else {
6608     int capt = 0;
6609     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6610     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6611       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6612       if(capt)
6613       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6614     }
6615   }
6616   DrawPosition(TRUE, NULL);
6617 }
6618
6619 int
6620 Explode(Board board, int fromX, int fromY, int toX, int toY)
6621 {
6622     if(gameInfo.variant == VariantAtomic &&
6623        (board[toY][toX] != EmptySquare ||                     // capture?
6624         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6625                          board[fromY][fromX] == BlackPawn   )
6626       )) {
6627         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6628         return TRUE;
6629     }
6630     return FALSE;
6631 }
6632
6633 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6634
6635 int CanPromote(ChessSquare piece, int y)
6636 {
6637         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6638         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6639         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6640            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6641            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6642                                                   gameInfo.variant == VariantMakruk) return FALSE;
6643         return (piece == BlackPawn && y == 1 ||
6644                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6645                 piece == BlackLance && y == 1 ||
6646                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6647 }
6648
6649 void LeftClick(ClickType clickType, int xPix, int yPix)
6650 {
6651     int x, y;
6652     Boolean saveAnimate;
6653     static int second = 0, promotionChoice = 0, clearFlag = 0;
6654     char promoChoice = NULLCHAR;
6655     ChessSquare piece;
6656
6657     if(appData.seekGraph && appData.icsActive && loggedOn &&
6658         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6659         SeekGraphClick(clickType, xPix, yPix, 0);
6660         return;
6661     }
6662
6663     if (clickType == Press) ErrorPopDown();
6664     MarkTargetSquares(1);
6665
6666     x = EventToSquare(xPix, BOARD_WIDTH);
6667     y = EventToSquare(yPix, BOARD_HEIGHT);
6668     if (!flipView && y >= 0) {
6669         y = BOARD_HEIGHT - 1 - y;
6670     }
6671     if (flipView && x >= 0) {
6672         x = BOARD_WIDTH - 1 - x;
6673     }
6674
6675     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6676         defaultPromoChoice = promoSweep;
6677         promoSweep = EmptySquare;   // terminate sweep
6678         promoDefaultAltered = TRUE;
6679         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6680     }
6681
6682     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6683         if(clickType == Release) return; // ignore upclick of click-click destination
6684         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6685         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6686         if(gameInfo.holdingsWidth &&
6687                 (WhiteOnMove(currentMove)
6688                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6689                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6690             // click in right holdings, for determining promotion piece
6691             ChessSquare p = boards[currentMove][y][x];
6692             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6693             if(p != EmptySquare) {
6694                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6695                 fromX = fromY = -1;
6696                 return;
6697             }
6698         }
6699         DrawPosition(FALSE, boards[currentMove]);
6700         return;
6701     }
6702
6703     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6704     if(clickType == Press
6705             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6706               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6707               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6708         return;
6709
6710     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6711         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6712
6713     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6714         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6715                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6716         defaultPromoChoice = DefaultPromoChoice(side);
6717     }
6718
6719     autoQueen = appData.alwaysPromoteToQueen;
6720
6721     if (fromX == -1) {
6722       int originalY = y;
6723       gatingPiece = EmptySquare;
6724       if (clickType != Press) {
6725         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6726             DragPieceEnd(xPix, yPix); dragging = 0;
6727             DrawPosition(FALSE, NULL);
6728         }
6729         return;
6730       }
6731       fromX = x; fromY = y;
6732       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6733          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6734          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6735             /* First square */
6736             if (OKToStartUserMove(fromX, fromY)) {
6737                 second = 0;
6738                 MarkTargetSquares(0);
6739                 DragPieceBegin(xPix, yPix); dragging = 1;
6740                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6741                     promoSweep = defaultPromoChoice;
6742                     selectFlag = 0; lastX = xPix; lastY = yPix;
6743                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6744                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6745                 }
6746                 if (appData.highlightDragging) {
6747                     SetHighlights(fromX, fromY, -1, -1);
6748                 }
6749             } else fromX = fromY = -1;
6750             return;
6751         }
6752     }
6753
6754     /* fromX != -1 */
6755     if (clickType == Press && gameMode != EditPosition) {
6756         ChessSquare fromP;
6757         ChessSquare toP;
6758         int frc;
6759
6760         // ignore off-board to clicks
6761         if(y < 0 || x < 0) return;
6762
6763         /* Check if clicking again on the same color piece */
6764         fromP = boards[currentMove][fromY][fromX];
6765         toP = boards[currentMove][y][x];
6766         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6767         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6768              WhitePawn <= toP && toP <= WhiteKing &&
6769              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6770              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6771             (BlackPawn <= fromP && fromP <= BlackKing &&
6772              BlackPawn <= toP && toP <= BlackKing &&
6773              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6774              !(fromP == BlackKing && toP == BlackRook && frc))) {
6775             /* Clicked again on same color piece -- changed his mind */
6776             second = (x == fromX && y == fromY);
6777             promoDefaultAltered = FALSE;
6778            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6779             if (appData.highlightDragging) {
6780                 SetHighlights(x, y, -1, -1);
6781             } else {
6782                 ClearHighlights();
6783             }
6784             if (OKToStartUserMove(x, y)) {
6785                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6786                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6787                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6788                  gatingPiece = boards[currentMove][fromY][fromX];
6789                 else gatingPiece = EmptySquare;
6790                 fromX = x;
6791                 fromY = y; dragging = 1;
6792                 MarkTargetSquares(0);
6793                 DragPieceBegin(xPix, yPix);
6794                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6795                     promoSweep = defaultPromoChoice;
6796                     selectFlag = 0; lastX = xPix; lastY = yPix;
6797                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6798                 }
6799             }
6800            }
6801            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6802            second = FALSE; 
6803         }
6804         // ignore clicks on holdings
6805         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6806     }
6807
6808     if (clickType == Release && x == fromX && y == fromY) {
6809         DragPieceEnd(xPix, yPix); dragging = 0;
6810         if(clearFlag) {
6811             // a deferred attempt to click-click move an empty square on top of a piece
6812             boards[currentMove][y][x] = EmptySquare;
6813             ClearHighlights();
6814             DrawPosition(FALSE, boards[currentMove]);
6815             fromX = fromY = -1; clearFlag = 0;
6816             return;
6817         }
6818         if (appData.animateDragging) {
6819             /* Undo animation damage if any */
6820             DrawPosition(FALSE, NULL);
6821         }
6822         if (second) {
6823             /* Second up/down in same square; just abort move */
6824             second = 0;
6825             fromX = fromY = -1;
6826             gatingPiece = EmptySquare;
6827             ClearHighlights();
6828             gotPremove = 0;
6829             ClearPremoveHighlights();
6830         } else {
6831             /* First upclick in same square; start click-click mode */
6832             SetHighlights(x, y, -1, -1);
6833         }
6834         return;
6835     }
6836
6837     clearFlag = 0;
6838
6839     /* we now have a different from- and (possibly off-board) to-square */
6840     /* Completed move */
6841     toX = x;
6842     toY = y;
6843     saveAnimate = appData.animate;
6844     if (clickType == Press) {
6845         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6846             // must be Edit Position mode with empty-square selected
6847             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6848             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6849             return;
6850         }
6851         /* Finish clickclick move */
6852         if (appData.animate || appData.highlightLastMove) {
6853             SetHighlights(fromX, fromY, toX, toY);
6854         } else {
6855             ClearHighlights();
6856         }
6857     } else {
6858         /* Finish drag move */
6859         if (appData.highlightLastMove) {
6860             SetHighlights(fromX, fromY, toX, toY);
6861         } else {
6862             ClearHighlights();
6863         }
6864         DragPieceEnd(xPix, yPix); dragging = 0;
6865         /* Don't animate move and drag both */
6866         appData.animate = FALSE;
6867     }
6868
6869     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6870     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6871         ChessSquare piece = boards[currentMove][fromY][fromX];
6872         if(gameMode == EditPosition && piece != EmptySquare &&
6873            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6874             int n;
6875
6876             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6877                 n = PieceToNumber(piece - (int)BlackPawn);
6878                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6879                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6880                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6881             } else
6882             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6883                 n = PieceToNumber(piece);
6884                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6885                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6886                 boards[currentMove][n][BOARD_WIDTH-2]++;
6887             }
6888             boards[currentMove][fromY][fromX] = EmptySquare;
6889         }
6890         ClearHighlights();
6891         fromX = fromY = -1;
6892         DrawPosition(TRUE, boards[currentMove]);
6893         return;
6894     }
6895
6896     // off-board moves should not be highlighted
6897     if(x < 0 || y < 0) ClearHighlights();
6898
6899     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6900
6901     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6902         SetHighlights(fromX, fromY, toX, toY);
6903         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6904             // [HGM] super: promotion to captured piece selected from holdings
6905             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6906             promotionChoice = TRUE;
6907             // kludge follows to temporarily execute move on display, without promoting yet
6908             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6909             boards[currentMove][toY][toX] = p;
6910             DrawPosition(FALSE, boards[currentMove]);
6911             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6912             boards[currentMove][toY][toX] = q;
6913             DisplayMessage("Click in holdings to choose piece", "");
6914             return;
6915         }
6916         PromotionPopUp();
6917     } else {
6918         int oldMove = currentMove;
6919         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6920         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6921         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6922         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6923            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6924             DrawPosition(TRUE, boards[currentMove]);
6925         fromX = fromY = -1;
6926     }
6927     appData.animate = saveAnimate;
6928     if (appData.animate || appData.animateDragging) {
6929         /* Undo animation damage if needed */
6930         DrawPosition(FALSE, NULL);
6931     }
6932 }
6933
6934 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6935 {   // front-end-free part taken out of PieceMenuPopup
6936     int whichMenu; int xSqr, ySqr;
6937
6938     if(seekGraphUp) { // [HGM] seekgraph
6939         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6940         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6941         return -2;
6942     }
6943
6944     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6945          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6946         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6947         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6948         if(action == Press)   {
6949             originalFlip = flipView;
6950             flipView = !flipView; // temporarily flip board to see game from partners perspective
6951             DrawPosition(TRUE, partnerBoard);
6952             DisplayMessage(partnerStatus, "");
6953             partnerUp = TRUE;
6954         } else if(action == Release) {
6955             flipView = originalFlip;
6956             DrawPosition(TRUE, boards[currentMove]);
6957             partnerUp = FALSE;
6958         }
6959         return -2;
6960     }
6961
6962     xSqr = EventToSquare(x, BOARD_WIDTH);
6963     ySqr = EventToSquare(y, BOARD_HEIGHT);
6964     if (action == Release) {
6965         if(pieceSweep != EmptySquare) {
6966             EditPositionMenuEvent(pieceSweep, toX, toY);
6967             pieceSweep = EmptySquare;
6968         } else UnLoadPV(); // [HGM] pv
6969     }
6970     if (action != Press) return -2; // return code to be ignored
6971     switch (gameMode) {
6972       case IcsExamining:
6973         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6974       case EditPosition:
6975         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6976         if (xSqr < 0 || ySqr < 0) return -1;
6977         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6978         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6979         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6980         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6981         NextPiece(0);
6982         return -2;\r
6983       case IcsObserving:
6984         if(!appData.icsEngineAnalyze) return -1;
6985       case IcsPlayingWhite:
6986       case IcsPlayingBlack:
6987         if(!appData.zippyPlay) goto noZip;
6988       case AnalyzeMode:
6989       case AnalyzeFile:
6990       case MachinePlaysWhite:
6991       case MachinePlaysBlack:
6992       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6993         if (!appData.dropMenu) {
6994           LoadPV(x, y);
6995           return 2; // flag front-end to grab mouse events
6996         }
6997         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6998            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6999       case EditGame:
7000       noZip:
7001         if (xSqr < 0 || ySqr < 0) return -1;
7002         if (!appData.dropMenu || appData.testLegality &&
7003             gameInfo.variant != VariantBughouse &&
7004             gameInfo.variant != VariantCrazyhouse) return -1;
7005         whichMenu = 1; // drop menu
7006         break;
7007       default:
7008         return -1;
7009     }
7010
7011     if (((*fromX = xSqr) < 0) ||
7012         ((*fromY = ySqr) < 0)) {
7013         *fromX = *fromY = -1;
7014         return -1;
7015     }
7016     if (flipView)
7017       *fromX = BOARD_WIDTH - 1 - *fromX;
7018     else
7019       *fromY = BOARD_HEIGHT - 1 - *fromY;
7020
7021     return whichMenu;
7022 }
7023
7024 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7025 {
7026 //    char * hint = lastHint;
7027     FrontEndProgramStats stats;
7028
7029     stats.which = cps == &first ? 0 : 1;
7030     stats.depth = cpstats->depth;
7031     stats.nodes = cpstats->nodes;
7032     stats.score = cpstats->score;
7033     stats.time = cpstats->time;
7034     stats.pv = cpstats->movelist;
7035     stats.hint = lastHint;
7036     stats.an_move_index = 0;
7037     stats.an_move_count = 0;
7038
7039     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7040         stats.hint = cpstats->move_name;
7041         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7042         stats.an_move_count = cpstats->nr_moves;
7043     }
7044
7045     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
7046
7047     SetProgramStats( &stats );
7048 }
7049
7050 #define MAXPLAYERS 500
7051
7052 char *
7053 TourneyStandings(int display)
7054 {
7055     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7056     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7057     char result, *p, *names[MAXPLAYERS];
7058
7059     names[0] = p = strdup(appData.participants);
7060     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7061
7062     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7063
7064     while(result = appData.results[nr]) {
7065         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7066         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7067         wScore = bScore = 0;
7068         switch(result) {
7069           case '+': wScore = 2; break;
7070           case '-': bScore = 2; break;
7071           case '=': wScore = bScore = 1; break;
7072           case ' ':
7073           case '*': return NULL; // tourney not finished
7074         }
7075         score[w] += wScore;
7076         score[b] += bScore;
7077         games[w]++;
7078         games[b]++;
7079         nr++;
7080     }
7081     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7082     for(w=0; w<nPlayers; w++) {
7083         bScore = -1;
7084         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7085         ranking[w] = b; points[w] = bScore; score[b] = -2;
7086     }
7087     p = malloc(nPlayers*34+1);
7088     for(w=0; w<nPlayers && w<display; w++)
7089         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7090     free(names[0]);
7091     return p;
7092 }
7093
7094 void
7095 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7096 {       // count all piece types
7097         int p, f, r;
7098         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7099         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7100         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7101                 p = board[r][f];
7102                 pCnt[p]++;
7103                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7104                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7105                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7106                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7107                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7108                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7109         }
7110 }
7111
7112 int
7113 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7114 {
7115         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7116         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7117
7118         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7119         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7120         if(myPawns == 2 && nMine == 3) // KPP
7121             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7122         if(myPawns == 1 && nMine == 2) // KP
7123             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7124         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7125             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7126         if(myPawns) return FALSE;
7127         if(pCnt[WhiteRook+side])
7128             return pCnt[BlackRook-side] ||
7129                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7130                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7131                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7132         if(pCnt[WhiteCannon+side]) {
7133             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7134             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7135         }
7136         if(pCnt[WhiteKnight+side])
7137             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7138         return FALSE;
7139 }
7140
7141 int
7142 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7143 {
7144         VariantClass v = gameInfo.variant;
7145
7146         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7147         if(v == VariantShatranj) return TRUE; // always winnable through baring
7148         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7149         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7150
7151         if(v == VariantXiangqi) {
7152                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7153
7154                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7155                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7156                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7157                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7158                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7159                 if(stale) // we have at least one last-rank P plus perhaps C
7160                     return majors // KPKX
7161                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7162                 else // KCA*E*
7163                     return pCnt[WhiteFerz+side] // KCAK
7164                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7165                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7166                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7167
7168         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7169                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7170
7171                 if(nMine == 1) return FALSE; // bare King
7172                 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
7173                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7174                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7175                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7176                 if(pCnt[WhiteKnight+side])
7177                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7178                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7179                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7180                 if(nBishops)
7181                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7182                 if(pCnt[WhiteAlfil+side])
7183                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7184                 if(pCnt[WhiteWazir+side])
7185                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7186         }
7187
7188         return TRUE;
7189 }
7190
7191 int
7192 Adjudicate(ChessProgramState *cps)
7193 {       // [HGM] some adjudications useful with buggy engines
7194         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7195         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7196         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7197         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7198         int k, count = 0; static int bare = 1;
7199         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7200         Boolean canAdjudicate = !appData.icsActive;
7201
7202         // most tests only when we understand the game, i.e. legality-checking on
7203             if( appData.testLegality )
7204             {   /* [HGM] Some more adjudications for obstinate engines */
7205                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7206                 static int moveCount = 6;
7207                 ChessMove result;
7208                 char *reason = NULL;
7209
7210                 /* Count what is on board. */
7211                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7212
7213                 /* Some material-based adjudications that have to be made before stalemate test */
7214                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7215                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7216                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7217                      if(canAdjudicate && appData.checkMates) {
7218                          if(engineOpponent)
7219                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7220                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7221                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7222                          return 1;
7223                      }
7224                 }
7225
7226                 /* Bare King in Shatranj (loses) or Losers (wins) */
7227                 if( nrW == 1 || nrB == 1) {
7228                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7229                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7230                      if(canAdjudicate && appData.checkMates) {
7231                          if(engineOpponent)
7232                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7233                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7234                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7235                          return 1;
7236                      }
7237                   } else
7238                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7239                   {    /* bare King */
7240                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7241                         if(canAdjudicate && appData.checkMates) {
7242                             /* but only adjudicate if adjudication enabled */
7243                             if(engineOpponent)
7244                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7245                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7246                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7247                             return 1;
7248                         }
7249                   }
7250                 } else bare = 1;
7251
7252
7253             // don't wait for engine to announce game end if we can judge ourselves
7254             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7255               case MT_CHECK:
7256                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7257                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7258                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7259                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7260                             checkCnt++;
7261                         if(checkCnt >= 2) {
7262                             reason = "Xboard adjudication: 3rd check";
7263                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7264                             break;
7265                         }
7266                     }
7267                 }
7268               case MT_NONE:
7269               default:
7270                 break;
7271               case MT_STALEMATE:
7272               case MT_STAINMATE:
7273                 reason = "Xboard adjudication: Stalemate";
7274                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7275                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7276                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7277                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7278                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7279                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7280                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7281                                                                         EP_CHECKMATE : EP_WINS);
7282                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7283                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7284                 }
7285                 break;
7286               case MT_CHECKMATE:
7287                 reason = "Xboard adjudication: Checkmate";
7288                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7289                 break;
7290             }
7291
7292                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7293                     case EP_STALEMATE:
7294                         result = GameIsDrawn; break;
7295                     case EP_CHECKMATE:
7296                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7297                     case EP_WINS:
7298                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7299                     default:
7300                         result = EndOfFile;
7301                 }
7302                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7303                     if(engineOpponent)
7304                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7305                     GameEnds( result, reason, GE_XBOARD );
7306                     return 1;
7307                 }
7308
7309                 /* Next absolutely insufficient mating material. */
7310                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7311                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7312                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7313
7314                      /* always flag draws, for judging claims */
7315                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7316
7317                      if(canAdjudicate && appData.materialDraws) {
7318                          /* but only adjudicate them if adjudication enabled */
7319                          if(engineOpponent) {
7320                            SendToProgram("force\n", engineOpponent); // suppress reply
7321                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7322                          }
7323                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7324                          return 1;
7325                      }
7326                 }
7327
7328                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7329                 if(gameInfo.variant == VariantXiangqi ?
7330                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7331                  : nrW + nrB == 4 &&
7332                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7333                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7334                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7335                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7336                    ) ) {
7337                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7338                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7339                           if(engineOpponent) {
7340                             SendToProgram("force\n", engineOpponent); // suppress reply
7341                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7342                           }
7343                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7344                           return 1;
7345                      }
7346                 } else moveCount = 6;
7347             }
7348         if (appData.debugMode) { int i;
7349             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7350                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7351                     appData.drawRepeats);
7352             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7353               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7354
7355         }
7356
7357         // Repetition draws and 50-move rule can be applied independently of legality testing
7358
7359                 /* Check for rep-draws */
7360                 count = 0;
7361                 for(k = forwardMostMove-2;
7362                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7363                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7364                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7365                     k-=2)
7366                 {   int rights=0;
7367                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7368                         /* compare castling rights */
7369                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7370                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7371                                 rights++; /* King lost rights, while rook still had them */
7372                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7373                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7374                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7375                                    rights++; /* but at least one rook lost them */
7376                         }
7377                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7378                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7379                                 rights++;
7380                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7381                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7382                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7383                                    rights++;
7384                         }
7385                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7386                             && appData.drawRepeats > 1) {
7387                              /* adjudicate after user-specified nr of repeats */
7388                              int result = GameIsDrawn;
7389                              char *details = "XBoard adjudication: repetition draw";
7390                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7391                                 // [HGM] xiangqi: check for forbidden perpetuals
7392                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7393                                 for(m=forwardMostMove; m>k; m-=2) {
7394                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7395                                         ourPerpetual = 0; // the current mover did not always check
7396                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7397                                         hisPerpetual = 0; // the opponent did not always check
7398                                 }
7399                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7400                                                                         ourPerpetual, hisPerpetual);
7401                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7402                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7403                                     details = "Xboard adjudication: perpetual checking";
7404                                 } else
7405                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7406                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7407                                 } else
7408                                 // Now check for perpetual chases
7409                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7410                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7411                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7412                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7413                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7414                                         details = "Xboard adjudication: perpetual chasing";
7415                                     } else
7416                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7417                                         break; // Abort repetition-checking loop.
7418                                 }
7419                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7420                              }
7421                              if(engineOpponent) {
7422                                SendToProgram("force\n", engineOpponent); // suppress reply
7423                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7424                              }
7425                              GameEnds( result, details, GE_XBOARD );
7426                              return 1;
7427                         }
7428                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7429                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7430                     }
7431                 }
7432
7433                 /* Now we test for 50-move draws. Determine ply count */
7434                 count = forwardMostMove;
7435                 /* look for last irreversble move */
7436                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7437                     count--;
7438                 /* if we hit starting position, add initial plies */
7439                 if( count == backwardMostMove )
7440                     count -= initialRulePlies;
7441                 count = forwardMostMove - count;
7442                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7443                         // adjust reversible move counter for checks in Xiangqi
7444                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7445                         if(i < backwardMostMove) i = backwardMostMove;
7446                         while(i <= forwardMostMove) {
7447                                 lastCheck = inCheck; // check evasion does not count
7448                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7449                                 if(inCheck || lastCheck) count--; // check does not count
7450                                 i++;
7451                         }
7452                 }
7453                 if( count >= 100)
7454                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7455                          /* this is used to judge if draw claims are legal */
7456                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7457                          if(engineOpponent) {
7458                            SendToProgram("force\n", engineOpponent); // suppress reply
7459                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7460                          }
7461                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7462                          return 1;
7463                 }
7464
7465                 /* if draw offer is pending, treat it as a draw claim
7466                  * when draw condition present, to allow engines a way to
7467                  * claim draws before making their move to avoid a race
7468                  * condition occurring after their move
7469                  */
7470                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7471                          char *p = NULL;
7472                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7473                              p = "Draw claim: 50-move rule";
7474                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7475                              p = "Draw claim: 3-fold repetition";
7476                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7477                              p = "Draw claim: insufficient mating material";
7478                          if( p != NULL && canAdjudicate) {
7479                              if(engineOpponent) {
7480                                SendToProgram("force\n", engineOpponent); // suppress reply
7481                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7482                              }
7483                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7484                              return 1;
7485                          }
7486                 }
7487
7488                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7489                     if(engineOpponent) {
7490                       SendToProgram("force\n", engineOpponent); // suppress reply
7491                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7492                     }
7493                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7494                     return 1;
7495                 }
7496         return 0;
7497 }
7498
7499 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7500 {   // [HGM] book: this routine intercepts moves to simulate book replies
7501     char *bookHit = NULL;
7502
7503     //first determine if the incoming move brings opponent into his book
7504     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7505         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7506     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7507     if(bookHit != NULL && !cps->bookSuspend) {
7508         // make sure opponent is not going to reply after receiving move to book position
7509         SendToProgram("force\n", cps);
7510         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7511     }
7512     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7513     // now arrange restart after book miss
7514     if(bookHit) {
7515         // after a book hit we never send 'go', and the code after the call to this routine
7516         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7517         char buf[MSG_SIZ];
7518         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7519         SendToProgram(buf, cps);
7520         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7521     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7522         SendToProgram("go\n", cps);
7523         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7524     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7525         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7526             SendToProgram("go\n", cps);
7527         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7528     }
7529     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7530 }
7531
7532 char *savedMessage;
7533 ChessProgramState *savedState;
7534 void DeferredBookMove(void)
7535 {
7536         if(savedState->lastPing != savedState->lastPong)
7537                     ScheduleDelayedEvent(DeferredBookMove, 10);
7538         else
7539         HandleMachineMove(savedMessage, savedState);
7540 }
7541
7542 void
7543 HandleMachineMove(message, cps)
7544      char *message;
7545      ChessProgramState *cps;
7546 {
7547     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7548     char realname[MSG_SIZ];
7549     int fromX, fromY, toX, toY;
7550     ChessMove moveType;
7551     char promoChar;
7552     char *p;
7553     int machineWhite;
7554     char *bookHit;
7555
7556     cps->userError = 0;
7557
7558 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7559     /*
7560      * Kludge to ignore BEL characters
7561      */
7562     while (*message == '\007') message++;
7563
7564     /*
7565      * [HGM] engine debug message: ignore lines starting with '#' character
7566      */
7567     if(cps->debug && *message == '#') return;
7568
7569     /*
7570      * Look for book output
7571      */
7572     if (cps == &first && bookRequested) {
7573         if (message[0] == '\t' || message[0] == ' ') {
7574             /* Part of the book output is here; append it */
7575             strcat(bookOutput, message);
7576             strcat(bookOutput, "  \n");
7577             return;
7578         } else if (bookOutput[0] != NULLCHAR) {
7579             /* All of book output has arrived; display it */
7580             char *p = bookOutput;
7581             while (*p != NULLCHAR) {
7582                 if (*p == '\t') *p = ' ';
7583                 p++;
7584             }
7585             DisplayInformation(bookOutput);
7586             bookRequested = FALSE;
7587             /* Fall through to parse the current output */
7588         }
7589     }
7590
7591     /*
7592      * Look for machine move.
7593      */
7594     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7595         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7596     {
7597         /* This method is only useful on engines that support ping */
7598         if (cps->lastPing != cps->lastPong) {
7599           if (gameMode == BeginningOfGame) {
7600             /* Extra move from before last new; ignore */
7601             if (appData.debugMode) {
7602                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7603             }
7604           } else {
7605             if (appData.debugMode) {
7606                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7607                         cps->which, gameMode);
7608             }
7609
7610             SendToProgram("undo\n", cps);
7611           }
7612           return;
7613         }
7614
7615         switch (gameMode) {
7616           case BeginningOfGame:
7617             /* Extra move from before last reset; ignore */
7618             if (appData.debugMode) {
7619                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7620             }
7621             return;
7622
7623           case EndOfGame:
7624           case IcsIdle:
7625           default:
7626             /* Extra move after we tried to stop.  The mode test is
7627                not a reliable way of detecting this problem, but it's
7628                the best we can do on engines that don't support ping.
7629             */
7630             if (appData.debugMode) {
7631                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7632                         cps->which, gameMode);
7633             }
7634             SendToProgram("undo\n", cps);
7635             return;
7636
7637           case MachinePlaysWhite:
7638           case IcsPlayingWhite:
7639             machineWhite = TRUE;
7640             break;
7641
7642           case MachinePlaysBlack:
7643           case IcsPlayingBlack:
7644             machineWhite = FALSE;
7645             break;
7646
7647           case TwoMachinesPlay:
7648             machineWhite = (cps->twoMachinesColor[0] == 'w');
7649             break;
7650         }
7651         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7652             if (appData.debugMode) {
7653                 fprintf(debugFP,
7654                         "Ignoring move out of turn by %s, gameMode %d"
7655                         ", forwardMost %d\n",
7656                         cps->which, gameMode, forwardMostMove);
7657             }
7658             return;
7659         }
7660
7661     if (appData.debugMode) { int f = forwardMostMove;
7662         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7663                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7664                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7665     }
7666         if(cps->alphaRank) AlphaRank(machineMove, 4);
7667         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7668                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7669             /* Machine move could not be parsed; ignore it. */
7670           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7671                     machineMove, _(cps->which));
7672             DisplayError(buf1, 0);
7673             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7674                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7675             if (gameMode == TwoMachinesPlay) {
7676               GameEnds(machineWhite ? BlackWins : WhiteWins,
7677                        buf1, GE_XBOARD);
7678             }
7679             return;
7680         }
7681
7682         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7683         /* So we have to redo legality test with true e.p. status here,  */
7684         /* to make sure an illegal e.p. capture does not slip through,   */
7685         /* to cause a forfeit on a justified illegal-move complaint      */
7686         /* of the opponent.                                              */
7687         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7688            ChessMove moveType;
7689            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7690                              fromY, fromX, toY, toX, promoChar);
7691             if (appData.debugMode) {
7692                 int i;
7693                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7694                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7695                 fprintf(debugFP, "castling rights\n");
7696             }
7697             if(moveType == IllegalMove) {
7698               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7699                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7700                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7701                            buf1, GE_XBOARD);
7702                 return;
7703            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7704            /* [HGM] Kludge to handle engines that send FRC-style castling
7705               when they shouldn't (like TSCP-Gothic) */
7706            switch(moveType) {
7707              case WhiteASideCastleFR:
7708              case BlackASideCastleFR:
7709                toX+=2;
7710                currentMoveString[2]++;
7711                break;
7712              case WhiteHSideCastleFR:
7713              case BlackHSideCastleFR:
7714                toX--;
7715                currentMoveString[2]--;
7716                break;
7717              default: ; // nothing to do, but suppresses warning of pedantic compilers
7718            }
7719         }
7720         hintRequested = FALSE;
7721         lastHint[0] = NULLCHAR;
7722         bookRequested = FALSE;
7723         /* Program may be pondering now */
7724         cps->maybeThinking = TRUE;
7725         if (cps->sendTime == 2) cps->sendTime = 1;
7726         if (cps->offeredDraw) cps->offeredDraw--;
7727
7728         /* [AS] Save move info*/
7729         pvInfoList[ forwardMostMove ].score = programStats.score;
7730         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7731         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7732
7733         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7734
7735         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7736         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7737             int count = 0;
7738
7739             while( count < adjudicateLossPlies ) {
7740                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7741
7742                 if( count & 1 ) {
7743                     score = -score; /* Flip score for winning side */
7744                 }
7745
7746                 if( score > adjudicateLossThreshold ) {
7747                     break;
7748                 }
7749
7750                 count++;
7751             }
7752
7753             if( count >= adjudicateLossPlies ) {
7754                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7755
7756                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7757                     "Xboard adjudication",
7758                     GE_XBOARD );
7759
7760                 return;
7761             }
7762         }
7763
7764         if(Adjudicate(cps)) {
7765             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7766             return; // [HGM] adjudicate: for all automatic game ends
7767         }
7768
7769 #if ZIPPY
7770         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7771             first.initDone) {
7772           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7773                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7774                 SendToICS("draw ");
7775                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7776           }
7777           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7778           ics_user_moved = 1;
7779           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7780                 char buf[3*MSG_SIZ];
7781
7782                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7783                         programStats.score / 100.,
7784                         programStats.depth,
7785                         programStats.time / 100.,
7786                         (unsigned int)programStats.nodes,
7787                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7788                         programStats.movelist);
7789                 SendToICS(buf);
7790 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7791           }
7792         }
7793 #endif
7794
7795         /* [AS] Clear stats for next move */
7796         ClearProgramStats();
7797         thinkOutput[0] = NULLCHAR;
7798         hiddenThinkOutputState = 0;
7799
7800         bookHit = NULL;
7801         if (gameMode == TwoMachinesPlay) {
7802             /* [HGM] relaying draw offers moved to after reception of move */
7803             /* and interpreting offer as claim if it brings draw condition */
7804             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7805                 SendToProgram("draw\n", cps->other);
7806             }
7807             if (cps->other->sendTime) {
7808                 SendTimeRemaining(cps->other,
7809                                   cps->other->twoMachinesColor[0] == 'w');
7810             }
7811             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7812             if (firstMove && !bookHit) {
7813                 firstMove = FALSE;
7814                 if (cps->other->useColors) {
7815                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7816                 }
7817                 SendToProgram("go\n", cps->other);
7818             }
7819             cps->other->maybeThinking = TRUE;
7820         }
7821
7822         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7823
7824         if (!pausing && appData.ringBellAfterMoves) {
7825             RingBell();
7826         }
7827
7828         /*
7829          * Reenable menu items that were disabled while
7830          * machine was thinking
7831          */
7832         if (gameMode != TwoMachinesPlay)
7833             SetUserThinkingEnables();
7834
7835         // [HGM] book: after book hit opponent has received move and is now in force mode
7836         // force the book reply into it, and then fake that it outputted this move by jumping
7837         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7838         if(bookHit) {
7839                 static char bookMove[MSG_SIZ]; // a bit generous?
7840
7841                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7842                 strcat(bookMove, bookHit);
7843                 message = bookMove;
7844                 cps = cps->other;
7845                 programStats.nodes = programStats.depth = programStats.time =
7846                 programStats.score = programStats.got_only_move = 0;
7847                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7848
7849                 if(cps->lastPing != cps->lastPong) {
7850                     savedMessage = message; // args for deferred call
7851                     savedState = cps;
7852                     ScheduleDelayedEvent(DeferredBookMove, 10);
7853                     return;
7854                 }
7855                 goto FakeBookMove;
7856         }
7857
7858         return;
7859     }
7860
7861     /* Set special modes for chess engines.  Later something general
7862      *  could be added here; for now there is just one kludge feature,
7863      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7864      *  when "xboard" is given as an interactive command.
7865      */
7866     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7867         cps->useSigint = FALSE;
7868         cps->useSigterm = FALSE;
7869     }
7870     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7871       ParseFeatures(message+8, cps);
7872       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7873     }
7874
7875     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7876       int dummy, s=6; char buf[MSG_SIZ];
7877       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7878       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7879       ParseFEN(boards[0], &dummy, message+s);
7880       DrawPosition(TRUE, boards[0]);
7881       startedFromSetupPosition = TRUE;
7882       return;
7883     }
7884     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7885      * want this, I was asked to put it in, and obliged.
7886      */
7887     if (!strncmp(message, "setboard ", 9)) {
7888         Board initial_position;
7889
7890         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7891
7892         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7893             DisplayError(_("Bad FEN received from engine"), 0);
7894             return ;
7895         } else {
7896            Reset(TRUE, FALSE);
7897            CopyBoard(boards[0], initial_position);
7898            initialRulePlies = FENrulePlies;
7899            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7900            else gameMode = MachinePlaysBlack;
7901            DrawPosition(FALSE, boards[currentMove]);
7902         }
7903         return;
7904     }
7905
7906     /*
7907      * Look for communication commands
7908      */
7909     if (!strncmp(message, "telluser ", 9)) {
7910         if(message[9] == '\\' && message[10] == '\\')
7911             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7912         DisplayNote(message + 9);
7913         return;
7914     }
7915     if (!strncmp(message, "tellusererror ", 14)) {
7916         cps->userError = 1;
7917         if(message[14] == '\\' && message[15] == '\\')
7918             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7919         DisplayError(message + 14, 0);
7920         return;
7921     }
7922     if (!strncmp(message, "tellopponent ", 13)) {
7923       if (appData.icsActive) {
7924         if (loggedOn) {
7925           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7926           SendToICS(buf1);
7927         }
7928       } else {
7929         DisplayNote(message + 13);
7930       }
7931       return;
7932     }
7933     if (!strncmp(message, "tellothers ", 11)) {
7934       if (appData.icsActive) {
7935         if (loggedOn) {
7936           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7937           SendToICS(buf1);
7938         }
7939       }
7940       return;
7941     }
7942     if (!strncmp(message, "tellall ", 8)) {
7943       if (appData.icsActive) {
7944         if (loggedOn) {
7945           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7946           SendToICS(buf1);
7947         }
7948       } else {
7949         DisplayNote(message + 8);
7950       }
7951       return;
7952     }
7953     if (strncmp(message, "warning", 7) == 0) {
7954         /* Undocumented feature, use tellusererror in new code */
7955         DisplayError(message, 0);
7956         return;
7957     }
7958     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7959         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7960         strcat(realname, " query");
7961         AskQuestion(realname, buf2, buf1, cps->pr);
7962         return;
7963     }
7964     /* Commands from the engine directly to ICS.  We don't allow these to be
7965      *  sent until we are logged on. Crafty kibitzes have been known to
7966      *  interfere with the login process.
7967      */
7968     if (loggedOn) {
7969         if (!strncmp(message, "tellics ", 8)) {
7970             SendToICS(message + 8);
7971             SendToICS("\n");
7972             return;
7973         }
7974         if (!strncmp(message, "tellicsnoalias ", 15)) {
7975             SendToICS(ics_prefix);
7976             SendToICS(message + 15);
7977             SendToICS("\n");
7978             return;
7979         }
7980         /* The following are for backward compatibility only */
7981         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7982             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7983             SendToICS(ics_prefix);
7984             SendToICS(message);
7985             SendToICS("\n");
7986             return;
7987         }
7988     }
7989     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7990         return;
7991     }
7992     /*
7993      * If the move is illegal, cancel it and redraw the board.
7994      * Also deal with other error cases.  Matching is rather loose
7995      * here to accommodate engines written before the spec.
7996      */
7997     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7998         strncmp(message, "Error", 5) == 0) {
7999         if (StrStr(message, "name") ||
8000             StrStr(message, "rating") || StrStr(message, "?") ||
8001             StrStr(message, "result") || StrStr(message, "board") ||
8002             StrStr(message, "bk") || StrStr(message, "computer") ||
8003             StrStr(message, "variant") || StrStr(message, "hint") ||
8004             StrStr(message, "random") || StrStr(message, "depth") ||
8005             StrStr(message, "accepted")) {
8006             return;
8007         }
8008         if (StrStr(message, "protover")) {
8009           /* Program is responding to input, so it's apparently done
8010              initializing, and this error message indicates it is
8011              protocol version 1.  So we don't need to wait any longer
8012              for it to initialize and send feature commands. */
8013           FeatureDone(cps, 1);
8014           cps->protocolVersion = 1;
8015           return;
8016         }
8017         cps->maybeThinking = FALSE;
8018
8019         if (StrStr(message, "draw")) {
8020             /* Program doesn't have "draw" command */
8021             cps->sendDrawOffers = 0;
8022             return;
8023         }
8024         if (cps->sendTime != 1 &&
8025             (StrStr(message, "time") || StrStr(message, "otim"))) {
8026           /* Program apparently doesn't have "time" or "otim" command */
8027           cps->sendTime = 0;
8028           return;
8029         }
8030         if (StrStr(message, "analyze")) {
8031             cps->analysisSupport = FALSE;
8032             cps->analyzing = FALSE;
8033             Reset(FALSE, TRUE);
8034             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8035             DisplayError(buf2, 0);
8036             return;
8037         }
8038         if (StrStr(message, "(no matching move)st")) {
8039           /* Special kludge for GNU Chess 4 only */
8040           cps->stKludge = TRUE;
8041           SendTimeControl(cps, movesPerSession, timeControl,
8042                           timeIncrement, appData.searchDepth,
8043                           searchTime);
8044           return;
8045         }
8046         if (StrStr(message, "(no matching move)sd")) {
8047           /* Special kludge for GNU Chess 4 only */
8048           cps->sdKludge = TRUE;
8049           SendTimeControl(cps, movesPerSession, timeControl,
8050                           timeIncrement, appData.searchDepth,
8051                           searchTime);
8052           return;
8053         }
8054         if (!StrStr(message, "llegal")) {
8055             return;
8056         }
8057         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8058             gameMode == IcsIdle) return;
8059         if (forwardMostMove <= backwardMostMove) return;
8060         if (pausing) PauseEvent();
8061       if(appData.forceIllegal) {
8062             // [HGM] illegal: machine refused move; force position after move into it
8063           SendToProgram("force\n", cps);
8064           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8065                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8066                 // when black is to move, while there might be nothing on a2 or black
8067                 // might already have the move. So send the board as if white has the move.
8068                 // But first we must change the stm of the engine, as it refused the last move
8069                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8070                 if(WhiteOnMove(forwardMostMove)) {
8071                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8072                     SendBoard(cps, forwardMostMove); // kludgeless board
8073                 } else {
8074                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8075                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8076                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8077                 }
8078           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8079             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8080                  gameMode == TwoMachinesPlay)
8081               SendToProgram("go\n", cps);
8082             return;
8083       } else
8084         if (gameMode == PlayFromGameFile) {
8085             /* Stop reading this game file */
8086             gameMode = EditGame;
8087             ModeHighlight();
8088         }
8089         /* [HGM] illegal-move claim should forfeit game when Xboard */
8090         /* only passes fully legal moves                            */
8091         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8092             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8093                                 "False illegal-move claim", GE_XBOARD );
8094             return; // do not take back move we tested as valid
8095         }
8096         currentMove = forwardMostMove-1;
8097         DisplayMove(currentMove-1); /* before DisplayMoveError */
8098         SwitchClocks(forwardMostMove-1); // [HGM] race
8099         DisplayBothClocks();
8100         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8101                 parseList[currentMove], _(cps->which));
8102         DisplayMoveError(buf1);
8103         DrawPosition(FALSE, boards[currentMove]);
8104         return;
8105     }
8106     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8107         /* Program has a broken "time" command that
8108            outputs a string not ending in newline.
8109            Don't use it. */
8110         cps->sendTime = 0;
8111     }
8112
8113     /*
8114      * If chess program startup fails, exit with an error message.
8115      * Attempts to recover here are futile.
8116      */
8117     if ((StrStr(message, "unknown host") != NULL)
8118         || (StrStr(message, "No remote directory") != NULL)
8119         || (StrStr(message, "not found") != NULL)
8120         || (StrStr(message, "No such file") != NULL)
8121         || (StrStr(message, "can't alloc") != NULL)
8122         || (StrStr(message, "Permission denied") != NULL)) {
8123
8124         cps->maybeThinking = FALSE;
8125         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8126                 _(cps->which), cps->program, cps->host, message);
8127         RemoveInputSource(cps->isr);
8128         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8129             if(cps == &first) appData.noChessProgram = TRUE;
8130             DisplayError(buf1, 0);
8131         }
8132         return;
8133     }
8134
8135     /*
8136      * Look for hint output
8137      */
8138     if (sscanf(message, "Hint: %s", buf1) == 1) {
8139         if (cps == &first && hintRequested) {
8140             hintRequested = FALSE;
8141             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8142                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8143                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8144                                     PosFlags(forwardMostMove),
8145                                     fromY, fromX, toY, toX, promoChar, buf1);
8146                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8147                 DisplayInformation(buf2);
8148             } else {
8149                 /* Hint move could not be parsed!? */
8150               snprintf(buf2, sizeof(buf2),
8151                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8152                         buf1, _(cps->which));
8153                 DisplayError(buf2, 0);
8154             }
8155         } else {
8156           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8157         }
8158         return;
8159     }
8160
8161     /*
8162      * Ignore other messages if game is not in progress
8163      */
8164     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8165         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8166
8167     /*
8168      * look for win, lose, draw, or draw offer
8169      */
8170     if (strncmp(message, "1-0", 3) == 0) {
8171         char *p, *q, *r = "";
8172         p = strchr(message, '{');
8173         if (p) {
8174             q = strchr(p, '}');
8175             if (q) {
8176                 *q = NULLCHAR;
8177                 r = p + 1;
8178             }
8179         }
8180         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8181         return;
8182     } else if (strncmp(message, "0-1", 3) == 0) {
8183         char *p, *q, *r = "";
8184         p = strchr(message, '{');
8185         if (p) {
8186             q = strchr(p, '}');
8187             if (q) {
8188                 *q = NULLCHAR;
8189                 r = p + 1;
8190             }
8191         }
8192         /* Kludge for Arasan 4.1 bug */
8193         if (strcmp(r, "Black resigns") == 0) {
8194             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8195             return;
8196         }
8197         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8198         return;
8199     } else if (strncmp(message, "1/2", 3) == 0) {
8200         char *p, *q, *r = "";
8201         p = strchr(message, '{');
8202         if (p) {
8203             q = strchr(p, '}');
8204             if (q) {
8205                 *q = NULLCHAR;
8206                 r = p + 1;
8207             }
8208         }
8209
8210         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8211         return;
8212
8213     } else if (strncmp(message, "White resign", 12) == 0) {
8214         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8215         return;
8216     } else if (strncmp(message, "Black resign", 12) == 0) {
8217         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8218         return;
8219     } else if (strncmp(message, "White matches", 13) == 0 ||
8220                strncmp(message, "Black matches", 13) == 0   ) {
8221         /* [HGM] ignore GNUShogi noises */
8222         return;
8223     } else if (strncmp(message, "White", 5) == 0 &&
8224                message[5] != '(' &&
8225                StrStr(message, "Black") == NULL) {
8226         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8227         return;
8228     } else if (strncmp(message, "Black", 5) == 0 &&
8229                message[5] != '(') {
8230         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8231         return;
8232     } else if (strcmp(message, "resign") == 0 ||
8233                strcmp(message, "computer resigns") == 0) {
8234         switch (gameMode) {
8235           case MachinePlaysBlack:
8236           case IcsPlayingBlack:
8237             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8238             break;
8239           case MachinePlaysWhite:
8240           case IcsPlayingWhite:
8241             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8242             break;
8243           case TwoMachinesPlay:
8244             if (cps->twoMachinesColor[0] == 'w')
8245               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8246             else
8247               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8248             break;
8249           default:
8250             /* can't happen */
8251             break;
8252         }
8253         return;
8254     } else if (strncmp(message, "opponent mates", 14) == 0) {
8255         switch (gameMode) {
8256           case MachinePlaysBlack:
8257           case IcsPlayingBlack:
8258             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8259             break;
8260           case MachinePlaysWhite:
8261           case IcsPlayingWhite:
8262             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8263             break;
8264           case TwoMachinesPlay:
8265             if (cps->twoMachinesColor[0] == 'w')
8266               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8267             else
8268               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8269             break;
8270           default:
8271             /* can't happen */
8272             break;
8273         }
8274         return;
8275     } else if (strncmp(message, "computer mates", 14) == 0) {
8276         switch (gameMode) {
8277           case MachinePlaysBlack:
8278           case IcsPlayingBlack:
8279             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8280             break;
8281           case MachinePlaysWhite:
8282           case IcsPlayingWhite:
8283             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8284             break;
8285           case TwoMachinesPlay:
8286             if (cps->twoMachinesColor[0] == 'w')
8287               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8288             else
8289               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8290             break;
8291           default:
8292             /* can't happen */
8293             break;
8294         }
8295         return;
8296     } else if (strncmp(message, "checkmate", 9) == 0) {
8297         if (WhiteOnMove(forwardMostMove)) {
8298             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8299         } else {
8300             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8301         }
8302         return;
8303     } else if (strstr(message, "Draw") != NULL ||
8304                strstr(message, "game is a draw") != NULL) {
8305         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8306         return;
8307     } else if (strstr(message, "offer") != NULL &&
8308                strstr(message, "draw") != NULL) {
8309 #if ZIPPY
8310         if (appData.zippyPlay && first.initDone) {
8311             /* Relay offer to ICS */
8312             SendToICS(ics_prefix);
8313             SendToICS("draw\n");
8314         }
8315 #endif
8316         cps->offeredDraw = 2; /* valid until this engine moves twice */
8317         if (gameMode == TwoMachinesPlay) {
8318             if (cps->other->offeredDraw) {
8319                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8320             /* [HGM] in two-machine mode we delay relaying draw offer      */
8321             /* until after we also have move, to see if it is really claim */
8322             }
8323         } else if (gameMode == MachinePlaysWhite ||
8324                    gameMode == MachinePlaysBlack) {
8325           if (userOfferedDraw) {
8326             DisplayInformation(_("Machine accepts your draw offer"));
8327             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8328           } else {
8329             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8330           }
8331         }
8332     }
8333
8334
8335     /*
8336      * Look for thinking output
8337      */
8338     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8339           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8340                                 ) {
8341         int plylev, mvleft, mvtot, curscore, time;
8342         char mvname[MOVE_LEN];
8343         u64 nodes; // [DM]
8344         char plyext;
8345         int ignore = FALSE;
8346         int prefixHint = FALSE;
8347         mvname[0] = NULLCHAR;
8348
8349         switch (gameMode) {
8350           case MachinePlaysBlack:
8351           case IcsPlayingBlack:
8352             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8353             break;
8354           case MachinePlaysWhite:
8355           case IcsPlayingWhite:
8356             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8357             break;
8358           case AnalyzeMode:
8359           case AnalyzeFile:
8360             break;
8361           case IcsObserving: /* [DM] icsEngineAnalyze */
8362             if (!appData.icsEngineAnalyze) ignore = TRUE;
8363             break;
8364           case TwoMachinesPlay:
8365             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8366                 ignore = TRUE;
8367             }
8368             break;
8369           default:
8370             ignore = TRUE;
8371             break;
8372         }
8373
8374         if (!ignore) {
8375             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8376             buf1[0] = NULLCHAR;
8377             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8378                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8379
8380                 if (plyext != ' ' && plyext != '\t') {
8381                     time *= 100;
8382                 }
8383
8384                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8385                 if( cps->scoreIsAbsolute &&
8386                     ( gameMode == MachinePlaysBlack ||
8387                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8388                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8389                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8390                      !WhiteOnMove(currentMove)
8391                     ) )
8392                 {
8393                     curscore = -curscore;
8394                 }
8395
8396
8397                 tempStats.depth = plylev;
8398                 tempStats.nodes = nodes;
8399                 tempStats.time = time;
8400                 tempStats.score = curscore;
8401                 tempStats.got_only_move = 0;
8402
8403                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8404                         int ticklen;
8405
8406                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8407                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8408                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8409                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8410                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8411                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8412                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8413                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8414                 }
8415
8416                 /* Buffer overflow protection */
8417                 if (buf1[0] != NULLCHAR) {
8418                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8419                         && appData.debugMode) {
8420                         fprintf(debugFP,
8421                                 "PV is too long; using the first %u bytes.\n",
8422                                 (unsigned) sizeof(tempStats.movelist) - 1);
8423                     }
8424
8425                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8426                 } else {
8427                     sprintf(tempStats.movelist, " no PV\n");
8428                 }
8429
8430                 if (tempStats.seen_stat) {
8431                     tempStats.ok_to_send = 1;
8432                 }
8433
8434                 if (strchr(tempStats.movelist, '(') != NULL) {
8435                     tempStats.line_is_book = 1;
8436                     tempStats.nr_moves = 0;
8437                     tempStats.moves_left = 0;
8438                 } else {
8439                     tempStats.line_is_book = 0;
8440                 }
8441
8442                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8443                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8444
8445                 SendProgramStatsToFrontend( cps, &tempStats );
8446
8447                 /*
8448                     [AS] Protect the thinkOutput buffer from overflow... this
8449                     is only useful if buf1 hasn't overflowed first!
8450                 */
8451                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8452                          plylev,
8453                          (gameMode == TwoMachinesPlay ?
8454                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8455                          ((double) curscore) / 100.0,
8456                          prefixHint ? lastHint : "",
8457                          prefixHint ? " " : "" );
8458
8459                 if( buf1[0] != NULLCHAR ) {
8460                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8461
8462                     if( strlen(buf1) > max_len ) {
8463                         if( appData.debugMode) {
8464                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8465                         }
8466                         buf1[max_len+1] = '\0';
8467                     }
8468
8469                     strcat( thinkOutput, buf1 );
8470                 }
8471
8472                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8473                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8474                     DisplayMove(currentMove - 1);
8475                 }
8476                 return;
8477
8478             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8479                 /* crafty (9.25+) says "(only move) <move>"
8480                  * if there is only 1 legal move
8481                  */
8482                 sscanf(p, "(only move) %s", buf1);
8483                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8484                 sprintf(programStats.movelist, "%s (only move)", buf1);
8485                 programStats.depth = 1;
8486                 programStats.nr_moves = 1;
8487                 programStats.moves_left = 1;
8488                 programStats.nodes = 1;
8489                 programStats.time = 1;
8490                 programStats.got_only_move = 1;
8491
8492                 /* Not really, but we also use this member to
8493                    mean "line isn't going to change" (Crafty
8494                    isn't searching, so stats won't change) */
8495                 programStats.line_is_book = 1;
8496
8497                 SendProgramStatsToFrontend( cps, &programStats );
8498
8499                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8500                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8501                     DisplayMove(currentMove - 1);
8502                 }
8503                 return;
8504             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8505                               &time, &nodes, &plylev, &mvleft,
8506                               &mvtot, mvname) >= 5) {
8507                 /* The stat01: line is from Crafty (9.29+) in response
8508                    to the "." command */
8509                 programStats.seen_stat = 1;
8510                 cps->maybeThinking = TRUE;
8511
8512                 if (programStats.got_only_move || !appData.periodicUpdates)
8513                   return;
8514
8515                 programStats.depth = plylev;
8516                 programStats.time = time;
8517                 programStats.nodes = nodes;
8518                 programStats.moves_left = mvleft;
8519                 programStats.nr_moves = mvtot;
8520                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8521                 programStats.ok_to_send = 1;
8522                 programStats.movelist[0] = '\0';
8523
8524                 SendProgramStatsToFrontend( cps, &programStats );
8525
8526                 return;
8527
8528             } else if (strncmp(message,"++",2) == 0) {
8529                 /* Crafty 9.29+ outputs this */
8530                 programStats.got_fail = 2;
8531                 return;
8532
8533             } else if (strncmp(message,"--",2) == 0) {
8534                 /* Crafty 9.29+ outputs this */
8535                 programStats.got_fail = 1;
8536                 return;
8537
8538             } else if (thinkOutput[0] != NULLCHAR &&
8539                        strncmp(message, "    ", 4) == 0) {
8540                 unsigned message_len;
8541
8542                 p = message;
8543                 while (*p && *p == ' ') p++;
8544
8545                 message_len = strlen( p );
8546
8547                 /* [AS] Avoid buffer overflow */
8548                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8549                     strcat(thinkOutput, " ");
8550                     strcat(thinkOutput, p);
8551                 }
8552
8553                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8554                     strcat(programStats.movelist, " ");
8555                     strcat(programStats.movelist, p);
8556                 }
8557
8558                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8559                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8560                     DisplayMove(currentMove - 1);
8561                 }
8562                 return;
8563             }
8564         }
8565         else {
8566             buf1[0] = NULLCHAR;
8567
8568             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8569                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8570             {
8571                 ChessProgramStats cpstats;
8572
8573                 if (plyext != ' ' && plyext != '\t') {
8574                     time *= 100;
8575                 }
8576
8577                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8578                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8579                     curscore = -curscore;
8580                 }
8581
8582                 cpstats.depth = plylev;
8583                 cpstats.nodes = nodes;
8584                 cpstats.time = time;
8585                 cpstats.score = curscore;
8586                 cpstats.got_only_move = 0;
8587                 cpstats.movelist[0] = '\0';
8588
8589                 if (buf1[0] != NULLCHAR) {
8590                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8591                 }
8592
8593                 cpstats.ok_to_send = 0;
8594                 cpstats.line_is_book = 0;
8595                 cpstats.nr_moves = 0;
8596                 cpstats.moves_left = 0;
8597
8598                 SendProgramStatsToFrontend( cps, &cpstats );
8599             }
8600         }
8601     }
8602 }
8603
8604
8605 /* Parse a game score from the character string "game", and
8606    record it as the history of the current game.  The game
8607    score is NOT assumed to start from the standard position.
8608    The display is not updated in any way.
8609    */
8610 void
8611 ParseGameHistory(game)
8612      char *game;
8613 {
8614     ChessMove moveType;
8615     int fromX, fromY, toX, toY, boardIndex;
8616     char promoChar;
8617     char *p, *q;
8618     char buf[MSG_SIZ];
8619
8620     if (appData.debugMode)
8621       fprintf(debugFP, "Parsing game history: %s\n", game);
8622
8623     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8624     gameInfo.site = StrSave(appData.icsHost);
8625     gameInfo.date = PGNDate();
8626     gameInfo.round = StrSave("-");
8627
8628     /* Parse out names of players */
8629     while (*game == ' ') game++;
8630     p = buf;
8631     while (*game != ' ') *p++ = *game++;
8632     *p = NULLCHAR;
8633     gameInfo.white = StrSave(buf);
8634     while (*game == ' ') game++;
8635     p = buf;
8636     while (*game != ' ' && *game != '\n') *p++ = *game++;
8637     *p = NULLCHAR;
8638     gameInfo.black = StrSave(buf);
8639
8640     /* Parse moves */
8641     boardIndex = blackPlaysFirst ? 1 : 0;
8642     yynewstr(game);
8643     for (;;) {
8644         yyboardindex = boardIndex;
8645         moveType = (ChessMove) Myylex();
8646         switch (moveType) {
8647           case IllegalMove:             /* maybe suicide chess, etc. */
8648   if (appData.debugMode) {
8649     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8650     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8651     setbuf(debugFP, NULL);
8652   }
8653           case WhitePromotion:
8654           case BlackPromotion:
8655           case WhiteNonPromotion:
8656           case BlackNonPromotion:
8657           case NormalMove:
8658           case WhiteCapturesEnPassant:
8659           case BlackCapturesEnPassant:
8660           case WhiteKingSideCastle:
8661           case WhiteQueenSideCastle:
8662           case BlackKingSideCastle:
8663           case BlackQueenSideCastle:
8664           case WhiteKingSideCastleWild:
8665           case WhiteQueenSideCastleWild:
8666           case BlackKingSideCastleWild:
8667           case BlackQueenSideCastleWild:
8668           /* PUSH Fabien */
8669           case WhiteHSideCastleFR:
8670           case WhiteASideCastleFR:
8671           case BlackHSideCastleFR:
8672           case BlackASideCastleFR:
8673           /* POP Fabien */
8674             fromX = currentMoveString[0] - AAA;
8675             fromY = currentMoveString[1] - ONE;
8676             toX = currentMoveString[2] - AAA;
8677             toY = currentMoveString[3] - ONE;
8678             promoChar = currentMoveString[4];
8679             break;
8680           case WhiteDrop:
8681           case BlackDrop:
8682             fromX = moveType == WhiteDrop ?
8683               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8684             (int) CharToPiece(ToLower(currentMoveString[0]));
8685             fromY = DROP_RANK;
8686             toX = currentMoveString[2] - AAA;
8687             toY = currentMoveString[3] - ONE;
8688             promoChar = NULLCHAR;
8689             break;
8690           case AmbiguousMove:
8691             /* bug? */
8692             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8693   if (appData.debugMode) {
8694     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8695     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8696     setbuf(debugFP, NULL);
8697   }
8698             DisplayError(buf, 0);
8699             return;
8700           case ImpossibleMove:
8701             /* bug? */
8702             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8703   if (appData.debugMode) {
8704     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8705     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8706     setbuf(debugFP, NULL);
8707   }
8708             DisplayError(buf, 0);
8709             return;
8710           case EndOfFile:
8711             if (boardIndex < backwardMostMove) {
8712                 /* Oops, gap.  How did that happen? */
8713                 DisplayError(_("Gap in move list"), 0);
8714                 return;
8715             }
8716             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8717             if (boardIndex > forwardMostMove) {
8718                 forwardMostMove = boardIndex;
8719             }
8720             return;
8721           case ElapsedTime:
8722             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8723                 strcat(parseList[boardIndex-1], " ");
8724                 strcat(parseList[boardIndex-1], yy_text);
8725             }
8726             continue;
8727           case Comment:
8728           case PGNTag:
8729           case NAG:
8730           default:
8731             /* ignore */
8732             continue;
8733           case WhiteWins:
8734           case BlackWins:
8735           case GameIsDrawn:
8736           case GameUnfinished:
8737             if (gameMode == IcsExamining) {
8738                 if (boardIndex < backwardMostMove) {
8739                     /* Oops, gap.  How did that happen? */
8740                     return;
8741                 }
8742                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8743                 return;
8744             }
8745             gameInfo.result = moveType;
8746             p = strchr(yy_text, '{');
8747             if (p == NULL) p = strchr(yy_text, '(');
8748             if (p == NULL) {
8749                 p = yy_text;
8750                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8751             } else {
8752                 q = strchr(p, *p == '{' ? '}' : ')');
8753                 if (q != NULL) *q = NULLCHAR;
8754                 p++;
8755             }
8756             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8757             gameInfo.resultDetails = StrSave(p);
8758             continue;
8759         }
8760         if (boardIndex >= forwardMostMove &&
8761             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8762             backwardMostMove = blackPlaysFirst ? 1 : 0;
8763             return;
8764         }
8765         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8766                                  fromY, fromX, toY, toX, promoChar,
8767                                  parseList[boardIndex]);
8768         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8769         /* currentMoveString is set as a side-effect of yylex */
8770         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8771         strcat(moveList[boardIndex], "\n");
8772         boardIndex++;
8773         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8774         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8775           case MT_NONE:
8776           case MT_STALEMATE:
8777           default:
8778             break;
8779           case MT_CHECK:
8780             if(gameInfo.variant != VariantShogi)
8781                 strcat(parseList[boardIndex - 1], "+");
8782             break;
8783           case MT_CHECKMATE:
8784           case MT_STAINMATE:
8785             strcat(parseList[boardIndex - 1], "#");
8786             break;
8787         }
8788     }
8789 }
8790
8791
8792 /* Apply a move to the given board  */
8793 void
8794 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8795      int fromX, fromY, toX, toY;
8796      int promoChar;
8797      Board board;
8798 {
8799   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8800   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8801
8802     /* [HGM] compute & store e.p. status and castling rights for new position */
8803     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8804
8805       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8806       oldEP = (signed char)board[EP_STATUS];
8807       board[EP_STATUS] = EP_NONE;
8808
8809       if( board[toY][toX] != EmptySquare )
8810            board[EP_STATUS] = EP_CAPTURE;
8811
8812   if (fromY == DROP_RANK) {
8813         /* must be first */
8814         piece = board[toY][toX] = (ChessSquare) fromX;
8815   } else {
8816       int i;
8817
8818       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8819            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8820                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8821       } else
8822       if( board[fromY][fromX] == WhitePawn ) {
8823            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8824                board[EP_STATUS] = EP_PAWN_MOVE;
8825            if( toY-fromY==2) {
8826                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8827                         gameInfo.variant != VariantBerolina || toX < fromX)
8828                       board[EP_STATUS] = toX | berolina;
8829                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8830                         gameInfo.variant != VariantBerolina || toX > fromX)
8831                       board[EP_STATUS] = toX;
8832            }
8833       } else
8834       if( board[fromY][fromX] == BlackPawn ) {
8835            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8836                board[EP_STATUS] = EP_PAWN_MOVE;
8837            if( toY-fromY== -2) {
8838                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8839                         gameInfo.variant != VariantBerolina || toX < fromX)
8840                       board[EP_STATUS] = toX | berolina;
8841                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8842                         gameInfo.variant != VariantBerolina || toX > fromX)
8843                       board[EP_STATUS] = toX;
8844            }
8845        }
8846
8847        for(i=0; i<nrCastlingRights; i++) {
8848            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8849               board[CASTLING][i] == toX   && castlingRank[i] == toY
8850              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8851        }
8852
8853      if (fromX == toX && fromY == toY) return;
8854
8855      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8856      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8857      if(gameInfo.variant == VariantKnightmate)
8858          king += (int) WhiteUnicorn - (int) WhiteKing;
8859
8860     /* Code added by Tord: */
8861     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8862     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8863         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8864       board[fromY][fromX] = EmptySquare;
8865       board[toY][toX] = EmptySquare;
8866       if((toX > fromX) != (piece == WhiteRook)) {
8867         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8868       } else {
8869         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8870       }
8871     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8872                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8873       board[fromY][fromX] = EmptySquare;
8874       board[toY][toX] = EmptySquare;
8875       if((toX > fromX) != (piece == BlackRook)) {
8876         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8877       } else {
8878         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8879       }
8880     /* End of code added by Tord */
8881
8882     } else if (board[fromY][fromX] == king
8883         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8884         && toY == fromY && toX > fromX+1) {
8885         board[fromY][fromX] = EmptySquare;
8886         board[toY][toX] = king;
8887         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8888         board[fromY][BOARD_RGHT-1] = EmptySquare;
8889     } else if (board[fromY][fromX] == king
8890         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8891                && toY == fromY && toX < fromX-1) {
8892         board[fromY][fromX] = EmptySquare;
8893         board[toY][toX] = king;
8894         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8895         board[fromY][BOARD_LEFT] = EmptySquare;
8896     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8897                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8898                && toY >= BOARD_HEIGHT-promoRank
8899                ) {
8900         /* white pawn promotion */
8901         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8902         if (board[toY][toX] == EmptySquare) {
8903             board[toY][toX] = WhiteQueen;
8904         }
8905         if(gameInfo.variant==VariantBughouse ||
8906            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8907             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8908         board[fromY][fromX] = EmptySquare;
8909     } else if ((fromY == BOARD_HEIGHT-4)
8910                && (toX != fromX)
8911                && gameInfo.variant != VariantXiangqi
8912                && gameInfo.variant != VariantBerolina
8913                && (board[fromY][fromX] == WhitePawn)
8914                && (board[toY][toX] == EmptySquare)) {
8915         board[fromY][fromX] = EmptySquare;
8916         board[toY][toX] = WhitePawn;
8917         captured = board[toY - 1][toX];
8918         board[toY - 1][toX] = EmptySquare;
8919     } else if ((fromY == BOARD_HEIGHT-4)
8920                && (toX == fromX)
8921                && gameInfo.variant == VariantBerolina
8922                && (board[fromY][fromX] == WhitePawn)
8923                && (board[toY][toX] == EmptySquare)) {
8924         board[fromY][fromX] = EmptySquare;
8925         board[toY][toX] = WhitePawn;
8926         if(oldEP & EP_BEROLIN_A) {
8927                 captured = board[fromY][fromX-1];
8928                 board[fromY][fromX-1] = EmptySquare;
8929         }else{  captured = board[fromY][fromX+1];
8930                 board[fromY][fromX+1] = EmptySquare;
8931         }
8932     } else if (board[fromY][fromX] == king
8933         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8934                && toY == fromY && toX > fromX+1) {
8935         board[fromY][fromX] = EmptySquare;
8936         board[toY][toX] = king;
8937         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8938         board[fromY][BOARD_RGHT-1] = EmptySquare;
8939     } else if (board[fromY][fromX] == king
8940         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8941                && toY == fromY && toX < fromX-1) {
8942         board[fromY][fromX] = EmptySquare;
8943         board[toY][toX] = king;
8944         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8945         board[fromY][BOARD_LEFT] = EmptySquare;
8946     } else if (fromY == 7 && fromX == 3
8947                && board[fromY][fromX] == BlackKing
8948                && toY == 7 && toX == 5) {
8949         board[fromY][fromX] = EmptySquare;
8950         board[toY][toX] = BlackKing;
8951         board[fromY][7] = EmptySquare;
8952         board[toY][4] = BlackRook;
8953     } else if (fromY == 7 && fromX == 3
8954                && board[fromY][fromX] == BlackKing
8955                && toY == 7 && toX == 1) {
8956         board[fromY][fromX] = EmptySquare;
8957         board[toY][toX] = BlackKing;
8958         board[fromY][0] = EmptySquare;
8959         board[toY][2] = BlackRook;
8960     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8961                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8962                && toY < promoRank
8963                ) {
8964         /* black pawn promotion */
8965         board[toY][toX] = CharToPiece(ToLower(promoChar));
8966         if (board[toY][toX] == EmptySquare) {
8967             board[toY][toX] = BlackQueen;
8968         }
8969         if(gameInfo.variant==VariantBughouse ||
8970            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8971             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8972         board[fromY][fromX] = EmptySquare;
8973     } else if ((fromY == 3)
8974                && (toX != fromX)
8975                && gameInfo.variant != VariantXiangqi
8976                && gameInfo.variant != VariantBerolina
8977                && (board[fromY][fromX] == BlackPawn)
8978                && (board[toY][toX] == EmptySquare)) {
8979         board[fromY][fromX] = EmptySquare;
8980         board[toY][toX] = BlackPawn;
8981         captured = board[toY + 1][toX];
8982         board[toY + 1][toX] = EmptySquare;
8983     } else if ((fromY == 3)
8984                && (toX == fromX)
8985                && gameInfo.variant == VariantBerolina
8986                && (board[fromY][fromX] == BlackPawn)
8987                && (board[toY][toX] == EmptySquare)) {
8988         board[fromY][fromX] = EmptySquare;
8989         board[toY][toX] = BlackPawn;
8990         if(oldEP & EP_BEROLIN_A) {
8991                 captured = board[fromY][fromX-1];
8992                 board[fromY][fromX-1] = EmptySquare;
8993         }else{  captured = board[fromY][fromX+1];
8994                 board[fromY][fromX+1] = EmptySquare;
8995         }
8996     } else {
8997         board[toY][toX] = board[fromY][fromX];
8998         board[fromY][fromX] = EmptySquare;
8999     }
9000   }
9001
9002     if (gameInfo.holdingsWidth != 0) {
9003
9004       /* !!A lot more code needs to be written to support holdings  */
9005       /* [HGM] OK, so I have written it. Holdings are stored in the */
9006       /* penultimate board files, so they are automaticlly stored   */
9007       /* in the game history.                                       */
9008       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9009                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9010         /* Delete from holdings, by decreasing count */
9011         /* and erasing image if necessary            */
9012         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9013         if(p < (int) BlackPawn) { /* white drop */
9014              p -= (int)WhitePawn;
9015                  p = PieceToNumber((ChessSquare)p);
9016              if(p >= gameInfo.holdingsSize) p = 0;
9017              if(--board[p][BOARD_WIDTH-2] <= 0)
9018                   board[p][BOARD_WIDTH-1] = EmptySquare;
9019              if((int)board[p][BOARD_WIDTH-2] < 0)
9020                         board[p][BOARD_WIDTH-2] = 0;
9021         } else {                  /* black drop */
9022              p -= (int)BlackPawn;
9023                  p = PieceToNumber((ChessSquare)p);
9024              if(p >= gameInfo.holdingsSize) p = 0;
9025              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9026                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9027              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9028                         board[BOARD_HEIGHT-1-p][1] = 0;
9029         }
9030       }
9031       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9032           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9033         /* [HGM] holdings: Add to holdings, if holdings exist */
9034         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9035                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9036                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9037         }
9038         p = (int) captured;
9039         if (p >= (int) BlackPawn) {
9040           p -= (int)BlackPawn;
9041           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9042                   /* in Shogi restore piece to its original  first */
9043                   captured = (ChessSquare) (DEMOTED captured);
9044                   p = DEMOTED p;
9045           }
9046           p = PieceToNumber((ChessSquare)p);
9047           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9048           board[p][BOARD_WIDTH-2]++;
9049           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9050         } else {
9051           p -= (int)WhitePawn;
9052           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9053                   captured = (ChessSquare) (DEMOTED captured);
9054                   p = DEMOTED p;
9055           }
9056           p = PieceToNumber((ChessSquare)p);
9057           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9058           board[BOARD_HEIGHT-1-p][1]++;
9059           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9060         }
9061       }
9062     } else if (gameInfo.variant == VariantAtomic) {
9063       if (captured != EmptySquare) {
9064         int y, x;
9065         for (y = toY-1; y <= toY+1; y++) {
9066           for (x = toX-1; x <= toX+1; x++) {
9067             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9068                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9069               board[y][x] = EmptySquare;
9070             }
9071           }
9072         }
9073         board[toY][toX] = EmptySquare;
9074       }
9075     }
9076     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9077         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9078     } else
9079     if(promoChar == '+') {
9080         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9081         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9082     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9083         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9084     }
9085     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9086                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9087         // [HGM] superchess: take promotion piece out of holdings
9088         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9089         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9090             if(!--board[k][BOARD_WIDTH-2])
9091                 board[k][BOARD_WIDTH-1] = EmptySquare;
9092         } else {
9093             if(!--board[BOARD_HEIGHT-1-k][1])
9094                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9095         }
9096     }
9097
9098 }
9099
9100 /* Updates forwardMostMove */
9101 void
9102 MakeMove(fromX, fromY, toX, toY, promoChar)
9103      int fromX, fromY, toX, toY;
9104      int promoChar;
9105 {
9106 //    forwardMostMove++; // [HGM] bare: moved downstream
9107
9108     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9109         int timeLeft; static int lastLoadFlag=0; int king, piece;
9110         piece = boards[forwardMostMove][fromY][fromX];
9111         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9112         if(gameInfo.variant == VariantKnightmate)
9113             king += (int) WhiteUnicorn - (int) WhiteKing;
9114         if(forwardMostMove == 0) {
9115             if(blackPlaysFirst)
9116                 fprintf(serverMoves, "%s;", second.tidy);
9117             fprintf(serverMoves, "%s;", first.tidy);
9118             if(!blackPlaysFirst)
9119                 fprintf(serverMoves, "%s;", second.tidy);
9120         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9121         lastLoadFlag = loadFlag;
9122         // print base move
9123         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9124         // print castling suffix
9125         if( toY == fromY && piece == king ) {
9126             if(toX-fromX > 1)
9127                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9128             if(fromX-toX >1)
9129                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9130         }
9131         // e.p. suffix
9132         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9133              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9134              boards[forwardMostMove][toY][toX] == EmptySquare
9135              && fromX != toX && fromY != toY)
9136                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9137         // promotion suffix
9138         if(promoChar != NULLCHAR)
9139                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9140         if(!loadFlag) {
9141             fprintf(serverMoves, "/%d/%d",
9142                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9143             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9144             else                      timeLeft = blackTimeRemaining/1000;
9145             fprintf(serverMoves, "/%d", timeLeft);
9146         }
9147         fflush(serverMoves);
9148     }
9149
9150     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9151       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9152                         0, 1);
9153       return;
9154     }
9155     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9156     if (commentList[forwardMostMove+1] != NULL) {
9157         free(commentList[forwardMostMove+1]);
9158         commentList[forwardMostMove+1] = NULL;
9159     }
9160     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9161     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9162     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9163     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9164     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9165     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9166     gameInfo.result = GameUnfinished;
9167     if (gameInfo.resultDetails != NULL) {
9168         free(gameInfo.resultDetails);
9169         gameInfo.resultDetails = NULL;
9170     }
9171     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9172                               moveList[forwardMostMove - 1]);
9173     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9174                              PosFlags(forwardMostMove - 1),
9175                              fromY, fromX, toY, toX, promoChar,
9176                              parseList[forwardMostMove - 1]);
9177     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9178       case MT_NONE:
9179       case MT_STALEMATE:
9180       default:
9181         break;
9182       case MT_CHECK:
9183         if(gameInfo.variant != VariantShogi)
9184             strcat(parseList[forwardMostMove - 1], "+");
9185         break;
9186       case MT_CHECKMATE:
9187       case MT_STAINMATE:
9188         strcat(parseList[forwardMostMove - 1], "#");
9189         break;
9190     }
9191     if (appData.debugMode) {
9192         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9193     }
9194
9195 }
9196
9197 /* Updates currentMove if not pausing */
9198 void
9199 ShowMove(fromX, fromY, toX, toY)
9200 {
9201     int instant = (gameMode == PlayFromGameFile) ?
9202         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9203     if(appData.noGUI) return;
9204     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9205         if (!instant) {
9206             if (forwardMostMove == currentMove + 1) {
9207                 AnimateMove(boards[forwardMostMove - 1],
9208                             fromX, fromY, toX, toY);
9209             }
9210             if (appData.highlightLastMove) {
9211                 SetHighlights(fromX, fromY, toX, toY);
9212             }
9213         }
9214         currentMove = forwardMostMove;
9215     }
9216
9217     if (instant) return;
9218
9219     DisplayMove(currentMove - 1);
9220     DrawPosition(FALSE, boards[currentMove]);
9221     DisplayBothClocks();
9222     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9223 }
9224
9225 void SendEgtPath(ChessProgramState *cps)
9226 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9227         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9228
9229         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9230
9231         while(*p) {
9232             char c, *q = name+1, *r, *s;
9233
9234             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9235             while(*p && *p != ',') *q++ = *p++;
9236             *q++ = ':'; *q = 0;
9237             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9238                 strcmp(name, ",nalimov:") == 0 ) {
9239                 // take nalimov path from the menu-changeable option first, if it is defined
9240               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9241                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9242             } else
9243             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9244                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9245                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9246                 s = r = StrStr(s, ":") + 1; // beginning of path info
9247                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9248                 c = *r; *r = 0;             // temporarily null-terminate path info
9249                     *--q = 0;               // strip of trailig ':' from name
9250                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9251                 *r = c;
9252                 SendToProgram(buf,cps);     // send egtbpath command for this format
9253             }
9254             if(*p == ',') p++; // read away comma to position for next format name
9255         }
9256 }
9257
9258 void
9259 InitChessProgram(cps, setup)
9260      ChessProgramState *cps;
9261      int setup; /* [HGM] needed to setup FRC opening position */
9262 {
9263     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9264     if (appData.noChessProgram) return;
9265     hintRequested = FALSE;
9266     bookRequested = FALSE;
9267
9268     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9269     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9270     if(cps->memSize) { /* [HGM] memory */
9271       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9272         SendToProgram(buf, cps);
9273     }
9274     SendEgtPath(cps); /* [HGM] EGT */
9275     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9276       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9277         SendToProgram(buf, cps);
9278     }
9279
9280     SendToProgram(cps->initString, cps);
9281     if (gameInfo.variant != VariantNormal &&
9282         gameInfo.variant != VariantLoadable
9283         /* [HGM] also send variant if board size non-standard */
9284         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9285                                             ) {
9286       char *v = VariantName(gameInfo.variant);
9287       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9288         /* [HGM] in protocol 1 we have to assume all variants valid */
9289         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9290         DisplayFatalError(buf, 0, 1);
9291         return;
9292       }
9293
9294       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9295       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9296       if( gameInfo.variant == VariantXiangqi )
9297            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9298       if( gameInfo.variant == VariantShogi )
9299            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9300       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9301            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9302       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9303           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9304            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9305       if( gameInfo.variant == VariantCourier )
9306            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9307       if( gameInfo.variant == VariantSuper )
9308            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9309       if( gameInfo.variant == VariantGreat )
9310            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9311       if( gameInfo.variant == VariantSChess )
9312            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9313
9314       if(overruled) {
9315         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9316                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9317            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9318            if(StrStr(cps->variants, b) == NULL) {
9319                // specific sized variant not known, check if general sizing allowed
9320                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9321                    if(StrStr(cps->variants, "boardsize") == NULL) {
9322                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9323                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9324                        DisplayFatalError(buf, 0, 1);
9325                        return;
9326                    }
9327                    /* [HGM] here we really should compare with the maximum supported board size */
9328                }
9329            }
9330       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9331       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9332       SendToProgram(buf, cps);
9333     }
9334     currentlyInitializedVariant = gameInfo.variant;
9335
9336     /* [HGM] send opening position in FRC to first engine */
9337     if(setup) {
9338           SendToProgram("force\n", cps);
9339           SendBoard(cps, 0);
9340           /* engine is now in force mode! Set flag to wake it up after first move. */
9341           setboardSpoiledMachineBlack = 1;
9342     }
9343
9344     if (cps->sendICS) {
9345       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9346       SendToProgram(buf, cps);
9347     }
9348     cps->maybeThinking = FALSE;
9349     cps->offeredDraw = 0;
9350     if (!appData.icsActive) {
9351         SendTimeControl(cps, movesPerSession, timeControl,
9352                         timeIncrement, appData.searchDepth,
9353                         searchTime);
9354     }
9355     if (appData.showThinking
9356         // [HGM] thinking: four options require thinking output to be sent
9357         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9358                                 ) {
9359         SendToProgram("post\n", cps);
9360     }
9361     SendToProgram("hard\n", cps);
9362     if (!appData.ponderNextMove) {
9363         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9364            it without being sure what state we are in first.  "hard"
9365            is not a toggle, so that one is OK.
9366          */
9367         SendToProgram("easy\n", cps);
9368     }
9369     if (cps->usePing) {
9370       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9371       SendToProgram(buf, cps);
9372     }
9373     cps->initDone = TRUE;
9374 }
9375
9376
9377 void
9378 StartChessProgram(cps)
9379      ChessProgramState *cps;
9380 {
9381     char buf[MSG_SIZ];
9382     int err;
9383
9384     if (appData.noChessProgram) return;
9385     cps->initDone = FALSE;
9386
9387     if (strcmp(cps->host, "localhost") == 0) {
9388         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9389     } else if (*appData.remoteShell == NULLCHAR) {
9390         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9391     } else {
9392         if (*appData.remoteUser == NULLCHAR) {
9393           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9394                     cps->program);
9395         } else {
9396           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9397                     cps->host, appData.remoteUser, cps->program);
9398         }
9399         err = StartChildProcess(buf, "", &cps->pr);
9400     }
9401
9402     if (err != 0) {
9403       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9404         DisplayFatalError(buf, err, 1);
9405         cps->pr = NoProc;
9406         cps->isr = NULL;
9407         return;
9408     }
9409
9410     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9411     if (cps->protocolVersion > 1) {
9412       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9413       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9414       cps->comboCnt = 0;  //                and values of combo boxes
9415       SendToProgram(buf, cps);
9416     } else {
9417       SendToProgram("xboard\n", cps);
9418     }
9419 }
9420
9421 void
9422 TwoMachinesEventIfReady P((void))
9423 {
9424   static int curMess = 0;
9425   if (first.lastPing != first.lastPong) {
9426     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9427     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9428     return;
9429   }
9430   if (second.lastPing != second.lastPong) {
9431     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9432     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9433     return;
9434   }
9435   DisplayMessage("", ""); curMess = 0;
9436   ThawUI();
9437   TwoMachinesEvent();
9438 }
9439
9440 int
9441 CreateTourney(char *name)
9442 {
9443         FILE *f;
9444         if(name[0] == NULLCHAR) return 0;
9445         f = fopen(appData.tourneyFile, "r");
9446         if(f) { // file exists
9447             ParseArgsFromFile(f); // parse it
9448         } else {
9449             f = fopen(appData.tourneyFile, "w");
9450             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9451                 // create a file with tournament description
9452                 fprintf(f, "-participants {%s}\n", appData.participants);
9453                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9454                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9455                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9456                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9457                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9458                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9459                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9460                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9461                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9462                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9463                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9464                 fprintf(f, "-results \"\"\n");
9465             }
9466         }
9467         fclose(f);
9468         appData.noChessProgram = FALSE;
9469         appData.clockMode = TRUE;
9470         SetGNUMode();
9471         return 1;
9472 }
9473
9474 #define MAXENGINES 1000
9475 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9476
9477 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9478 {
9479     char buf[MSG_SIZ], *p, *q;
9480     int i=1;
9481     while(*names) {
9482         p = names; q = buf;
9483         while(*p && *p != '\n') *q++ = *p++;
9484         *q = 0;
9485         if(engineList[i]) free(engineList[i]);
9486         engineList[i] = strdup(buf);
9487         if(*p == '\n') p++;
9488         TidyProgramName(engineList[i], "localhost", buf);
9489         if(engineMnemonic[i]) free(engineMnemonic[i]);
9490         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9491             strcat(buf, " (");
9492             sscanf(q + 8, "%s", buf + strlen(buf));
9493             strcat(buf, ")");
9494         }
9495         engineMnemonic[i] = strdup(buf);
9496         names = p; i++;
9497       if(i > MAXENGINES - 2) break;
9498     }
9499     engineList[i] = NULL;
9500 }
9501
9502 // following implemented as macro to avoid type limitations
9503 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9504
9505 void SwapEngines(int n)
9506 {   // swap settings for first engine and other engine (so far only some selected options)
9507     int h;
9508     char *p;
9509     if(n == 0) return;
9510     SWAP(directory, p)
9511     SWAP(chessProgram, p)
9512     SWAP(isUCI, h)
9513     SWAP(hasOwnBookUCI, h)
9514     SWAP(protocolVersion, h)
9515     SWAP(reuse, h)
9516     SWAP(scoreIsAbsolute, h)
9517     SWAP(timeOdds, h)
9518     SWAP(logo, p)
9519 }
9520
9521 void
9522 SetPlayer(int player)
9523 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9524     int i;
9525     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9526     static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9527                                  "-firstNeedsNoncompliantFEN false -firstNPS -1";
9528     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9529     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9530     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9531     if(mnemonic[i]) {
9532         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9533         ParseArgsFromString(resetOptions);
9534         ParseArgsFromString(buf);
9535     }
9536     free(engineName);
9537 }
9538
9539 int
9540 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9541 {   // determine players from game number
9542     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9543
9544     if(appData.tourneyType == 0) {
9545         roundsPerCycle = (nPlayers - 1) | 1;
9546         pairingsPerRound = nPlayers / 2;
9547     } else if(appData.tourneyType > 0) {
9548         roundsPerCycle = nPlayers - appData.tourneyType;
9549         pairingsPerRound = appData.tourneyType;
9550     }
9551     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9552     gamesPerCycle = gamesPerRound * roundsPerCycle;
9553     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9554     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9555     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9556     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9557     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9558     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9559
9560     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9561     if(appData.roundSync) *syncInterval = gamesPerRound;
9562
9563     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9564
9565     if(appData.tourneyType == 0) {
9566         if(curPairing == (nPlayers-1)/2 ) {
9567             *whitePlayer = curRound;
9568             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9569         } else {
9570             *whitePlayer = curRound - pairingsPerRound + curPairing;
9571             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9572             *blackPlayer = curRound + pairingsPerRound - curPairing;
9573             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9574         }
9575     } else if(appData.tourneyType > 0) {
9576         *whitePlayer = curPairing;
9577         *blackPlayer = curRound + appData.tourneyType;
9578     }
9579
9580     // take care of white/black alternation per round. 
9581     // For cycles and games this is already taken care of by default, derived from matchGame!
9582     return curRound & 1;
9583 }
9584
9585 int
9586 NextTourneyGame(int nr, int *swapColors)
9587 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9588     char *p, *q;
9589     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9590     FILE *tf;
9591     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9592     tf = fopen(appData.tourneyFile, "r");
9593     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9594     ParseArgsFromFile(tf); fclose(tf);
9595
9596     p = appData.participants;
9597     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9598     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9599
9600     if(syncInterval) {
9601         p = q = appData.results;
9602         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9603         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9604             DisplayMessage(_("Waiting for other game(s)"),"");
9605             waitingForGame = TRUE;
9606             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9607             return 0;
9608         }
9609         waitingForGame = FALSE;
9610     }
9611
9612     if(first.pr != NoProc) return 1; // engines already loaded
9613
9614     // redefine engines, engine dir, etc.
9615     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9616     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9617     SwapEngines(1);
9618     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9619     SwapEngines(1);         // and make that valid for second engine by swapping
9620     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9621     InitEngine(&second, 1);
9622     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9623     return 1;
9624 }
9625
9626 void
9627 NextMatchGame()
9628 {   // performs game initialization that does not invoke engines, and then tries to start the game
9629     int firstWhite, swapColors = 0;
9630     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9631     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9632     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9633     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9634     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9635     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9636     Reset(FALSE, first.pr != NoProc);
9637     appData.noChessProgram = FALSE;
9638     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9639     TwoMachinesEvent();
9640 }
9641
9642 void UserAdjudicationEvent( int result )
9643 {
9644     ChessMove gameResult = GameIsDrawn;
9645
9646     if( result > 0 ) {
9647         gameResult = WhiteWins;
9648     }
9649     else if( result < 0 ) {
9650         gameResult = BlackWins;
9651     }
9652
9653     if( gameMode == TwoMachinesPlay ) {
9654         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9655     }
9656 }
9657
9658
9659 // [HGM] save: calculate checksum of game to make games easily identifiable
9660 int StringCheckSum(char *s)
9661 {
9662         int i = 0;
9663         if(s==NULL) return 0;
9664         while(*s) i = i*259 + *s++;
9665         return i;
9666 }
9667
9668 int GameCheckSum()
9669 {
9670         int i, sum=0;
9671         for(i=backwardMostMove; i<forwardMostMove; i++) {
9672                 sum += pvInfoList[i].depth;
9673                 sum += StringCheckSum(parseList[i]);
9674                 sum += StringCheckSum(commentList[i]);
9675                 sum *= 261;
9676         }
9677         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9678         return sum + StringCheckSum(commentList[i]);
9679 } // end of save patch
9680
9681 void
9682 GameEnds(result, resultDetails, whosays)
9683      ChessMove result;
9684      char *resultDetails;
9685      int whosays;
9686 {
9687     GameMode nextGameMode;
9688     int isIcsGame;
9689     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9690
9691     if(endingGame) return; /* [HGM] crash: forbid recursion */
9692     endingGame = 1;
9693     if(twoBoards) { // [HGM] dual: switch back to one board
9694         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9695         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9696     }
9697     if (appData.debugMode) {
9698       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9699               result, resultDetails ? resultDetails : "(null)", whosays);
9700     }
9701
9702     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9703
9704     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9705         /* If we are playing on ICS, the server decides when the
9706            game is over, but the engine can offer to draw, claim
9707            a draw, or resign.
9708          */
9709 #if ZIPPY
9710         if (appData.zippyPlay && first.initDone) {
9711             if (result == GameIsDrawn) {
9712                 /* In case draw still needs to be claimed */
9713                 SendToICS(ics_prefix);
9714                 SendToICS("draw\n");
9715             } else if (StrCaseStr(resultDetails, "resign")) {
9716                 SendToICS(ics_prefix);
9717                 SendToICS("resign\n");
9718             }
9719         }
9720 #endif
9721         endingGame = 0; /* [HGM] crash */
9722         return;
9723     }
9724
9725     /* If we're loading the game from a file, stop */
9726     if (whosays == GE_FILE) {
9727       (void) StopLoadGameTimer();
9728       gameFileFP = NULL;
9729     }
9730
9731     /* Cancel draw offers */
9732     first.offeredDraw = second.offeredDraw = 0;
9733
9734     /* If this is an ICS game, only ICS can really say it's done;
9735        if not, anyone can. */
9736     isIcsGame = (gameMode == IcsPlayingWhite ||
9737                  gameMode == IcsPlayingBlack ||
9738                  gameMode == IcsObserving    ||
9739                  gameMode == IcsExamining);
9740
9741     if (!isIcsGame || whosays == GE_ICS) {
9742         /* OK -- not an ICS game, or ICS said it was done */
9743         StopClocks();
9744         if (!isIcsGame && !appData.noChessProgram)
9745           SetUserThinkingEnables();
9746
9747         /* [HGM] if a machine claims the game end we verify this claim */
9748         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9749             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9750                 char claimer;
9751                 ChessMove trueResult = (ChessMove) -1;
9752
9753                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9754                                             first.twoMachinesColor[0] :
9755                                             second.twoMachinesColor[0] ;
9756
9757                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9758                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9759                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9760                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9761                 } else
9762                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9763                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9764                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9765                 } else
9766                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9767                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9768                 }
9769
9770                 // now verify win claims, but not in drop games, as we don't understand those yet
9771                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9772                                                  || gameInfo.variant == VariantGreat) &&
9773                     (result == WhiteWins && claimer == 'w' ||
9774                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9775                       if (appData.debugMode) {
9776                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9777                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9778                       }
9779                       if(result != trueResult) {
9780                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9781                               result = claimer == 'w' ? BlackWins : WhiteWins;
9782                               resultDetails = buf;
9783                       }
9784                 } else
9785                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9786                     && (forwardMostMove <= backwardMostMove ||
9787                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9788                         (claimer=='b')==(forwardMostMove&1))
9789                                                                                   ) {
9790                       /* [HGM] verify: draws that were not flagged are false claims */
9791                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9792                       result = claimer == 'w' ? BlackWins : WhiteWins;
9793                       resultDetails = buf;
9794                 }
9795                 /* (Claiming a loss is accepted no questions asked!) */
9796             }
9797             /* [HGM] bare: don't allow bare King to win */
9798             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9799                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9800                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9801                && result != GameIsDrawn)
9802             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9803                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9804                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9805                         if(p >= 0 && p <= (int)WhiteKing) k++;
9806                 }
9807                 if (appData.debugMode) {
9808                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9809                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9810                 }
9811                 if(k <= 1) {
9812                         result = GameIsDrawn;
9813                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9814                         resultDetails = buf;
9815                 }
9816             }
9817         }
9818
9819
9820         if(serverMoves != NULL && !loadFlag) { char c = '=';
9821             if(result==WhiteWins) c = '+';
9822             if(result==BlackWins) c = '-';
9823             if(resultDetails != NULL)
9824                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9825         }
9826         if (resultDetails != NULL) {
9827             gameInfo.result = result;
9828             gameInfo.resultDetails = StrSave(resultDetails);
9829
9830             /* display last move only if game was not loaded from file */
9831             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9832                 DisplayMove(currentMove - 1);
9833
9834             if (forwardMostMove != 0) {
9835                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9836                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9837                                                                 ) {
9838                     if (*appData.saveGameFile != NULLCHAR) {
9839                         SaveGameToFile(appData.saveGameFile, TRUE);
9840                     } else if (appData.autoSaveGames) {
9841                         AutoSaveGame();
9842                     }
9843                     if (*appData.savePositionFile != NULLCHAR) {
9844                         SavePositionToFile(appData.savePositionFile);
9845                     }
9846                 }
9847             }
9848
9849             /* Tell program how game ended in case it is learning */
9850             /* [HGM] Moved this to after saving the PGN, just in case */
9851             /* engine died and we got here through time loss. In that */
9852             /* case we will get a fatal error writing the pipe, which */
9853             /* would otherwise lose us the PGN.                       */
9854             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9855             /* output during GameEnds should never be fatal anymore   */
9856             if (gameMode == MachinePlaysWhite ||
9857                 gameMode == MachinePlaysBlack ||
9858                 gameMode == TwoMachinesPlay ||
9859                 gameMode == IcsPlayingWhite ||
9860                 gameMode == IcsPlayingBlack ||
9861                 gameMode == BeginningOfGame) {
9862                 char buf[MSG_SIZ];
9863                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9864                         resultDetails);
9865                 if (first.pr != NoProc) {
9866                     SendToProgram(buf, &first);
9867                 }
9868                 if (second.pr != NoProc &&
9869                     gameMode == TwoMachinesPlay) {
9870                     SendToProgram(buf, &second);
9871                 }
9872             }
9873         }
9874
9875         if (appData.icsActive) {
9876             if (appData.quietPlay &&
9877                 (gameMode == IcsPlayingWhite ||
9878                  gameMode == IcsPlayingBlack)) {
9879                 SendToICS(ics_prefix);
9880                 SendToICS("set shout 1\n");
9881             }
9882             nextGameMode = IcsIdle;
9883             ics_user_moved = FALSE;
9884             /* clean up premove.  It's ugly when the game has ended and the
9885              * premove highlights are still on the board.
9886              */
9887             if (gotPremove) {
9888               gotPremove = FALSE;
9889               ClearPremoveHighlights();
9890               DrawPosition(FALSE, boards[currentMove]);
9891             }
9892             if (whosays == GE_ICS) {
9893                 switch (result) {
9894                 case WhiteWins:
9895                     if (gameMode == IcsPlayingWhite)
9896                         PlayIcsWinSound();
9897                     else if(gameMode == IcsPlayingBlack)
9898                         PlayIcsLossSound();
9899                     break;
9900                 case BlackWins:
9901                     if (gameMode == IcsPlayingBlack)
9902                         PlayIcsWinSound();
9903                     else if(gameMode == IcsPlayingWhite)
9904                         PlayIcsLossSound();
9905                     break;
9906                 case GameIsDrawn:
9907                     PlayIcsDrawSound();
9908                     break;
9909                 default:
9910                     PlayIcsUnfinishedSound();
9911                 }
9912             }
9913         } else if (gameMode == EditGame ||
9914                    gameMode == PlayFromGameFile ||
9915                    gameMode == AnalyzeMode ||
9916                    gameMode == AnalyzeFile) {
9917             nextGameMode = gameMode;
9918         } else {
9919             nextGameMode = EndOfGame;
9920         }
9921         pausing = FALSE;
9922         ModeHighlight();
9923     } else {
9924         nextGameMode = gameMode;
9925     }
9926
9927     if (appData.noChessProgram) {
9928         gameMode = nextGameMode;
9929         ModeHighlight();
9930         endingGame = 0; /* [HGM] crash */
9931         return;
9932     }
9933
9934     if (first.reuse) {
9935         /* Put first chess program into idle state */
9936         if (first.pr != NoProc &&
9937             (gameMode == MachinePlaysWhite ||
9938              gameMode == MachinePlaysBlack ||
9939              gameMode == TwoMachinesPlay ||
9940              gameMode == IcsPlayingWhite ||
9941              gameMode == IcsPlayingBlack ||
9942              gameMode == BeginningOfGame)) {
9943             SendToProgram("force\n", &first);
9944             if (first.usePing) {
9945               char buf[MSG_SIZ];
9946               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9947               SendToProgram(buf, &first);
9948             }
9949         }
9950     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9951         /* Kill off first chess program */
9952         if (first.isr != NULL)
9953           RemoveInputSource(first.isr);
9954         first.isr = NULL;
9955
9956         if (first.pr != NoProc) {
9957             ExitAnalyzeMode();
9958             DoSleep( appData.delayBeforeQuit );
9959             SendToProgram("quit\n", &first);
9960             DoSleep( appData.delayAfterQuit );
9961             DestroyChildProcess(first.pr, first.useSigterm);
9962         }
9963         first.pr = NoProc;
9964     }
9965     if (second.reuse) {
9966         /* Put second chess program into idle state */
9967         if (second.pr != NoProc &&
9968             gameMode == TwoMachinesPlay) {
9969             SendToProgram("force\n", &second);
9970             if (second.usePing) {
9971               char buf[MSG_SIZ];
9972               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9973               SendToProgram(buf, &second);
9974             }
9975         }
9976     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9977         /* Kill off second chess program */
9978         if (second.isr != NULL)
9979           RemoveInputSource(second.isr);
9980         second.isr = NULL;
9981
9982         if (second.pr != NoProc) {
9983             DoSleep( appData.delayBeforeQuit );
9984             SendToProgram("quit\n", &second);
9985             DoSleep( appData.delayAfterQuit );
9986             DestroyChildProcess(second.pr, second.useSigterm);
9987         }
9988         second.pr = NoProc;
9989     }
9990
9991     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
9992         char resChar = '=';
9993         switch (result) {
9994         case WhiteWins:
9995           resChar = '+';
9996           if (first.twoMachinesColor[0] == 'w') {
9997             first.matchWins++;
9998           } else {
9999             second.matchWins++;
10000           }
10001           break;
10002         case BlackWins:
10003           resChar = '-';
10004           if (first.twoMachinesColor[0] == 'b') {
10005             first.matchWins++;
10006           } else {
10007             second.matchWins++;
10008           }
10009           break;
10010         case GameUnfinished:
10011           resChar = ' ';
10012         default:
10013           break;
10014         }
10015
10016         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10017         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10018             ReserveGame(nextGame, resChar); // sets nextGame
10019             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10020         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10021
10022         if (nextGame <= appData.matchGames) {
10023             gameMode = nextGameMode;
10024             matchGame = nextGame; // this will be overruled in tourney mode!
10025             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10026             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10027             endingGame = 0; /* [HGM] crash */
10028             return;
10029         } else {
10030             gameMode = nextGameMode;
10031             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10032                      first.tidy, second.tidy,
10033                      first.matchWins, second.matchWins,
10034                      appData.matchGames - (first.matchWins + second.matchWins));
10035             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10036             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10037                 first.twoMachinesColor = "black\n";
10038                 second.twoMachinesColor = "white\n";
10039             } else {
10040                 first.twoMachinesColor = "white\n";
10041                 second.twoMachinesColor = "black\n";
10042             }
10043         }
10044     }
10045     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10046         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10047       ExitAnalyzeMode();
10048     gameMode = nextGameMode;
10049     ModeHighlight();
10050     endingGame = 0;  /* [HGM] crash */
10051     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10052       if(matchMode == TRUE) DisplayFatalError(ranking ? ranking : buf, 0, 0); else {
10053         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10054         DisplayNote(ranking ? ranking : buf);
10055       }
10056       if(ranking) free(ranking);
10057     }
10058 }
10059
10060 /* Assumes program was just initialized (initString sent).
10061    Leaves program in force mode. */
10062 void
10063 FeedMovesToProgram(cps, upto)
10064      ChessProgramState *cps;
10065      int upto;
10066 {
10067     int i;
10068
10069     if (appData.debugMode)
10070       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10071               startedFromSetupPosition ? "position and " : "",
10072               backwardMostMove, upto, cps->which);
10073     if(currentlyInitializedVariant != gameInfo.variant) {
10074       char buf[MSG_SIZ];
10075         // [HGM] variantswitch: make engine aware of new variant
10076         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10077                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10078         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10079         SendToProgram(buf, cps);
10080         currentlyInitializedVariant = gameInfo.variant;
10081     }
10082     SendToProgram("force\n", cps);
10083     if (startedFromSetupPosition) {
10084         SendBoard(cps, backwardMostMove);
10085     if (appData.debugMode) {
10086         fprintf(debugFP, "feedMoves\n");
10087     }
10088     }
10089     for (i = backwardMostMove; i < upto; i++) {
10090         SendMoveToProgram(i, cps);
10091     }
10092 }
10093
10094
10095 int
10096 ResurrectChessProgram()
10097 {
10098      /* The chess program may have exited.
10099         If so, restart it and feed it all the moves made so far. */
10100     static int doInit = 0;
10101
10102     if (appData.noChessProgram) return 1;
10103
10104     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10105         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10106         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10107         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10108     } else {
10109         if (first.pr != NoProc) return 1;
10110         StartChessProgram(&first);
10111     }
10112     InitChessProgram(&first, FALSE);
10113     FeedMovesToProgram(&first, currentMove);
10114
10115     if (!first.sendTime) {
10116         /* can't tell gnuchess what its clock should read,
10117            so we bow to its notion. */
10118         ResetClocks();
10119         timeRemaining[0][currentMove] = whiteTimeRemaining;
10120         timeRemaining[1][currentMove] = blackTimeRemaining;
10121     }
10122
10123     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10124                 appData.icsEngineAnalyze) && first.analysisSupport) {
10125       SendToProgram("analyze\n", &first);
10126       first.analyzing = TRUE;
10127     }
10128     return 1;
10129 }
10130
10131 /*
10132  * Button procedures
10133  */
10134 void
10135 Reset(redraw, init)
10136      int redraw, init;
10137 {
10138     int i;
10139
10140     if (appData.debugMode) {
10141         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10142                 redraw, init, gameMode);
10143     }
10144     CleanupTail(); // [HGM] vari: delete any stored variations
10145     pausing = pauseExamInvalid = FALSE;
10146     startedFromSetupPosition = blackPlaysFirst = FALSE;
10147     firstMove = TRUE;
10148     whiteFlag = blackFlag = FALSE;
10149     userOfferedDraw = FALSE;
10150     hintRequested = bookRequested = FALSE;
10151     first.maybeThinking = FALSE;
10152     second.maybeThinking = FALSE;
10153     first.bookSuspend = FALSE; // [HGM] book
10154     second.bookSuspend = FALSE;
10155     thinkOutput[0] = NULLCHAR;
10156     lastHint[0] = NULLCHAR;
10157     ClearGameInfo(&gameInfo);
10158     gameInfo.variant = StringToVariant(appData.variant);
10159     ics_user_moved = ics_clock_paused = FALSE;
10160     ics_getting_history = H_FALSE;
10161     ics_gamenum = -1;
10162     white_holding[0] = black_holding[0] = NULLCHAR;
10163     ClearProgramStats();
10164     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10165
10166     ResetFrontEnd();
10167     ClearHighlights();
10168     flipView = appData.flipView;
10169     ClearPremoveHighlights();
10170     gotPremove = FALSE;
10171     alarmSounded = FALSE;
10172
10173     GameEnds(EndOfFile, NULL, GE_PLAYER);
10174     if(appData.serverMovesName != NULL) {
10175         /* [HGM] prepare to make moves file for broadcasting */
10176         clock_t t = clock();
10177         if(serverMoves != NULL) fclose(serverMoves);
10178         serverMoves = fopen(appData.serverMovesName, "r");
10179         if(serverMoves != NULL) {
10180             fclose(serverMoves);
10181             /* delay 15 sec before overwriting, so all clients can see end */
10182             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10183         }
10184         serverMoves = fopen(appData.serverMovesName, "w");
10185     }
10186
10187     ExitAnalyzeMode();
10188     gameMode = BeginningOfGame;
10189     ModeHighlight();
10190     if(appData.icsActive) gameInfo.variant = VariantNormal;
10191     currentMove = forwardMostMove = backwardMostMove = 0;
10192     InitPosition(redraw);
10193     for (i = 0; i < MAX_MOVES; i++) {
10194         if (commentList[i] != NULL) {
10195             free(commentList[i]);
10196             commentList[i] = NULL;
10197         }
10198     }
10199     ResetClocks();
10200     timeRemaining[0][0] = whiteTimeRemaining;
10201     timeRemaining[1][0] = blackTimeRemaining;
10202
10203     if (first.pr == NULL) {
10204         StartChessProgram(&first);
10205     }
10206     if (init) {
10207             InitChessProgram(&first, startedFromSetupPosition);
10208     }
10209     DisplayTitle("");
10210     DisplayMessage("", "");
10211     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10212     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10213 }
10214
10215 void
10216 AutoPlayGameLoop()
10217 {
10218     for (;;) {
10219         if (!AutoPlayOneMove())
10220           return;
10221         if (matchMode || appData.timeDelay == 0)
10222           continue;
10223         if (appData.timeDelay < 0)
10224           return;
10225         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10226         break;
10227     }
10228 }
10229
10230
10231 int
10232 AutoPlayOneMove()
10233 {
10234     int fromX, fromY, toX, toY;
10235
10236     if (appData.debugMode) {
10237       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10238     }
10239
10240     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10241       return FALSE;
10242
10243     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10244       pvInfoList[currentMove].depth = programStats.depth;
10245       pvInfoList[currentMove].score = programStats.score;
10246       pvInfoList[currentMove].time  = 0;
10247       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10248     }
10249
10250     if (currentMove >= forwardMostMove) {
10251       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10252       gameMode = EditGame;
10253       ModeHighlight();
10254
10255       /* [AS] Clear current move marker at the end of a game */
10256       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10257
10258       return FALSE;
10259     }
10260
10261     toX = moveList[currentMove][2] - AAA;
10262     toY = moveList[currentMove][3] - ONE;
10263
10264     if (moveList[currentMove][1] == '@') {
10265         if (appData.highlightLastMove) {
10266             SetHighlights(-1, -1, toX, toY);
10267         }
10268     } else {
10269         fromX = moveList[currentMove][0] - AAA;
10270         fromY = moveList[currentMove][1] - ONE;
10271
10272         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10273
10274         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10275
10276         if (appData.highlightLastMove) {
10277             SetHighlights(fromX, fromY, toX, toY);
10278         }
10279     }
10280     DisplayMove(currentMove);
10281     SendMoveToProgram(currentMove++, &first);
10282     DisplayBothClocks();
10283     DrawPosition(FALSE, boards[currentMove]);
10284     // [HGM] PV info: always display, routine tests if empty
10285     DisplayComment(currentMove - 1, commentList[currentMove]);
10286     return TRUE;
10287 }
10288
10289
10290 int
10291 LoadGameOneMove(readAhead)
10292      ChessMove readAhead;
10293 {
10294     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10295     char promoChar = NULLCHAR;
10296     ChessMove moveType;
10297     char move[MSG_SIZ];
10298     char *p, *q;
10299
10300     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10301         gameMode != AnalyzeMode && gameMode != Training) {
10302         gameFileFP = NULL;
10303         return FALSE;
10304     }
10305
10306     yyboardindex = forwardMostMove;
10307     if (readAhead != EndOfFile) {
10308       moveType = readAhead;
10309     } else {
10310       if (gameFileFP == NULL)
10311           return FALSE;
10312       moveType = (ChessMove) Myylex();
10313     }
10314
10315     done = FALSE;
10316     switch (moveType) {
10317       case Comment:
10318         if (appData.debugMode)
10319           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10320         p = yy_text;
10321
10322         /* append the comment but don't display it */
10323         AppendComment(currentMove, p, FALSE);
10324         return TRUE;
10325
10326       case WhiteCapturesEnPassant:
10327       case BlackCapturesEnPassant:
10328       case WhitePromotion:
10329       case BlackPromotion:
10330       case WhiteNonPromotion:
10331       case BlackNonPromotion:
10332       case NormalMove:
10333       case WhiteKingSideCastle:
10334       case WhiteQueenSideCastle:
10335       case BlackKingSideCastle:
10336       case BlackQueenSideCastle:
10337       case WhiteKingSideCastleWild:
10338       case WhiteQueenSideCastleWild:
10339       case BlackKingSideCastleWild:
10340       case BlackQueenSideCastleWild:
10341       /* PUSH Fabien */
10342       case WhiteHSideCastleFR:
10343       case WhiteASideCastleFR:
10344       case BlackHSideCastleFR:
10345       case BlackASideCastleFR:
10346       /* POP Fabien */
10347         if (appData.debugMode)
10348           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10349         fromX = currentMoveString[0] - AAA;
10350         fromY = currentMoveString[1] - ONE;
10351         toX = currentMoveString[2] - AAA;
10352         toY = currentMoveString[3] - ONE;
10353         promoChar = currentMoveString[4];
10354         break;
10355
10356       case WhiteDrop:
10357       case BlackDrop:
10358         if (appData.debugMode)
10359           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10360         fromX = moveType == WhiteDrop ?
10361           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10362         (int) CharToPiece(ToLower(currentMoveString[0]));
10363         fromY = DROP_RANK;
10364         toX = currentMoveString[2] - AAA;
10365         toY = currentMoveString[3] - ONE;
10366         break;
10367
10368       case WhiteWins:
10369       case BlackWins:
10370       case GameIsDrawn:
10371       case GameUnfinished:
10372         if (appData.debugMode)
10373           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10374         p = strchr(yy_text, '{');
10375         if (p == NULL) p = strchr(yy_text, '(');
10376         if (p == NULL) {
10377             p = yy_text;
10378             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10379         } else {
10380             q = strchr(p, *p == '{' ? '}' : ')');
10381             if (q != NULL) *q = NULLCHAR;
10382             p++;
10383         }
10384         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10385         GameEnds(moveType, p, GE_FILE);
10386         done = TRUE;
10387         if (cmailMsgLoaded) {
10388             ClearHighlights();
10389             flipView = WhiteOnMove(currentMove);
10390             if (moveType == GameUnfinished) flipView = !flipView;
10391             if (appData.debugMode)
10392               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10393         }
10394         break;
10395
10396       case EndOfFile:
10397         if (appData.debugMode)
10398           fprintf(debugFP, "Parser hit end of file\n");
10399         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10400           case MT_NONE:
10401           case MT_CHECK:
10402             break;
10403           case MT_CHECKMATE:
10404           case MT_STAINMATE:
10405             if (WhiteOnMove(currentMove)) {
10406                 GameEnds(BlackWins, "Black mates", GE_FILE);
10407             } else {
10408                 GameEnds(WhiteWins, "White mates", GE_FILE);
10409             }
10410             break;
10411           case MT_STALEMATE:
10412             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10413             break;
10414         }
10415         done = TRUE;
10416         break;
10417
10418       case MoveNumberOne:
10419         if (lastLoadGameStart == GNUChessGame) {
10420             /* GNUChessGames have numbers, but they aren't move numbers */
10421             if (appData.debugMode)
10422               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10423                       yy_text, (int) moveType);
10424             return LoadGameOneMove(EndOfFile); /* tail recursion */
10425         }
10426         /* else fall thru */
10427
10428       case XBoardGame:
10429       case GNUChessGame:
10430       case PGNTag:
10431         /* Reached start of next game in file */
10432         if (appData.debugMode)
10433           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10434         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10435           case MT_NONE:
10436           case MT_CHECK:
10437             break;
10438           case MT_CHECKMATE:
10439           case MT_STAINMATE:
10440             if (WhiteOnMove(currentMove)) {
10441                 GameEnds(BlackWins, "Black mates", GE_FILE);
10442             } else {
10443                 GameEnds(WhiteWins, "White mates", GE_FILE);
10444             }
10445             break;
10446           case MT_STALEMATE:
10447             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10448             break;
10449         }
10450         done = TRUE;
10451         break;
10452
10453       case PositionDiagram:     /* should not happen; ignore */
10454       case ElapsedTime:         /* ignore */
10455       case NAG:                 /* ignore */
10456         if (appData.debugMode)
10457           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10458                   yy_text, (int) moveType);
10459         return LoadGameOneMove(EndOfFile); /* tail recursion */
10460
10461       case IllegalMove:
10462         if (appData.testLegality) {
10463             if (appData.debugMode)
10464               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10465             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10466                     (forwardMostMove / 2) + 1,
10467                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10468             DisplayError(move, 0);
10469             done = TRUE;
10470         } else {
10471             if (appData.debugMode)
10472               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10473                       yy_text, currentMoveString);
10474             fromX = currentMoveString[0] - AAA;
10475             fromY = currentMoveString[1] - ONE;
10476             toX = currentMoveString[2] - AAA;
10477             toY = currentMoveString[3] - ONE;
10478             promoChar = currentMoveString[4];
10479         }
10480         break;
10481
10482       case AmbiguousMove:
10483         if (appData.debugMode)
10484           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10485         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10486                 (forwardMostMove / 2) + 1,
10487                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10488         DisplayError(move, 0);
10489         done = TRUE;
10490         break;
10491
10492       default:
10493       case ImpossibleMove:
10494         if (appData.debugMode)
10495           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10496         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10497                 (forwardMostMove / 2) + 1,
10498                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10499         DisplayError(move, 0);
10500         done = TRUE;
10501         break;
10502     }
10503
10504     if (done) {
10505         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10506             DrawPosition(FALSE, boards[currentMove]);
10507             DisplayBothClocks();
10508             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10509               DisplayComment(currentMove - 1, commentList[currentMove]);
10510         }
10511         (void) StopLoadGameTimer();
10512         gameFileFP = NULL;
10513         cmailOldMove = forwardMostMove;
10514         return FALSE;
10515     } else {
10516         /* currentMoveString is set as a side-effect of yylex */
10517
10518         thinkOutput[0] = NULLCHAR;
10519         MakeMove(fromX, fromY, toX, toY, promoChar);
10520         currentMove = forwardMostMove;
10521         return TRUE;
10522     }
10523 }
10524
10525 /* Load the nth game from the given file */
10526 int
10527 LoadGameFromFile(filename, n, title, useList)
10528      char *filename;
10529      int n;
10530      char *title;
10531      /*Boolean*/ int useList;
10532 {
10533     FILE *f;
10534     char buf[MSG_SIZ];
10535
10536     if (strcmp(filename, "-") == 0) {
10537         f = stdin;
10538         title = "stdin";
10539     } else {
10540         f = fopen(filename, "rb");
10541         if (f == NULL) {
10542           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10543             DisplayError(buf, errno);
10544             return FALSE;
10545         }
10546     }
10547     if (fseek(f, 0, 0) == -1) {
10548         /* f is not seekable; probably a pipe */
10549         useList = FALSE;
10550     }
10551     if (useList && n == 0) {
10552         int error = GameListBuild(f);
10553         if (error) {
10554             DisplayError(_("Cannot build game list"), error);
10555         } else if (!ListEmpty(&gameList) &&
10556                    ((ListGame *) gameList.tailPred)->number > 1) {
10557             GameListPopUp(f, title);
10558             return TRUE;
10559         }
10560         GameListDestroy();
10561         n = 1;
10562     }
10563     if (n == 0) n = 1;
10564     return LoadGame(f, n, title, FALSE);
10565 }
10566
10567
10568 void
10569 MakeRegisteredMove()
10570 {
10571     int fromX, fromY, toX, toY;
10572     char promoChar;
10573     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10574         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10575           case CMAIL_MOVE:
10576           case CMAIL_DRAW:
10577             if (appData.debugMode)
10578               fprintf(debugFP, "Restoring %s for game %d\n",
10579                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10580
10581             thinkOutput[0] = NULLCHAR;
10582             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10583             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10584             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10585             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10586             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10587             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10588             MakeMove(fromX, fromY, toX, toY, promoChar);
10589             ShowMove(fromX, fromY, toX, toY);
10590
10591             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10592               case MT_NONE:
10593               case MT_CHECK:
10594                 break;
10595
10596               case MT_CHECKMATE:
10597               case MT_STAINMATE:
10598                 if (WhiteOnMove(currentMove)) {
10599                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10600                 } else {
10601                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10602                 }
10603                 break;
10604
10605               case MT_STALEMATE:
10606                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10607                 break;
10608             }
10609
10610             break;
10611
10612           case CMAIL_RESIGN:
10613             if (WhiteOnMove(currentMove)) {
10614                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10615             } else {
10616                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10617             }
10618             break;
10619
10620           case CMAIL_ACCEPT:
10621             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10622             break;
10623
10624           default:
10625             break;
10626         }
10627     }
10628
10629     return;
10630 }
10631
10632 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10633 int
10634 CmailLoadGame(f, gameNumber, title, useList)
10635      FILE *f;
10636      int gameNumber;
10637      char *title;
10638      int useList;
10639 {
10640     int retVal;
10641
10642     if (gameNumber > nCmailGames) {
10643         DisplayError(_("No more games in this message"), 0);
10644         return FALSE;
10645     }
10646     if (f == lastLoadGameFP) {
10647         int offset = gameNumber - lastLoadGameNumber;
10648         if (offset == 0) {
10649             cmailMsg[0] = NULLCHAR;
10650             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10651                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10652                 nCmailMovesRegistered--;
10653             }
10654             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10655             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10656                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10657             }
10658         } else {
10659             if (! RegisterMove()) return FALSE;
10660         }
10661     }
10662
10663     retVal = LoadGame(f, gameNumber, title, useList);
10664
10665     /* Make move registered during previous look at this game, if any */
10666     MakeRegisteredMove();
10667
10668     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10669         commentList[currentMove]
10670           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10671         DisplayComment(currentMove - 1, commentList[currentMove]);
10672     }
10673
10674     return retVal;
10675 }
10676
10677 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10678 int
10679 ReloadGame(offset)
10680      int offset;
10681 {
10682     int gameNumber = lastLoadGameNumber + offset;
10683     if (lastLoadGameFP == NULL) {
10684         DisplayError(_("No game has been loaded yet"), 0);
10685         return FALSE;
10686     }
10687     if (gameNumber <= 0) {
10688         DisplayError(_("Can't back up any further"), 0);
10689         return FALSE;
10690     }
10691     if (cmailMsgLoaded) {
10692         return CmailLoadGame(lastLoadGameFP, gameNumber,
10693                              lastLoadGameTitle, lastLoadGameUseList);
10694     } else {
10695         return LoadGame(lastLoadGameFP, gameNumber,
10696                         lastLoadGameTitle, lastLoadGameUseList);
10697     }
10698 }
10699
10700
10701
10702 /* Load the nth game from open file f */
10703 int
10704 LoadGame(f, gameNumber, title, useList)
10705      FILE *f;
10706      int gameNumber;
10707      char *title;
10708      int useList;
10709 {
10710     ChessMove cm;
10711     char buf[MSG_SIZ];
10712     int gn = gameNumber;
10713     ListGame *lg = NULL;
10714     int numPGNTags = 0;
10715     int err;
10716     GameMode oldGameMode;
10717     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10718
10719     if (appData.debugMode)
10720         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10721
10722     if (gameMode == Training )
10723         SetTrainingModeOff();
10724
10725     oldGameMode = gameMode;
10726     if (gameMode != BeginningOfGame) {
10727       Reset(FALSE, TRUE);
10728     }
10729
10730     gameFileFP = f;
10731     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10732         fclose(lastLoadGameFP);
10733     }
10734
10735     if (useList) {
10736         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10737
10738         if (lg) {
10739             fseek(f, lg->offset, 0);
10740             GameListHighlight(gameNumber);
10741             gn = 1;
10742         }
10743         else {
10744             DisplayError(_("Game number out of range"), 0);
10745             return FALSE;
10746         }
10747     } else {
10748         GameListDestroy();
10749         if (fseek(f, 0, 0) == -1) {
10750             if (f == lastLoadGameFP ?
10751                 gameNumber == lastLoadGameNumber + 1 :
10752                 gameNumber == 1) {
10753                 gn = 1;
10754             } else {
10755                 DisplayError(_("Can't seek on game file"), 0);
10756                 return FALSE;
10757             }
10758         }
10759     }
10760     lastLoadGameFP = f;
10761     lastLoadGameNumber = gameNumber;
10762     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10763     lastLoadGameUseList = useList;
10764
10765     yynewfile(f);
10766
10767     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10768       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10769                 lg->gameInfo.black);
10770             DisplayTitle(buf);
10771     } else if (*title != NULLCHAR) {
10772         if (gameNumber > 1) {
10773           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10774             DisplayTitle(buf);
10775         } else {
10776             DisplayTitle(title);
10777         }
10778     }
10779
10780     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10781         gameMode = PlayFromGameFile;
10782         ModeHighlight();
10783     }
10784
10785     currentMove = forwardMostMove = backwardMostMove = 0;
10786     CopyBoard(boards[0], initialPosition);
10787     StopClocks();
10788
10789     /*
10790      * Skip the first gn-1 games in the file.
10791      * Also skip over anything that precedes an identifiable
10792      * start of game marker, to avoid being confused by
10793      * garbage at the start of the file.  Currently
10794      * recognized start of game markers are the move number "1",
10795      * the pattern "gnuchess .* game", the pattern
10796      * "^[#;%] [^ ]* game file", and a PGN tag block.
10797      * A game that starts with one of the latter two patterns
10798      * will also have a move number 1, possibly
10799      * following a position diagram.
10800      * 5-4-02: Let's try being more lenient and allowing a game to
10801      * start with an unnumbered move.  Does that break anything?
10802      */
10803     cm = lastLoadGameStart = EndOfFile;
10804     while (gn > 0) {
10805         yyboardindex = forwardMostMove;
10806         cm = (ChessMove) Myylex();
10807         switch (cm) {
10808           case EndOfFile:
10809             if (cmailMsgLoaded) {
10810                 nCmailGames = CMAIL_MAX_GAMES - gn;
10811             } else {
10812                 Reset(TRUE, TRUE);
10813                 DisplayError(_("Game not found in file"), 0);
10814             }
10815             return FALSE;
10816
10817           case GNUChessGame:
10818           case XBoardGame:
10819             gn--;
10820             lastLoadGameStart = cm;
10821             break;
10822
10823           case MoveNumberOne:
10824             switch (lastLoadGameStart) {
10825               case GNUChessGame:
10826               case XBoardGame:
10827               case PGNTag:
10828                 break;
10829               case MoveNumberOne:
10830               case EndOfFile:
10831                 gn--;           /* count this game */
10832                 lastLoadGameStart = cm;
10833                 break;
10834               default:
10835                 /* impossible */
10836                 break;
10837             }
10838             break;
10839
10840           case PGNTag:
10841             switch (lastLoadGameStart) {
10842               case GNUChessGame:
10843               case PGNTag:
10844               case MoveNumberOne:
10845               case EndOfFile:
10846                 gn--;           /* count this game */
10847                 lastLoadGameStart = cm;
10848                 break;
10849               case XBoardGame:
10850                 lastLoadGameStart = cm; /* game counted already */
10851                 break;
10852               default:
10853                 /* impossible */
10854                 break;
10855             }
10856             if (gn > 0) {
10857                 do {
10858                     yyboardindex = forwardMostMove;
10859                     cm = (ChessMove) Myylex();
10860                 } while (cm == PGNTag || cm == Comment);
10861             }
10862             break;
10863
10864           case WhiteWins:
10865           case BlackWins:
10866           case GameIsDrawn:
10867             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10868                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10869                     != CMAIL_OLD_RESULT) {
10870                     nCmailResults ++ ;
10871                     cmailResult[  CMAIL_MAX_GAMES
10872                                 - gn - 1] = CMAIL_OLD_RESULT;
10873                 }
10874             }
10875             break;
10876
10877           case NormalMove:
10878             /* Only a NormalMove can be at the start of a game
10879              * without a position diagram. */
10880             if (lastLoadGameStart == EndOfFile ) {
10881               gn--;
10882               lastLoadGameStart = MoveNumberOne;
10883             }
10884             break;
10885
10886           default:
10887             break;
10888         }
10889     }
10890
10891     if (appData.debugMode)
10892       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10893
10894     if (cm == XBoardGame) {
10895         /* Skip any header junk before position diagram and/or move 1 */
10896         for (;;) {
10897             yyboardindex = forwardMostMove;
10898             cm = (ChessMove) Myylex();
10899
10900             if (cm == EndOfFile ||
10901                 cm == GNUChessGame || cm == XBoardGame) {
10902                 /* Empty game; pretend end-of-file and handle later */
10903                 cm = EndOfFile;
10904                 break;
10905             }
10906
10907             if (cm == MoveNumberOne || cm == PositionDiagram ||
10908                 cm == PGNTag || cm == Comment)
10909               break;
10910         }
10911     } else if (cm == GNUChessGame) {
10912         if (gameInfo.event != NULL) {
10913             free(gameInfo.event);
10914         }
10915         gameInfo.event = StrSave(yy_text);
10916     }
10917
10918     startedFromSetupPosition = FALSE;
10919     while (cm == PGNTag) {
10920         if (appData.debugMode)
10921           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10922         err = ParsePGNTag(yy_text, &gameInfo);
10923         if (!err) numPGNTags++;
10924
10925         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10926         if(gameInfo.variant != oldVariant) {
10927             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10928             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10929             InitPosition(TRUE);
10930             oldVariant = gameInfo.variant;
10931             if (appData.debugMode)
10932               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10933         }
10934
10935
10936         if (gameInfo.fen != NULL) {
10937           Board initial_position;
10938           startedFromSetupPosition = TRUE;
10939           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10940             Reset(TRUE, TRUE);
10941             DisplayError(_("Bad FEN position in file"), 0);
10942             return FALSE;
10943           }
10944           CopyBoard(boards[0], initial_position);
10945           if (blackPlaysFirst) {
10946             currentMove = forwardMostMove = backwardMostMove = 1;
10947             CopyBoard(boards[1], initial_position);
10948             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10949             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10950             timeRemaining[0][1] = whiteTimeRemaining;
10951             timeRemaining[1][1] = blackTimeRemaining;
10952             if (commentList[0] != NULL) {
10953               commentList[1] = commentList[0];
10954               commentList[0] = NULL;
10955             }
10956           } else {
10957             currentMove = forwardMostMove = backwardMostMove = 0;
10958           }
10959           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10960           {   int i;
10961               initialRulePlies = FENrulePlies;
10962               for( i=0; i< nrCastlingRights; i++ )
10963                   initialRights[i] = initial_position[CASTLING][i];
10964           }
10965           yyboardindex = forwardMostMove;
10966           free(gameInfo.fen);
10967           gameInfo.fen = NULL;
10968         }
10969
10970         yyboardindex = forwardMostMove;
10971         cm = (ChessMove) Myylex();
10972
10973         /* Handle comments interspersed among the tags */
10974         while (cm == Comment) {
10975             char *p;
10976             if (appData.debugMode)
10977               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10978             p = yy_text;
10979             AppendComment(currentMove, p, FALSE);
10980             yyboardindex = forwardMostMove;
10981             cm = (ChessMove) Myylex();
10982         }
10983     }
10984
10985     /* don't rely on existence of Event tag since if game was
10986      * pasted from clipboard the Event tag may not exist
10987      */
10988     if (numPGNTags > 0){
10989         char *tags;
10990         if (gameInfo.variant == VariantNormal) {
10991           VariantClass v = StringToVariant(gameInfo.event);
10992           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10993           if(v < VariantShogi) gameInfo.variant = v;
10994         }
10995         if (!matchMode) {
10996           if( appData.autoDisplayTags ) {
10997             tags = PGNTags(&gameInfo);
10998             TagsPopUp(tags, CmailMsg());
10999             free(tags);
11000           }
11001         }
11002     } else {
11003         /* Make something up, but don't display it now */
11004         SetGameInfo();
11005         TagsPopDown();
11006     }
11007
11008     if (cm == PositionDiagram) {
11009         int i, j;
11010         char *p;
11011         Board initial_position;
11012
11013         if (appData.debugMode)
11014           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11015
11016         if (!startedFromSetupPosition) {
11017             p = yy_text;
11018             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11019               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11020                 switch (*p) {
11021                   case '{':
11022                   case '[':
11023                   case '-':
11024                   case ' ':
11025                   case '\t':
11026                   case '\n':
11027                   case '\r':
11028                     break;
11029                   default:
11030                     initial_position[i][j++] = CharToPiece(*p);
11031                     break;
11032                 }
11033             while (*p == ' ' || *p == '\t' ||
11034                    *p == '\n' || *p == '\r') p++;
11035
11036             if (strncmp(p, "black", strlen("black"))==0)
11037               blackPlaysFirst = TRUE;
11038             else
11039               blackPlaysFirst = FALSE;
11040             startedFromSetupPosition = TRUE;
11041
11042             CopyBoard(boards[0], initial_position);
11043             if (blackPlaysFirst) {
11044                 currentMove = forwardMostMove = backwardMostMove = 1;
11045                 CopyBoard(boards[1], initial_position);
11046                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11047                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11048                 timeRemaining[0][1] = whiteTimeRemaining;
11049                 timeRemaining[1][1] = blackTimeRemaining;
11050                 if (commentList[0] != NULL) {
11051                     commentList[1] = commentList[0];
11052                     commentList[0] = NULL;
11053                 }
11054             } else {
11055                 currentMove = forwardMostMove = backwardMostMove = 0;
11056             }
11057         }
11058         yyboardindex = forwardMostMove;
11059         cm = (ChessMove) Myylex();
11060     }
11061
11062     if (first.pr == NoProc) {
11063         StartChessProgram(&first);
11064     }
11065     InitChessProgram(&first, FALSE);
11066     SendToProgram("force\n", &first);
11067     if (startedFromSetupPosition) {
11068         SendBoard(&first, forwardMostMove);
11069     if (appData.debugMode) {
11070         fprintf(debugFP, "Load Game\n");
11071     }
11072         DisplayBothClocks();
11073     }
11074
11075     /* [HGM] server: flag to write setup moves in broadcast file as one */
11076     loadFlag = appData.suppressLoadMoves;
11077
11078     while (cm == Comment) {
11079         char *p;
11080         if (appData.debugMode)
11081           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11082         p = yy_text;
11083         AppendComment(currentMove, p, FALSE);
11084         yyboardindex = forwardMostMove;
11085         cm = (ChessMove) Myylex();
11086     }
11087
11088     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11089         cm == WhiteWins || cm == BlackWins ||
11090         cm == GameIsDrawn || cm == GameUnfinished) {
11091         DisplayMessage("", _("No moves in game"));
11092         if (cmailMsgLoaded) {
11093             if (appData.debugMode)
11094               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11095             ClearHighlights();
11096             flipView = FALSE;
11097         }
11098         DrawPosition(FALSE, boards[currentMove]);
11099         DisplayBothClocks();
11100         gameMode = EditGame;
11101         ModeHighlight();
11102         gameFileFP = NULL;
11103         cmailOldMove = 0;
11104         return TRUE;
11105     }
11106
11107     // [HGM] PV info: routine tests if comment empty
11108     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11109         DisplayComment(currentMove - 1, commentList[currentMove]);
11110     }
11111     if (!matchMode && appData.timeDelay != 0)
11112       DrawPosition(FALSE, boards[currentMove]);
11113
11114     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11115       programStats.ok_to_send = 1;
11116     }
11117
11118     /* if the first token after the PGN tags is a move
11119      * and not move number 1, retrieve it from the parser
11120      */
11121     if (cm != MoveNumberOne)
11122         LoadGameOneMove(cm);
11123
11124     /* load the remaining moves from the file */
11125     while (LoadGameOneMove(EndOfFile)) {
11126       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11127       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11128     }
11129
11130     /* rewind to the start of the game */
11131     currentMove = backwardMostMove;
11132
11133     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11134
11135     if (oldGameMode == AnalyzeFile ||
11136         oldGameMode == AnalyzeMode) {
11137       AnalyzeFileEvent();
11138     }
11139
11140     if (matchMode || appData.timeDelay == 0) {
11141       ToEndEvent();
11142       gameMode = EditGame;
11143       ModeHighlight();
11144     } else if (appData.timeDelay > 0) {
11145       AutoPlayGameLoop();
11146     }
11147
11148     if (appData.debugMode)
11149         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11150
11151     loadFlag = 0; /* [HGM] true game starts */
11152     return TRUE;
11153 }
11154
11155 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11156 int
11157 ReloadPosition(offset)
11158      int offset;
11159 {
11160     int positionNumber = lastLoadPositionNumber + offset;
11161     if (lastLoadPositionFP == NULL) {
11162         DisplayError(_("No position has been loaded yet"), 0);
11163         return FALSE;
11164     }
11165     if (positionNumber <= 0) {
11166         DisplayError(_("Can't back up any further"), 0);
11167         return FALSE;
11168     }
11169     return LoadPosition(lastLoadPositionFP, positionNumber,
11170                         lastLoadPositionTitle);
11171 }
11172
11173 /* Load the nth position from the given file */
11174 int
11175 LoadPositionFromFile(filename, n, title)
11176      char *filename;
11177      int n;
11178      char *title;
11179 {
11180     FILE *f;
11181     char buf[MSG_SIZ];
11182
11183     if (strcmp(filename, "-") == 0) {
11184         return LoadPosition(stdin, n, "stdin");
11185     } else {
11186         f = fopen(filename, "rb");
11187         if (f == NULL) {
11188             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11189             DisplayError(buf, errno);
11190             return FALSE;
11191         } else {
11192             return LoadPosition(f, n, title);
11193         }
11194     }
11195 }
11196
11197 /* Load the nth position from the given open file, and close it */
11198 int
11199 LoadPosition(f, positionNumber, title)
11200      FILE *f;
11201      int positionNumber;
11202      char *title;
11203 {
11204     char *p, line[MSG_SIZ];
11205     Board initial_position;
11206     int i, j, fenMode, pn;
11207
11208     if (gameMode == Training )
11209         SetTrainingModeOff();
11210
11211     if (gameMode != BeginningOfGame) {
11212         Reset(FALSE, TRUE);
11213     }
11214     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11215         fclose(lastLoadPositionFP);
11216     }
11217     if (positionNumber == 0) positionNumber = 1;
11218     lastLoadPositionFP = f;
11219     lastLoadPositionNumber = positionNumber;
11220     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11221     if (first.pr == NoProc) {
11222       StartChessProgram(&first);
11223       InitChessProgram(&first, FALSE);
11224     }
11225     pn = positionNumber;
11226     if (positionNumber < 0) {
11227         /* Negative position number means to seek to that byte offset */
11228         if (fseek(f, -positionNumber, 0) == -1) {
11229             DisplayError(_("Can't seek on position file"), 0);
11230             return FALSE;
11231         };
11232         pn = 1;
11233     } else {
11234         if (fseek(f, 0, 0) == -1) {
11235             if (f == lastLoadPositionFP ?
11236                 positionNumber == lastLoadPositionNumber + 1 :
11237                 positionNumber == 1) {
11238                 pn = 1;
11239             } else {
11240                 DisplayError(_("Can't seek on position file"), 0);
11241                 return FALSE;
11242             }
11243         }
11244     }
11245     /* See if this file is FEN or old-style xboard */
11246     if (fgets(line, MSG_SIZ, f) == NULL) {
11247         DisplayError(_("Position not found in file"), 0);
11248         return FALSE;
11249     }
11250     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11251     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11252
11253     if (pn >= 2) {
11254         if (fenMode || line[0] == '#') pn--;
11255         while (pn > 0) {
11256             /* skip positions before number pn */
11257             if (fgets(line, MSG_SIZ, f) == NULL) {
11258                 Reset(TRUE, TRUE);
11259                 DisplayError(_("Position not found in file"), 0);
11260                 return FALSE;
11261             }
11262             if (fenMode || line[0] == '#') pn--;
11263         }
11264     }
11265
11266     if (fenMode) {
11267         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11268             DisplayError(_("Bad FEN position in file"), 0);
11269             return FALSE;
11270         }
11271     } else {
11272         (void) fgets(line, MSG_SIZ, f);
11273         (void) fgets(line, MSG_SIZ, f);
11274
11275         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11276             (void) fgets(line, MSG_SIZ, f);
11277             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11278                 if (*p == ' ')
11279                   continue;
11280                 initial_position[i][j++] = CharToPiece(*p);
11281             }
11282         }
11283
11284         blackPlaysFirst = FALSE;
11285         if (!feof(f)) {
11286             (void) fgets(line, MSG_SIZ, f);
11287             if (strncmp(line, "black", strlen("black"))==0)
11288               blackPlaysFirst = TRUE;
11289         }
11290     }
11291     startedFromSetupPosition = TRUE;
11292
11293     SendToProgram("force\n", &first);
11294     CopyBoard(boards[0], initial_position);
11295     if (blackPlaysFirst) {
11296         currentMove = forwardMostMove = backwardMostMove = 1;
11297         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11298         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11299         CopyBoard(boards[1], initial_position);
11300         DisplayMessage("", _("Black to play"));
11301     } else {
11302         currentMove = forwardMostMove = backwardMostMove = 0;
11303         DisplayMessage("", _("White to play"));
11304     }
11305     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11306     SendBoard(&first, forwardMostMove);
11307     if (appData.debugMode) {
11308 int i, j;
11309   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11310   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11311         fprintf(debugFP, "Load Position\n");
11312     }
11313
11314     if (positionNumber > 1) {
11315       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11316         DisplayTitle(line);
11317     } else {
11318         DisplayTitle(title);
11319     }
11320     gameMode = EditGame;
11321     ModeHighlight();
11322     ResetClocks();
11323     timeRemaining[0][1] = whiteTimeRemaining;
11324     timeRemaining[1][1] = blackTimeRemaining;
11325     DrawPosition(FALSE, boards[currentMove]);
11326
11327     return TRUE;
11328 }
11329
11330
11331 void
11332 CopyPlayerNameIntoFileName(dest, src)
11333      char **dest, *src;
11334 {
11335     while (*src != NULLCHAR && *src != ',') {
11336         if (*src == ' ') {
11337             *(*dest)++ = '_';
11338             src++;
11339         } else {
11340             *(*dest)++ = *src++;
11341         }
11342     }
11343 }
11344
11345 char *DefaultFileName(ext)
11346      char *ext;
11347 {
11348     static char def[MSG_SIZ];
11349     char *p;
11350
11351     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11352         p = def;
11353         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11354         *p++ = '-';
11355         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11356         *p++ = '.';
11357         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11358     } else {
11359         def[0] = NULLCHAR;
11360     }
11361     return def;
11362 }
11363
11364 /* Save the current game to the given file */
11365 int
11366 SaveGameToFile(filename, append)
11367      char *filename;
11368      int append;
11369 {
11370     FILE *f;
11371     char buf[MSG_SIZ];
11372     int result;
11373
11374     if (strcmp(filename, "-") == 0) {
11375         return SaveGame(stdout, 0, NULL);
11376     } else {
11377         f = fopen(filename, append ? "a" : "w");
11378         if (f == NULL) {
11379             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11380             DisplayError(buf, errno);
11381             return FALSE;
11382         } else {
11383             safeStrCpy(buf, lastMsg, MSG_SIZ);
11384             DisplayMessage(_("Waiting for access to save file"), "");
11385             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11386             DisplayMessage(_("Saving game"), "");
11387             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11388             result = SaveGame(f, 0, NULL);
11389             DisplayMessage(buf, "");
11390             return result;
11391         }
11392     }
11393 }
11394
11395 char *
11396 SavePart(str)
11397      char *str;
11398 {
11399     static char buf[MSG_SIZ];
11400     char *p;
11401
11402     p = strchr(str, ' ');
11403     if (p == NULL) return str;
11404     strncpy(buf, str, p - str);
11405     buf[p - str] = NULLCHAR;
11406     return buf;
11407 }
11408
11409 #define PGN_MAX_LINE 75
11410
11411 #define PGN_SIDE_WHITE  0
11412 #define PGN_SIDE_BLACK  1
11413
11414 /* [AS] */
11415 static int FindFirstMoveOutOfBook( int side )
11416 {
11417     int result = -1;
11418
11419     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11420         int index = backwardMostMove;
11421         int has_book_hit = 0;
11422
11423         if( (index % 2) != side ) {
11424             index++;
11425         }
11426
11427         while( index < forwardMostMove ) {
11428             /* Check to see if engine is in book */
11429             int depth = pvInfoList[index].depth;
11430             int score = pvInfoList[index].score;
11431             int in_book = 0;
11432
11433             if( depth <= 2 ) {
11434                 in_book = 1;
11435             }
11436             else if( score == 0 && depth == 63 ) {
11437                 in_book = 1; /* Zappa */
11438             }
11439             else if( score == 2 && depth == 99 ) {
11440                 in_book = 1; /* Abrok */
11441             }
11442
11443             has_book_hit += in_book;
11444
11445             if( ! in_book ) {
11446                 result = index;
11447
11448                 break;
11449             }
11450
11451             index += 2;
11452         }
11453     }
11454
11455     return result;
11456 }
11457
11458 /* [AS] */
11459 void GetOutOfBookInfo( char * buf )
11460 {
11461     int oob[2];
11462     int i;
11463     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11464
11465     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11466     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11467
11468     *buf = '\0';
11469
11470     if( oob[0] >= 0 || oob[1] >= 0 ) {
11471         for( i=0; i<2; i++ ) {
11472             int idx = oob[i];
11473
11474             if( idx >= 0 ) {
11475                 if( i > 0 && oob[0] >= 0 ) {
11476                     strcat( buf, "   " );
11477                 }
11478
11479                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11480                 sprintf( buf+strlen(buf), "%s%.2f",
11481                     pvInfoList[idx].score >= 0 ? "+" : "",
11482                     pvInfoList[idx].score / 100.0 );
11483             }
11484         }
11485     }
11486 }
11487
11488 /* Save game in PGN style and close the file */
11489 int
11490 SaveGamePGN(f)
11491      FILE *f;
11492 {
11493     int i, offset, linelen, newblock;
11494     time_t tm;
11495 //    char *movetext;
11496     char numtext[32];
11497     int movelen, numlen, blank;
11498     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11499
11500     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11501
11502     tm = time((time_t *) NULL);
11503
11504     PrintPGNTags(f, &gameInfo);
11505
11506     if (backwardMostMove > 0 || startedFromSetupPosition) {
11507         char *fen = PositionToFEN(backwardMostMove, NULL);
11508         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11509         fprintf(f, "\n{--------------\n");
11510         PrintPosition(f, backwardMostMove);
11511         fprintf(f, "--------------}\n");
11512         free(fen);
11513     }
11514     else {
11515         /* [AS] Out of book annotation */
11516         if( appData.saveOutOfBookInfo ) {
11517             char buf[64];
11518
11519             GetOutOfBookInfo( buf );
11520
11521             if( buf[0] != '\0' ) {
11522                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11523             }
11524         }
11525
11526         fprintf(f, "\n");
11527     }
11528
11529     i = backwardMostMove;
11530     linelen = 0;
11531     newblock = TRUE;
11532
11533     while (i < forwardMostMove) {
11534         /* Print comments preceding this move */
11535         if (commentList[i] != NULL) {
11536             if (linelen > 0) fprintf(f, "\n");
11537             fprintf(f, "%s", commentList[i]);
11538             linelen = 0;
11539             newblock = TRUE;
11540         }
11541
11542         /* Format move number */
11543         if ((i % 2) == 0)
11544           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11545         else
11546           if (newblock)
11547             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11548           else
11549             numtext[0] = NULLCHAR;
11550
11551         numlen = strlen(numtext);
11552         newblock = FALSE;
11553
11554         /* Print move number */
11555         blank = linelen > 0 && numlen > 0;
11556         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11557             fprintf(f, "\n");
11558             linelen = 0;
11559             blank = 0;
11560         }
11561         if (blank) {
11562             fprintf(f, " ");
11563             linelen++;
11564         }
11565         fprintf(f, "%s", numtext);
11566         linelen += numlen;
11567
11568         /* Get move */
11569         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11570         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11571
11572         /* Print move */
11573         blank = linelen > 0 && movelen > 0;
11574         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11575             fprintf(f, "\n");
11576             linelen = 0;
11577             blank = 0;
11578         }
11579         if (blank) {
11580             fprintf(f, " ");
11581             linelen++;
11582         }
11583         fprintf(f, "%s", move_buffer);
11584         linelen += movelen;
11585
11586         /* [AS] Add PV info if present */
11587         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11588             /* [HGM] add time */
11589             char buf[MSG_SIZ]; int seconds;
11590
11591             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11592
11593             if( seconds <= 0)
11594               buf[0] = 0;
11595             else
11596               if( seconds < 30 )
11597                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11598               else
11599                 {
11600                   seconds = (seconds + 4)/10; // round to full seconds
11601                   if( seconds < 60 )
11602                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11603                   else
11604                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11605                 }
11606
11607             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11608                       pvInfoList[i].score >= 0 ? "+" : "",
11609                       pvInfoList[i].score / 100.0,
11610                       pvInfoList[i].depth,
11611                       buf );
11612
11613             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11614
11615             /* Print score/depth */
11616             blank = linelen > 0 && movelen > 0;
11617             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11618                 fprintf(f, "\n");
11619                 linelen = 0;
11620                 blank = 0;
11621             }
11622             if (blank) {
11623                 fprintf(f, " ");
11624                 linelen++;
11625             }
11626             fprintf(f, "%s", move_buffer);
11627             linelen += movelen;
11628         }
11629
11630         i++;
11631     }
11632
11633     /* Start a new line */
11634     if (linelen > 0) fprintf(f, "\n");
11635
11636     /* Print comments after last move */
11637     if (commentList[i] != NULL) {
11638         fprintf(f, "%s\n", commentList[i]);
11639     }
11640
11641     /* Print result */
11642     if (gameInfo.resultDetails != NULL &&
11643         gameInfo.resultDetails[0] != NULLCHAR) {
11644         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11645                 PGNResult(gameInfo.result));
11646     } else {
11647         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11648     }
11649
11650     fclose(f);
11651     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11652     return TRUE;
11653 }
11654
11655 /* Save game in old style and close the file */
11656 int
11657 SaveGameOldStyle(f)
11658      FILE *f;
11659 {
11660     int i, offset;
11661     time_t tm;
11662
11663     tm = time((time_t *) NULL);
11664
11665     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11666     PrintOpponents(f);
11667
11668     if (backwardMostMove > 0 || startedFromSetupPosition) {
11669         fprintf(f, "\n[--------------\n");
11670         PrintPosition(f, backwardMostMove);
11671         fprintf(f, "--------------]\n");
11672     } else {
11673         fprintf(f, "\n");
11674     }
11675
11676     i = backwardMostMove;
11677     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11678
11679     while (i < forwardMostMove) {
11680         if (commentList[i] != NULL) {
11681             fprintf(f, "[%s]\n", commentList[i]);
11682         }
11683
11684         if ((i % 2) == 1) {
11685             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11686             i++;
11687         } else {
11688             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11689             i++;
11690             if (commentList[i] != NULL) {
11691                 fprintf(f, "\n");
11692                 continue;
11693             }
11694             if (i >= forwardMostMove) {
11695                 fprintf(f, "\n");
11696                 break;
11697             }
11698             fprintf(f, "%s\n", parseList[i]);
11699             i++;
11700         }
11701     }
11702
11703     if (commentList[i] != NULL) {
11704         fprintf(f, "[%s]\n", commentList[i]);
11705     }
11706
11707     /* This isn't really the old style, but it's close enough */
11708     if (gameInfo.resultDetails != NULL &&
11709         gameInfo.resultDetails[0] != NULLCHAR) {
11710         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11711                 gameInfo.resultDetails);
11712     } else {
11713         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11714     }
11715
11716     fclose(f);
11717     return TRUE;
11718 }
11719
11720 /* Save the current game to open file f and close the file */
11721 int
11722 SaveGame(f, dummy, dummy2)
11723      FILE *f;
11724      int dummy;
11725      char *dummy2;
11726 {
11727     if (gameMode == EditPosition) EditPositionDone(TRUE);
11728     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11729     if (appData.oldSaveStyle)
11730       return SaveGameOldStyle(f);
11731     else
11732       return SaveGamePGN(f);
11733 }
11734
11735 /* Save the current position to the given file */
11736 int
11737 SavePositionToFile(filename)
11738      char *filename;
11739 {
11740     FILE *f;
11741     char buf[MSG_SIZ];
11742
11743     if (strcmp(filename, "-") == 0) {
11744         return SavePosition(stdout, 0, NULL);
11745     } else {
11746         f = fopen(filename, "a");
11747         if (f == NULL) {
11748             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11749             DisplayError(buf, errno);
11750             return FALSE;
11751         } else {
11752             safeStrCpy(buf, lastMsg, MSG_SIZ);
11753             DisplayMessage(_("Waiting for access to save file"), "");
11754             flock(fileno(f), LOCK_EX); // [HGM] lock
11755             DisplayMessage(_("Saving position"), "");
11756             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11757             SavePosition(f, 0, NULL);
11758             DisplayMessage(buf, "");
11759             return TRUE;
11760         }
11761     }
11762 }
11763
11764 /* Save the current position to the given open file and close the file */
11765 int
11766 SavePosition(f, dummy, dummy2)
11767      FILE *f;
11768      int dummy;
11769      char *dummy2;
11770 {
11771     time_t tm;
11772     char *fen;
11773
11774     if (gameMode == EditPosition) EditPositionDone(TRUE);
11775     if (appData.oldSaveStyle) {
11776         tm = time((time_t *) NULL);
11777
11778         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11779         PrintOpponents(f);
11780         fprintf(f, "[--------------\n");
11781         PrintPosition(f, currentMove);
11782         fprintf(f, "--------------]\n");
11783     } else {
11784         fen = PositionToFEN(currentMove, NULL);
11785         fprintf(f, "%s\n", fen);
11786         free(fen);
11787     }
11788     fclose(f);
11789     return TRUE;
11790 }
11791
11792 void
11793 ReloadCmailMsgEvent(unregister)
11794      int unregister;
11795 {
11796 #if !WIN32
11797     static char *inFilename = NULL;
11798     static char *outFilename;
11799     int i;
11800     struct stat inbuf, outbuf;
11801     int status;
11802
11803     /* Any registered moves are unregistered if unregister is set, */
11804     /* i.e. invoked by the signal handler */
11805     if (unregister) {
11806         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11807             cmailMoveRegistered[i] = FALSE;
11808             if (cmailCommentList[i] != NULL) {
11809                 free(cmailCommentList[i]);
11810                 cmailCommentList[i] = NULL;
11811             }
11812         }
11813         nCmailMovesRegistered = 0;
11814     }
11815
11816     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11817         cmailResult[i] = CMAIL_NOT_RESULT;
11818     }
11819     nCmailResults = 0;
11820
11821     if (inFilename == NULL) {
11822         /* Because the filenames are static they only get malloced once  */
11823         /* and they never get freed                                      */
11824         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11825         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11826
11827         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11828         sprintf(outFilename, "%s.out", appData.cmailGameName);
11829     }
11830
11831     status = stat(outFilename, &outbuf);
11832     if (status < 0) {
11833         cmailMailedMove = FALSE;
11834     } else {
11835         status = stat(inFilename, &inbuf);
11836         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11837     }
11838
11839     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11840        counts the games, notes how each one terminated, etc.
11841
11842        It would be nice to remove this kludge and instead gather all
11843        the information while building the game list.  (And to keep it
11844        in the game list nodes instead of having a bunch of fixed-size
11845        parallel arrays.)  Note this will require getting each game's
11846        termination from the PGN tags, as the game list builder does
11847        not process the game moves.  --mann
11848        */
11849     cmailMsgLoaded = TRUE;
11850     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11851
11852     /* Load first game in the file or popup game menu */
11853     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11854
11855 #endif /* !WIN32 */
11856     return;
11857 }
11858
11859 int
11860 RegisterMove()
11861 {
11862     FILE *f;
11863     char string[MSG_SIZ];
11864
11865     if (   cmailMailedMove
11866         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11867         return TRUE;            /* Allow free viewing  */
11868     }
11869
11870     /* Unregister move to ensure that we don't leave RegisterMove        */
11871     /* with the move registered when the conditions for registering no   */
11872     /* longer hold                                                       */
11873     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11874         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11875         nCmailMovesRegistered --;
11876
11877         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11878           {
11879               free(cmailCommentList[lastLoadGameNumber - 1]);
11880               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11881           }
11882     }
11883
11884     if (cmailOldMove == -1) {
11885         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11886         return FALSE;
11887     }
11888
11889     if (currentMove > cmailOldMove + 1) {
11890         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11891         return FALSE;
11892     }
11893
11894     if (currentMove < cmailOldMove) {
11895         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11896         return FALSE;
11897     }
11898
11899     if (forwardMostMove > currentMove) {
11900         /* Silently truncate extra moves */
11901         TruncateGame();
11902     }
11903
11904     if (   (currentMove == cmailOldMove + 1)
11905         || (   (currentMove == cmailOldMove)
11906             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11907                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11908         if (gameInfo.result != GameUnfinished) {
11909             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11910         }
11911
11912         if (commentList[currentMove] != NULL) {
11913             cmailCommentList[lastLoadGameNumber - 1]
11914               = StrSave(commentList[currentMove]);
11915         }
11916         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11917
11918         if (appData.debugMode)
11919           fprintf(debugFP, "Saving %s for game %d\n",
11920                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11921
11922         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11923
11924         f = fopen(string, "w");
11925         if (appData.oldSaveStyle) {
11926             SaveGameOldStyle(f); /* also closes the file */
11927
11928             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11929             f = fopen(string, "w");
11930             SavePosition(f, 0, NULL); /* also closes the file */
11931         } else {
11932             fprintf(f, "{--------------\n");
11933             PrintPosition(f, currentMove);
11934             fprintf(f, "--------------}\n\n");
11935
11936             SaveGame(f, 0, NULL); /* also closes the file*/
11937         }
11938
11939         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11940         nCmailMovesRegistered ++;
11941     } else if (nCmailGames == 1) {
11942         DisplayError(_("You have not made a move yet"), 0);
11943         return FALSE;
11944     }
11945
11946     return TRUE;
11947 }
11948
11949 void
11950 MailMoveEvent()
11951 {
11952 #if !WIN32
11953     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11954     FILE *commandOutput;
11955     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11956     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11957     int nBuffers;
11958     int i;
11959     int archived;
11960     char *arcDir;
11961
11962     if (! cmailMsgLoaded) {
11963         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11964         return;
11965     }
11966
11967     if (nCmailGames == nCmailResults) {
11968         DisplayError(_("No unfinished games"), 0);
11969         return;
11970     }
11971
11972 #if CMAIL_PROHIBIT_REMAIL
11973     if (cmailMailedMove) {
11974       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);
11975         DisplayError(msg, 0);
11976         return;
11977     }
11978 #endif
11979
11980     if (! (cmailMailedMove || RegisterMove())) return;
11981
11982     if (   cmailMailedMove
11983         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11984       snprintf(string, MSG_SIZ, partCommandString,
11985                appData.debugMode ? " -v" : "", appData.cmailGameName);
11986         commandOutput = popen(string, "r");
11987
11988         if (commandOutput == NULL) {
11989             DisplayError(_("Failed to invoke cmail"), 0);
11990         } else {
11991             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11992                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11993             }
11994             if (nBuffers > 1) {
11995                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11996                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11997                 nBytes = MSG_SIZ - 1;
11998             } else {
11999                 (void) memcpy(msg, buffer, nBytes);
12000             }
12001             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12002
12003             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12004                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12005
12006                 archived = TRUE;
12007                 for (i = 0; i < nCmailGames; i ++) {
12008                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12009                         archived = FALSE;
12010                     }
12011                 }
12012                 if (   archived
12013                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12014                         != NULL)) {
12015                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12016                            arcDir,
12017                            appData.cmailGameName,
12018                            gameInfo.date);
12019                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12020                     cmailMsgLoaded = FALSE;
12021                 }
12022             }
12023
12024             DisplayInformation(msg);
12025             pclose(commandOutput);
12026         }
12027     } else {
12028         if ((*cmailMsg) != '\0') {
12029             DisplayInformation(cmailMsg);
12030         }
12031     }
12032
12033     return;
12034 #endif /* !WIN32 */
12035 }
12036
12037 char *
12038 CmailMsg()
12039 {
12040 #if WIN32
12041     return NULL;
12042 #else
12043     int  prependComma = 0;
12044     char number[5];
12045     char string[MSG_SIZ];       /* Space for game-list */
12046     int  i;
12047
12048     if (!cmailMsgLoaded) return "";
12049
12050     if (cmailMailedMove) {
12051       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12052     } else {
12053         /* Create a list of games left */
12054       snprintf(string, MSG_SIZ, "[");
12055         for (i = 0; i < nCmailGames; i ++) {
12056             if (! (   cmailMoveRegistered[i]
12057                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12058                 if (prependComma) {
12059                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12060                 } else {
12061                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12062                     prependComma = 1;
12063                 }
12064
12065                 strcat(string, number);
12066             }
12067         }
12068         strcat(string, "]");
12069
12070         if (nCmailMovesRegistered + nCmailResults == 0) {
12071             switch (nCmailGames) {
12072               case 1:
12073                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12074                 break;
12075
12076               case 2:
12077                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12078                 break;
12079
12080               default:
12081                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12082                          nCmailGames);
12083                 break;
12084             }
12085         } else {
12086             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12087               case 1:
12088                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12089                          string);
12090                 break;
12091
12092               case 0:
12093                 if (nCmailResults == nCmailGames) {
12094                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12095                 } else {
12096                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12097                 }
12098                 break;
12099
12100               default:
12101                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12102                          string);
12103             }
12104         }
12105     }
12106     return cmailMsg;
12107 #endif /* WIN32 */
12108 }
12109
12110 void
12111 ResetGameEvent()
12112 {
12113     if (gameMode == Training)
12114       SetTrainingModeOff();
12115
12116     Reset(TRUE, TRUE);
12117     cmailMsgLoaded = FALSE;
12118     if (appData.icsActive) {
12119       SendToICS(ics_prefix);
12120       SendToICS("refresh\n");
12121     }
12122 }
12123
12124 void
12125 ExitEvent(status)
12126      int status;
12127 {
12128     exiting++;
12129     if (exiting > 2) {
12130       /* Give up on clean exit */
12131       exit(status);
12132     }
12133     if (exiting > 1) {
12134       /* Keep trying for clean exit */
12135       return;
12136     }
12137
12138     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12139
12140     if (telnetISR != NULL) {
12141       RemoveInputSource(telnetISR);
12142     }
12143     if (icsPR != NoProc) {
12144       DestroyChildProcess(icsPR, TRUE);
12145     }
12146
12147     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12148     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12149
12150     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12151     /* make sure this other one finishes before killing it!                  */
12152     if(endingGame) { int count = 0;
12153         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12154         while(endingGame && count++ < 10) DoSleep(1);
12155         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12156     }
12157
12158     /* Kill off chess programs */
12159     if (first.pr != NoProc) {
12160         ExitAnalyzeMode();
12161
12162         DoSleep( appData.delayBeforeQuit );
12163         SendToProgram("quit\n", &first);
12164         DoSleep( appData.delayAfterQuit );
12165         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12166     }
12167     if (second.pr != NoProc) {
12168         DoSleep( appData.delayBeforeQuit );
12169         SendToProgram("quit\n", &second);
12170         DoSleep( appData.delayAfterQuit );
12171         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12172     }
12173     if (first.isr != NULL) {
12174         RemoveInputSource(first.isr);
12175     }
12176     if (second.isr != NULL) {
12177         RemoveInputSource(second.isr);
12178     }
12179
12180     ShutDownFrontEnd();
12181     exit(status);
12182 }
12183
12184 void
12185 PauseEvent()
12186 {
12187     if (appData.debugMode)
12188         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12189     if (pausing) {
12190         pausing = FALSE;
12191         ModeHighlight();
12192         if (gameMode == MachinePlaysWhite ||
12193             gameMode == MachinePlaysBlack) {
12194             StartClocks();
12195         } else {
12196             DisplayBothClocks();
12197         }
12198         if (gameMode == PlayFromGameFile) {
12199             if (appData.timeDelay >= 0)
12200                 AutoPlayGameLoop();
12201         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12202             Reset(FALSE, TRUE);
12203             SendToICS(ics_prefix);
12204             SendToICS("refresh\n");
12205         } else if (currentMove < forwardMostMove) {
12206             ForwardInner(forwardMostMove);
12207         }
12208         pauseExamInvalid = FALSE;
12209     } else {
12210         switch (gameMode) {
12211           default:
12212             return;
12213           case IcsExamining:
12214             pauseExamForwardMostMove = forwardMostMove;
12215             pauseExamInvalid = FALSE;
12216             /* fall through */
12217           case IcsObserving:
12218           case IcsPlayingWhite:
12219           case IcsPlayingBlack:
12220             pausing = TRUE;
12221             ModeHighlight();
12222             return;
12223           case PlayFromGameFile:
12224             (void) StopLoadGameTimer();
12225             pausing = TRUE;
12226             ModeHighlight();
12227             break;
12228           case BeginningOfGame:
12229             if (appData.icsActive) return;
12230             /* else fall through */
12231           case MachinePlaysWhite:
12232           case MachinePlaysBlack:
12233           case TwoMachinesPlay:
12234             if (forwardMostMove == 0)
12235               return;           /* don't pause if no one has moved */
12236             if ((gameMode == MachinePlaysWhite &&
12237                  !WhiteOnMove(forwardMostMove)) ||
12238                 (gameMode == MachinePlaysBlack &&
12239                  WhiteOnMove(forwardMostMove))) {
12240                 StopClocks();
12241             }
12242             pausing = TRUE;
12243             ModeHighlight();
12244             break;
12245         }
12246     }
12247 }
12248
12249 void
12250 EditCommentEvent()
12251 {
12252     char title[MSG_SIZ];
12253
12254     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12255       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12256     } else {
12257       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12258                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12259                parseList[currentMove - 1]);
12260     }
12261
12262     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12263 }
12264
12265
12266 void
12267 EditTagsEvent()
12268 {
12269     char *tags = PGNTags(&gameInfo);
12270     EditTagsPopUp(tags, NULL);
12271     free(tags);
12272 }
12273
12274 void
12275 AnalyzeModeEvent()
12276 {
12277     if (appData.noChessProgram || gameMode == AnalyzeMode)
12278       return;
12279
12280     if (gameMode != AnalyzeFile) {
12281         if (!appData.icsEngineAnalyze) {
12282                EditGameEvent();
12283                if (gameMode != EditGame) return;
12284         }
12285         ResurrectChessProgram();
12286         SendToProgram("analyze\n", &first);
12287         first.analyzing = TRUE;
12288         /*first.maybeThinking = TRUE;*/
12289         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12290         EngineOutputPopUp();
12291     }
12292     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12293     pausing = FALSE;
12294     ModeHighlight();
12295     SetGameInfo();
12296
12297     StartAnalysisClock();
12298     GetTimeMark(&lastNodeCountTime);
12299     lastNodeCount = 0;
12300 }
12301
12302 void
12303 AnalyzeFileEvent()
12304 {
12305     if (appData.noChessProgram || gameMode == AnalyzeFile)
12306       return;
12307
12308     if (gameMode != AnalyzeMode) {
12309         EditGameEvent();
12310         if (gameMode != EditGame) return;
12311         ResurrectChessProgram();
12312         SendToProgram("analyze\n", &first);
12313         first.analyzing = TRUE;
12314         /*first.maybeThinking = TRUE;*/
12315         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12316         EngineOutputPopUp();
12317     }
12318     gameMode = AnalyzeFile;
12319     pausing = FALSE;
12320     ModeHighlight();
12321     SetGameInfo();
12322
12323     StartAnalysisClock();
12324     GetTimeMark(&lastNodeCountTime);
12325     lastNodeCount = 0;
12326 }
12327
12328 void
12329 MachineWhiteEvent()
12330 {
12331     char buf[MSG_SIZ];
12332     char *bookHit = NULL;
12333
12334     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12335       return;
12336
12337
12338     if (gameMode == PlayFromGameFile ||
12339         gameMode == TwoMachinesPlay  ||
12340         gameMode == Training         ||
12341         gameMode == AnalyzeMode      ||
12342         gameMode == EndOfGame)
12343         EditGameEvent();
12344
12345     if (gameMode == EditPosition)
12346         EditPositionDone(TRUE);
12347
12348     if (!WhiteOnMove(currentMove)) {
12349         DisplayError(_("It is not White's turn"), 0);
12350         return;
12351     }
12352
12353     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12354       ExitAnalyzeMode();
12355
12356     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12357         gameMode == AnalyzeFile)
12358         TruncateGame();
12359
12360     ResurrectChessProgram();    /* in case it isn't running */
12361     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12362         gameMode = MachinePlaysWhite;
12363         ResetClocks();
12364     } else
12365     gameMode = MachinePlaysWhite;
12366     pausing = FALSE;
12367     ModeHighlight();
12368     SetGameInfo();
12369     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12370     DisplayTitle(buf);
12371     if (first.sendName) {
12372       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12373       SendToProgram(buf, &first);
12374     }
12375     if (first.sendTime) {
12376       if (first.useColors) {
12377         SendToProgram("black\n", &first); /*gnu kludge*/
12378       }
12379       SendTimeRemaining(&first, TRUE);
12380     }
12381     if (first.useColors) {
12382       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12383     }
12384     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12385     SetMachineThinkingEnables();
12386     first.maybeThinking = TRUE;
12387     StartClocks();
12388     firstMove = FALSE;
12389
12390     if (appData.autoFlipView && !flipView) {
12391       flipView = !flipView;
12392       DrawPosition(FALSE, NULL);
12393       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12394     }
12395
12396     if(bookHit) { // [HGM] book: simulate book reply
12397         static char bookMove[MSG_SIZ]; // a bit generous?
12398
12399         programStats.nodes = programStats.depth = programStats.time =
12400         programStats.score = programStats.got_only_move = 0;
12401         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12402
12403         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12404         strcat(bookMove, bookHit);
12405         HandleMachineMove(bookMove, &first);
12406     }
12407 }
12408
12409 void
12410 MachineBlackEvent()
12411 {
12412   char buf[MSG_SIZ];
12413   char *bookHit = NULL;
12414
12415     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12416         return;
12417
12418
12419     if (gameMode == PlayFromGameFile ||
12420         gameMode == TwoMachinesPlay  ||
12421         gameMode == Training         ||
12422         gameMode == AnalyzeMode      ||
12423         gameMode == EndOfGame)
12424         EditGameEvent();
12425
12426     if (gameMode == EditPosition)
12427         EditPositionDone(TRUE);
12428
12429     if (WhiteOnMove(currentMove)) {
12430         DisplayError(_("It is not Black's turn"), 0);
12431         return;
12432     }
12433
12434     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12435       ExitAnalyzeMode();
12436
12437     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12438         gameMode == AnalyzeFile)
12439         TruncateGame();
12440
12441     ResurrectChessProgram();    /* in case it isn't running */
12442     gameMode = MachinePlaysBlack;
12443     pausing = FALSE;
12444     ModeHighlight();
12445     SetGameInfo();
12446     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12447     DisplayTitle(buf);
12448     if (first.sendName) {
12449       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12450       SendToProgram(buf, &first);
12451     }
12452     if (first.sendTime) {
12453       if (first.useColors) {
12454         SendToProgram("white\n", &first); /*gnu kludge*/
12455       }
12456       SendTimeRemaining(&first, FALSE);
12457     }
12458     if (first.useColors) {
12459       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12460     }
12461     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12462     SetMachineThinkingEnables();
12463     first.maybeThinking = TRUE;
12464     StartClocks();
12465
12466     if (appData.autoFlipView && flipView) {
12467       flipView = !flipView;
12468       DrawPosition(FALSE, NULL);
12469       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12470     }
12471     if(bookHit) { // [HGM] book: simulate book reply
12472         static char bookMove[MSG_SIZ]; // a bit generous?
12473
12474         programStats.nodes = programStats.depth = programStats.time =
12475         programStats.score = programStats.got_only_move = 0;
12476         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12477
12478         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12479         strcat(bookMove, bookHit);
12480         HandleMachineMove(bookMove, &first);
12481     }
12482 }
12483
12484
12485 void
12486 DisplayTwoMachinesTitle()
12487 {
12488     char buf[MSG_SIZ];
12489     if (appData.matchGames > 0) {
12490         if (first.twoMachinesColor[0] == 'w') {
12491           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12492                    gameInfo.white, gameInfo.black,
12493                    first.matchWins, second.matchWins,
12494                    matchGame - 1 - (first.matchWins + second.matchWins));
12495         } else {
12496           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12497                    gameInfo.white, gameInfo.black,
12498                    second.matchWins, first.matchWins,
12499                    matchGame - 1 - (first.matchWins + second.matchWins));
12500         }
12501     } else {
12502       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12503     }
12504     DisplayTitle(buf);
12505 }
12506
12507 void
12508 SettingsMenuIfReady()
12509 {
12510   if (second.lastPing != second.lastPong) {
12511     DisplayMessage("", _("Waiting for second chess program"));
12512     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12513     return;
12514   }
12515   ThawUI();
12516   DisplayMessage("", "");
12517   SettingsPopUp(&second);
12518 }
12519
12520 int
12521 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12522 {
12523     char buf[MSG_SIZ];
12524     if (cps->pr == NULL) {
12525         StartChessProgram(cps);
12526         if (cps->protocolVersion == 1) {
12527           retry();
12528         } else {
12529           /* kludge: allow timeout for initial "feature" command */
12530           FreezeUI();
12531           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12532           DisplayMessage("", buf);
12533           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12534         }
12535         return 1;
12536     }
12537     return 0;
12538 }
12539
12540 void
12541 TwoMachinesEvent P((void))
12542 {
12543     int i;
12544     char buf[MSG_SIZ];
12545     ChessProgramState *onmove;
12546     char *bookHit = NULL;
12547     static int stalling = 0;
12548     TimeMark now;
12549     long wait;
12550
12551     if (appData.noChessProgram) return;
12552
12553     switch (gameMode) {
12554       case TwoMachinesPlay:
12555         return;
12556       case MachinePlaysWhite:
12557       case MachinePlaysBlack:
12558         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12559             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12560             return;
12561         }
12562         /* fall through */
12563       case BeginningOfGame:
12564       case PlayFromGameFile:
12565       case EndOfGame:
12566         EditGameEvent();
12567         if (gameMode != EditGame) return;
12568         break;
12569       case EditPosition:
12570         EditPositionDone(TRUE);
12571         break;
12572       case AnalyzeMode:
12573       case AnalyzeFile:
12574         ExitAnalyzeMode();
12575         break;
12576       case EditGame:
12577       default:
12578         break;
12579     }
12580
12581 //    forwardMostMove = currentMove;
12582     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12583
12584     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12585
12586     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12587     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12588       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12589       return;
12590     }
12591     if(!stalling) {
12592       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12593       SendToProgram("force\n", &second);
12594       stalling = 1;
12595       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12596       return;
12597     }
12598     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12599     if(appData.matchPause>10000 || appData.matchPause<10)
12600                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12601     wait = SubtractTimeMarks(&now, &pauseStart);
12602     if(wait < appData.matchPause) {
12603         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12604         return;
12605     }
12606     stalling = 0;
12607     DisplayMessage("", "");
12608     if (startedFromSetupPosition) {
12609         SendBoard(&second, backwardMostMove);
12610     if (appData.debugMode) {
12611         fprintf(debugFP, "Two Machines\n");
12612     }
12613     }
12614     for (i = backwardMostMove; i < forwardMostMove; i++) {
12615         SendMoveToProgram(i, &second);
12616     }
12617
12618     gameMode = TwoMachinesPlay;
12619     pausing = FALSE;
12620     ModeHighlight();
12621     SetGameInfo();
12622     DisplayTwoMachinesTitle();
12623     firstMove = TRUE;
12624     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12625         onmove = &first;
12626     } else {
12627         onmove = &second;
12628     }
12629     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12630     SendToProgram(first.computerString, &first);
12631     if (first.sendName) {
12632       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12633       SendToProgram(buf, &first);
12634     }
12635     SendToProgram(second.computerString, &second);
12636     if (second.sendName) {
12637       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12638       SendToProgram(buf, &second);
12639     }
12640
12641     ResetClocks();
12642     if (!first.sendTime || !second.sendTime) {
12643         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12644         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12645     }
12646     if (onmove->sendTime) {
12647       if (onmove->useColors) {
12648         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12649       }
12650       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12651     }
12652     if (onmove->useColors) {
12653       SendToProgram(onmove->twoMachinesColor, onmove);
12654     }
12655     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12656 //    SendToProgram("go\n", onmove);
12657     onmove->maybeThinking = TRUE;
12658     SetMachineThinkingEnables();
12659
12660     StartClocks();
12661
12662     if(bookHit) { // [HGM] book: simulate book reply
12663         static char bookMove[MSG_SIZ]; // a bit generous?
12664
12665         programStats.nodes = programStats.depth = programStats.time =
12666         programStats.score = programStats.got_only_move = 0;
12667         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12668
12669         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12670         strcat(bookMove, bookHit);
12671         savedMessage = bookMove; // args for deferred call
12672         savedState = onmove;
12673         ScheduleDelayedEvent(DeferredBookMove, 1);
12674     }
12675 }
12676
12677 void
12678 TrainingEvent()
12679 {
12680     if (gameMode == Training) {
12681       SetTrainingModeOff();
12682       gameMode = PlayFromGameFile;
12683       DisplayMessage("", _("Training mode off"));
12684     } else {
12685       gameMode = Training;
12686       animateTraining = appData.animate;
12687
12688       /* make sure we are not already at the end of the game */
12689       if (currentMove < forwardMostMove) {
12690         SetTrainingModeOn();
12691         DisplayMessage("", _("Training mode on"));
12692       } else {
12693         gameMode = PlayFromGameFile;
12694         DisplayError(_("Already at end of game"), 0);
12695       }
12696     }
12697     ModeHighlight();
12698 }
12699
12700 void
12701 IcsClientEvent()
12702 {
12703     if (!appData.icsActive) return;
12704     switch (gameMode) {
12705       case IcsPlayingWhite:
12706       case IcsPlayingBlack:
12707       case IcsObserving:
12708       case IcsIdle:
12709       case BeginningOfGame:
12710       case IcsExamining:
12711         return;
12712
12713       case EditGame:
12714         break;
12715
12716       case EditPosition:
12717         EditPositionDone(TRUE);
12718         break;
12719
12720       case AnalyzeMode:
12721       case AnalyzeFile:
12722         ExitAnalyzeMode();
12723         break;
12724
12725       default:
12726         EditGameEvent();
12727         break;
12728     }
12729
12730     gameMode = IcsIdle;
12731     ModeHighlight();
12732     return;
12733 }
12734
12735
12736 void
12737 EditGameEvent()
12738 {
12739     int i;
12740
12741     switch (gameMode) {
12742       case Training:
12743         SetTrainingModeOff();
12744         break;
12745       case MachinePlaysWhite:
12746       case MachinePlaysBlack:
12747       case BeginningOfGame:
12748         SendToProgram("force\n", &first);
12749         SetUserThinkingEnables();
12750         break;
12751       case PlayFromGameFile:
12752         (void) StopLoadGameTimer();
12753         if (gameFileFP != NULL) {
12754             gameFileFP = NULL;
12755         }
12756         break;
12757       case EditPosition:
12758         EditPositionDone(TRUE);
12759         break;
12760       case AnalyzeMode:
12761       case AnalyzeFile:
12762         ExitAnalyzeMode();
12763         SendToProgram("force\n", &first);
12764         break;
12765       case TwoMachinesPlay:
12766         GameEnds(EndOfFile, NULL, GE_PLAYER);
12767         ResurrectChessProgram();
12768         SetUserThinkingEnables();
12769         break;
12770       case EndOfGame:
12771         ResurrectChessProgram();
12772         break;
12773       case IcsPlayingBlack:
12774       case IcsPlayingWhite:
12775         DisplayError(_("Warning: You are still playing a game"), 0);
12776         break;
12777       case IcsObserving:
12778         DisplayError(_("Warning: You are still observing a game"), 0);
12779         break;
12780       case IcsExamining:
12781         DisplayError(_("Warning: You are still examining a game"), 0);
12782         break;
12783       case IcsIdle:
12784         break;
12785       case EditGame:
12786       default:
12787         return;
12788     }
12789
12790     pausing = FALSE;
12791     StopClocks();
12792     first.offeredDraw = second.offeredDraw = 0;
12793
12794     if (gameMode == PlayFromGameFile) {
12795         whiteTimeRemaining = timeRemaining[0][currentMove];
12796         blackTimeRemaining = timeRemaining[1][currentMove];
12797         DisplayTitle("");
12798     }
12799
12800     if (gameMode == MachinePlaysWhite ||
12801         gameMode == MachinePlaysBlack ||
12802         gameMode == TwoMachinesPlay ||
12803         gameMode == EndOfGame) {
12804         i = forwardMostMove;
12805         while (i > currentMove) {
12806             SendToProgram("undo\n", &first);
12807             i--;
12808         }
12809         whiteTimeRemaining = timeRemaining[0][currentMove];
12810         blackTimeRemaining = timeRemaining[1][currentMove];
12811         DisplayBothClocks();
12812         if (whiteFlag || blackFlag) {
12813             whiteFlag = blackFlag = 0;
12814         }
12815         DisplayTitle("");
12816     }
12817
12818     gameMode = EditGame;
12819     ModeHighlight();
12820     SetGameInfo();
12821 }
12822
12823
12824 void
12825 EditPositionEvent()
12826 {
12827     if (gameMode == EditPosition) {
12828         EditGameEvent();
12829         return;
12830     }
12831
12832     EditGameEvent();
12833     if (gameMode != EditGame) return;
12834
12835     gameMode = EditPosition;
12836     ModeHighlight();
12837     SetGameInfo();
12838     if (currentMove > 0)
12839       CopyBoard(boards[0], boards[currentMove]);
12840
12841     blackPlaysFirst = !WhiteOnMove(currentMove);
12842     ResetClocks();
12843     currentMove = forwardMostMove = backwardMostMove = 0;
12844     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12845     DisplayMove(-1);
12846 }
12847
12848 void
12849 ExitAnalyzeMode()
12850 {
12851     /* [DM] icsEngineAnalyze - possible call from other functions */
12852     if (appData.icsEngineAnalyze) {
12853         appData.icsEngineAnalyze = FALSE;
12854
12855         DisplayMessage("",_("Close ICS engine analyze..."));
12856     }
12857     if (first.analysisSupport && first.analyzing) {
12858       SendToProgram("exit\n", &first);
12859       first.analyzing = FALSE;
12860     }
12861     thinkOutput[0] = NULLCHAR;
12862 }
12863
12864 void
12865 EditPositionDone(Boolean fakeRights)
12866 {
12867     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12868
12869     startedFromSetupPosition = TRUE;
12870     InitChessProgram(&first, FALSE);
12871     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12872       boards[0][EP_STATUS] = EP_NONE;
12873       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12874     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12875         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12876         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12877       } else boards[0][CASTLING][2] = NoRights;
12878     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12879         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12880         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12881       } else boards[0][CASTLING][5] = NoRights;
12882     }
12883     SendToProgram("force\n", &first);
12884     if (blackPlaysFirst) {
12885         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12886         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12887         currentMove = forwardMostMove = backwardMostMove = 1;
12888         CopyBoard(boards[1], boards[0]);
12889     } else {
12890         currentMove = forwardMostMove = backwardMostMove = 0;
12891     }
12892     SendBoard(&first, forwardMostMove);
12893     if (appData.debugMode) {
12894         fprintf(debugFP, "EditPosDone\n");
12895     }
12896     DisplayTitle("");
12897     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12898     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12899     gameMode = EditGame;
12900     ModeHighlight();
12901     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12902     ClearHighlights(); /* [AS] */
12903 }
12904
12905 /* Pause for `ms' milliseconds */
12906 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12907 void
12908 TimeDelay(ms)
12909      long ms;
12910 {
12911     TimeMark m1, m2;
12912
12913     GetTimeMark(&m1);
12914     do {
12915         GetTimeMark(&m2);
12916     } while (SubtractTimeMarks(&m2, &m1) < ms);
12917 }
12918
12919 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12920 void
12921 SendMultiLineToICS(buf)
12922      char *buf;
12923 {
12924     char temp[MSG_SIZ+1], *p;
12925     int len;
12926
12927     len = strlen(buf);
12928     if (len > MSG_SIZ)
12929       len = MSG_SIZ;
12930
12931     strncpy(temp, buf, len);
12932     temp[len] = 0;
12933
12934     p = temp;
12935     while (*p) {
12936         if (*p == '\n' || *p == '\r')
12937           *p = ' ';
12938         ++p;
12939     }
12940
12941     strcat(temp, "\n");
12942     SendToICS(temp);
12943     SendToPlayer(temp, strlen(temp));
12944 }
12945
12946 void
12947 SetWhiteToPlayEvent()
12948 {
12949     if (gameMode == EditPosition) {
12950         blackPlaysFirst = FALSE;
12951         DisplayBothClocks();    /* works because currentMove is 0 */
12952     } else if (gameMode == IcsExamining) {
12953         SendToICS(ics_prefix);
12954         SendToICS("tomove white\n");
12955     }
12956 }
12957
12958 void
12959 SetBlackToPlayEvent()
12960 {
12961     if (gameMode == EditPosition) {
12962         blackPlaysFirst = TRUE;
12963         currentMove = 1;        /* kludge */
12964         DisplayBothClocks();
12965         currentMove = 0;
12966     } else if (gameMode == IcsExamining) {
12967         SendToICS(ics_prefix);
12968         SendToICS("tomove black\n");
12969     }
12970 }
12971
12972 void
12973 EditPositionMenuEvent(selection, x, y)
12974      ChessSquare selection;
12975      int x, y;
12976 {
12977     char buf[MSG_SIZ];
12978     ChessSquare piece = boards[0][y][x];
12979
12980     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12981
12982     switch (selection) {
12983       case ClearBoard:
12984         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12985             SendToICS(ics_prefix);
12986             SendToICS("bsetup clear\n");
12987         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12988             SendToICS(ics_prefix);
12989             SendToICS("clearboard\n");
12990         } else {
12991             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12992                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12993                 for (y = 0; y < BOARD_HEIGHT; y++) {
12994                     if (gameMode == IcsExamining) {
12995                         if (boards[currentMove][y][x] != EmptySquare) {
12996                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12997                                     AAA + x, ONE + y);
12998                             SendToICS(buf);
12999                         }
13000                     } else {
13001                         boards[0][y][x] = p;
13002                     }
13003                 }
13004             }
13005         }
13006         if (gameMode == EditPosition) {
13007             DrawPosition(FALSE, boards[0]);
13008         }
13009         break;
13010
13011       case WhitePlay:
13012         SetWhiteToPlayEvent();
13013         break;
13014
13015       case BlackPlay:
13016         SetBlackToPlayEvent();
13017         break;
13018
13019       case EmptySquare:
13020         if (gameMode == IcsExamining) {
13021             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13022             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13023             SendToICS(buf);
13024         } else {
13025             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13026                 if(x == BOARD_LEFT-2) {
13027                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13028                     boards[0][y][1] = 0;
13029                 } else
13030                 if(x == BOARD_RGHT+1) {
13031                     if(y >= gameInfo.holdingsSize) break;
13032                     boards[0][y][BOARD_WIDTH-2] = 0;
13033                 } else break;
13034             }
13035             boards[0][y][x] = EmptySquare;
13036             DrawPosition(FALSE, boards[0]);
13037         }
13038         break;
13039
13040       case PromotePiece:
13041         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13042            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13043             selection = (ChessSquare) (PROMOTED piece);
13044         } else if(piece == EmptySquare) selection = WhiteSilver;
13045         else selection = (ChessSquare)((int)piece - 1);
13046         goto defaultlabel;
13047
13048       case DemotePiece:
13049         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13050            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13051             selection = (ChessSquare) (DEMOTED piece);
13052         } else if(piece == EmptySquare) selection = BlackSilver;
13053         else selection = (ChessSquare)((int)piece + 1);
13054         goto defaultlabel;
13055
13056       case WhiteQueen:
13057       case BlackQueen:
13058         if(gameInfo.variant == VariantShatranj ||
13059            gameInfo.variant == VariantXiangqi  ||
13060            gameInfo.variant == VariantCourier  ||
13061            gameInfo.variant == VariantMakruk     )
13062             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13063         goto defaultlabel;
13064
13065       case WhiteKing:
13066       case BlackKing:
13067         if(gameInfo.variant == VariantXiangqi)
13068             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13069         if(gameInfo.variant == VariantKnightmate)
13070             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13071       default:
13072         defaultlabel:
13073         if (gameMode == IcsExamining) {
13074             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13075             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13076                      PieceToChar(selection), AAA + x, ONE + y);
13077             SendToICS(buf);
13078         } else {
13079             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13080                 int n;
13081                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13082                     n = PieceToNumber(selection - BlackPawn);
13083                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13084                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13085                     boards[0][BOARD_HEIGHT-1-n][1]++;
13086                 } else
13087                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13088                     n = PieceToNumber(selection);
13089                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13090                     boards[0][n][BOARD_WIDTH-1] = selection;
13091                     boards[0][n][BOARD_WIDTH-2]++;
13092                 }
13093             } else
13094             boards[0][y][x] = selection;
13095             DrawPosition(TRUE, boards[0]);
13096         }
13097         break;
13098     }
13099 }
13100
13101
13102 void
13103 DropMenuEvent(selection, x, y)
13104      ChessSquare selection;
13105      int x, y;
13106 {
13107     ChessMove moveType;
13108
13109     switch (gameMode) {
13110       case IcsPlayingWhite:
13111       case MachinePlaysBlack:
13112         if (!WhiteOnMove(currentMove)) {
13113             DisplayMoveError(_("It is Black's turn"));
13114             return;
13115         }
13116         moveType = WhiteDrop;
13117         break;
13118       case IcsPlayingBlack:
13119       case MachinePlaysWhite:
13120         if (WhiteOnMove(currentMove)) {
13121             DisplayMoveError(_("It is White's turn"));
13122             return;
13123         }
13124         moveType = BlackDrop;
13125         break;
13126       case EditGame:
13127         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13128         break;
13129       default:
13130         return;
13131     }
13132
13133     if (moveType == BlackDrop && selection < BlackPawn) {
13134       selection = (ChessSquare) ((int) selection
13135                                  + (int) BlackPawn - (int) WhitePawn);
13136     }
13137     if (boards[currentMove][y][x] != EmptySquare) {
13138         DisplayMoveError(_("That square is occupied"));
13139         return;
13140     }
13141
13142     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13143 }
13144
13145 void
13146 AcceptEvent()
13147 {
13148     /* Accept a pending offer of any kind from opponent */
13149
13150     if (appData.icsActive) {
13151         SendToICS(ics_prefix);
13152         SendToICS("accept\n");
13153     } else if (cmailMsgLoaded) {
13154         if (currentMove == cmailOldMove &&
13155             commentList[cmailOldMove] != NULL &&
13156             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13157                    "Black offers a draw" : "White offers a draw")) {
13158             TruncateGame();
13159             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13160             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13161         } else {
13162             DisplayError(_("There is no pending offer on this move"), 0);
13163             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13164         }
13165     } else {
13166         /* Not used for offers from chess program */
13167     }
13168 }
13169
13170 void
13171 DeclineEvent()
13172 {
13173     /* Decline a pending offer of any kind from opponent */
13174
13175     if (appData.icsActive) {
13176         SendToICS(ics_prefix);
13177         SendToICS("decline\n");
13178     } else if (cmailMsgLoaded) {
13179         if (currentMove == cmailOldMove &&
13180             commentList[cmailOldMove] != NULL &&
13181             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13182                    "Black offers a draw" : "White offers a draw")) {
13183 #ifdef NOTDEF
13184             AppendComment(cmailOldMove, "Draw declined", TRUE);
13185             DisplayComment(cmailOldMove - 1, "Draw declined");
13186 #endif /*NOTDEF*/
13187         } else {
13188             DisplayError(_("There is no pending offer on this move"), 0);
13189         }
13190     } else {
13191         /* Not used for offers from chess program */
13192     }
13193 }
13194
13195 void
13196 RematchEvent()
13197 {
13198     /* Issue ICS rematch command */
13199     if (appData.icsActive) {
13200         SendToICS(ics_prefix);
13201         SendToICS("rematch\n");
13202     }
13203 }
13204
13205 void
13206 CallFlagEvent()
13207 {
13208     /* Call your opponent's flag (claim a win on time) */
13209     if (appData.icsActive) {
13210         SendToICS(ics_prefix);
13211         SendToICS("flag\n");
13212     } else {
13213         switch (gameMode) {
13214           default:
13215             return;
13216           case MachinePlaysWhite:
13217             if (whiteFlag) {
13218                 if (blackFlag)
13219                   GameEnds(GameIsDrawn, "Both players ran out of time",
13220                            GE_PLAYER);
13221                 else
13222                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13223             } else {
13224                 DisplayError(_("Your opponent is not out of time"), 0);
13225             }
13226             break;
13227           case MachinePlaysBlack:
13228             if (blackFlag) {
13229                 if (whiteFlag)
13230                   GameEnds(GameIsDrawn, "Both players ran out of time",
13231                            GE_PLAYER);
13232                 else
13233                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13234             } else {
13235                 DisplayError(_("Your opponent is not out of time"), 0);
13236             }
13237             break;
13238         }
13239     }
13240 }
13241
13242 void
13243 ClockClick(int which)
13244 {       // [HGM] code moved to back-end from winboard.c
13245         if(which) { // black clock
13246           if (gameMode == EditPosition || gameMode == IcsExamining) {
13247             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13248             SetBlackToPlayEvent();
13249           } else if (gameMode == EditGame || shiftKey) {
13250             AdjustClock(which, -1);
13251           } else if (gameMode == IcsPlayingWhite ||
13252                      gameMode == MachinePlaysBlack) {
13253             CallFlagEvent();
13254           }
13255         } else { // white clock
13256           if (gameMode == EditPosition || gameMode == IcsExamining) {
13257             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13258             SetWhiteToPlayEvent();
13259           } else if (gameMode == EditGame || shiftKey) {
13260             AdjustClock(which, -1);
13261           } else if (gameMode == IcsPlayingBlack ||
13262                    gameMode == MachinePlaysWhite) {
13263             CallFlagEvent();
13264           }
13265         }
13266 }
13267
13268 void
13269 DrawEvent()
13270 {
13271     /* Offer draw or accept pending draw offer from opponent */
13272
13273     if (appData.icsActive) {
13274         /* Note: tournament rules require draw offers to be
13275            made after you make your move but before you punch
13276            your clock.  Currently ICS doesn't let you do that;
13277            instead, you immediately punch your clock after making
13278            a move, but you can offer a draw at any time. */
13279
13280         SendToICS(ics_prefix);
13281         SendToICS("draw\n");
13282         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13283     } else if (cmailMsgLoaded) {
13284         if (currentMove == cmailOldMove &&
13285             commentList[cmailOldMove] != NULL &&
13286             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13287                    "Black offers a draw" : "White offers a draw")) {
13288             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13289             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13290         } else if (currentMove == cmailOldMove + 1) {
13291             char *offer = WhiteOnMove(cmailOldMove) ?
13292               "White offers a draw" : "Black offers a draw";
13293             AppendComment(currentMove, offer, TRUE);
13294             DisplayComment(currentMove - 1, offer);
13295             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13296         } else {
13297             DisplayError(_("You must make your move before offering a draw"), 0);
13298             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13299         }
13300     } else if (first.offeredDraw) {
13301         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13302     } else {
13303         if (first.sendDrawOffers) {
13304             SendToProgram("draw\n", &first);
13305             userOfferedDraw = TRUE;
13306         }
13307     }
13308 }
13309
13310 void
13311 AdjournEvent()
13312 {
13313     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13314
13315     if (appData.icsActive) {
13316         SendToICS(ics_prefix);
13317         SendToICS("adjourn\n");
13318     } else {
13319         /* Currently GNU Chess doesn't offer or accept Adjourns */
13320     }
13321 }
13322
13323
13324 void
13325 AbortEvent()
13326 {
13327     /* Offer Abort or accept pending Abort offer from opponent */
13328
13329     if (appData.icsActive) {
13330         SendToICS(ics_prefix);
13331         SendToICS("abort\n");
13332     } else {
13333         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13334     }
13335 }
13336
13337 void
13338 ResignEvent()
13339 {
13340     /* Resign.  You can do this even if it's not your turn. */
13341
13342     if (appData.icsActive) {
13343         SendToICS(ics_prefix);
13344         SendToICS("resign\n");
13345     } else {
13346         switch (gameMode) {
13347           case MachinePlaysWhite:
13348             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13349             break;
13350           case MachinePlaysBlack:
13351             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13352             break;
13353           case EditGame:
13354             if (cmailMsgLoaded) {
13355                 TruncateGame();
13356                 if (WhiteOnMove(cmailOldMove)) {
13357                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13358                 } else {
13359                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13360                 }
13361                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13362             }
13363             break;
13364           default:
13365             break;
13366         }
13367     }
13368 }
13369
13370
13371 void
13372 StopObservingEvent()
13373 {
13374     /* Stop observing current games */
13375     SendToICS(ics_prefix);
13376     SendToICS("unobserve\n");
13377 }
13378
13379 void
13380 StopExaminingEvent()
13381 {
13382     /* Stop observing current game */
13383     SendToICS(ics_prefix);
13384     SendToICS("unexamine\n");
13385 }
13386
13387 void
13388 ForwardInner(target)
13389      int target;
13390 {
13391     int limit;
13392
13393     if (appData.debugMode)
13394         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13395                 target, currentMove, forwardMostMove);
13396
13397     if (gameMode == EditPosition)
13398       return;
13399
13400     if (gameMode == PlayFromGameFile && !pausing)
13401       PauseEvent();
13402
13403     if (gameMode == IcsExamining && pausing)
13404       limit = pauseExamForwardMostMove;
13405     else
13406       limit = forwardMostMove;
13407
13408     if (target > limit) target = limit;
13409
13410     if (target > 0 && moveList[target - 1][0]) {
13411         int fromX, fromY, toX, toY;
13412         toX = moveList[target - 1][2] - AAA;
13413         toY = moveList[target - 1][3] - ONE;
13414         if (moveList[target - 1][1] == '@') {
13415             if (appData.highlightLastMove) {
13416                 SetHighlights(-1, -1, toX, toY);
13417             }
13418         } else {
13419             fromX = moveList[target - 1][0] - AAA;
13420             fromY = moveList[target - 1][1] - ONE;
13421             if (target == currentMove + 1) {
13422                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13423             }
13424             if (appData.highlightLastMove) {
13425                 SetHighlights(fromX, fromY, toX, toY);
13426             }
13427         }
13428     }
13429     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13430         gameMode == Training || gameMode == PlayFromGameFile ||
13431         gameMode == AnalyzeFile) {
13432         while (currentMove < target) {
13433             SendMoveToProgram(currentMove++, &first);
13434         }
13435     } else {
13436         currentMove = target;
13437     }
13438
13439     if (gameMode == EditGame || gameMode == EndOfGame) {
13440         whiteTimeRemaining = timeRemaining[0][currentMove];
13441         blackTimeRemaining = timeRemaining[1][currentMove];
13442     }
13443     DisplayBothClocks();
13444     DisplayMove(currentMove - 1);
13445     DrawPosition(FALSE, boards[currentMove]);
13446     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13447     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13448         DisplayComment(currentMove - 1, commentList[currentMove]);
13449     }
13450 }
13451
13452
13453 void
13454 ForwardEvent()
13455 {
13456     if (gameMode == IcsExamining && !pausing) {
13457         SendToICS(ics_prefix);
13458         SendToICS("forward\n");
13459     } else {
13460         ForwardInner(currentMove + 1);
13461     }
13462 }
13463
13464 void
13465 ToEndEvent()
13466 {
13467     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13468         /* to optimze, we temporarily turn off analysis mode while we feed
13469          * the remaining moves to the engine. Otherwise we get analysis output
13470          * after each move.
13471          */
13472         if (first.analysisSupport) {
13473           SendToProgram("exit\nforce\n", &first);
13474           first.analyzing = FALSE;
13475         }
13476     }
13477
13478     if (gameMode == IcsExamining && !pausing) {
13479         SendToICS(ics_prefix);
13480         SendToICS("forward 999999\n");
13481     } else {
13482         ForwardInner(forwardMostMove);
13483     }
13484
13485     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13486         /* we have fed all the moves, so reactivate analysis mode */
13487         SendToProgram("analyze\n", &first);
13488         first.analyzing = TRUE;
13489         /*first.maybeThinking = TRUE;*/
13490         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13491     }
13492 }
13493
13494 void
13495 BackwardInner(target)
13496      int target;
13497 {
13498     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13499
13500     if (appData.debugMode)
13501         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13502                 target, currentMove, forwardMostMove);
13503
13504     if (gameMode == EditPosition) return;
13505     if (currentMove <= backwardMostMove) {
13506         ClearHighlights();
13507         DrawPosition(full_redraw, boards[currentMove]);
13508         return;
13509     }
13510     if (gameMode == PlayFromGameFile && !pausing)
13511       PauseEvent();
13512
13513     if (moveList[target][0]) {
13514         int fromX, fromY, toX, toY;
13515         toX = moveList[target][2] - AAA;
13516         toY = moveList[target][3] - ONE;
13517         if (moveList[target][1] == '@') {
13518             if (appData.highlightLastMove) {
13519                 SetHighlights(-1, -1, toX, toY);
13520             }
13521         } else {
13522             fromX = moveList[target][0] - AAA;
13523             fromY = moveList[target][1] - ONE;
13524             if (target == currentMove - 1) {
13525                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13526             }
13527             if (appData.highlightLastMove) {
13528                 SetHighlights(fromX, fromY, toX, toY);
13529             }
13530         }
13531     }
13532     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13533         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13534         while (currentMove > target) {
13535             SendToProgram("undo\n", &first);
13536             currentMove--;
13537         }
13538     } else {
13539         currentMove = target;
13540     }
13541
13542     if (gameMode == EditGame || gameMode == EndOfGame) {
13543         whiteTimeRemaining = timeRemaining[0][currentMove];
13544         blackTimeRemaining = timeRemaining[1][currentMove];
13545     }
13546     DisplayBothClocks();
13547     DisplayMove(currentMove - 1);
13548     DrawPosition(full_redraw, boards[currentMove]);
13549     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13550     // [HGM] PV info: routine tests if comment empty
13551     DisplayComment(currentMove - 1, commentList[currentMove]);
13552 }
13553
13554 void
13555 BackwardEvent()
13556 {
13557     if (gameMode == IcsExamining && !pausing) {
13558         SendToICS(ics_prefix);
13559         SendToICS("backward\n");
13560     } else {
13561         BackwardInner(currentMove - 1);
13562     }
13563 }
13564
13565 void
13566 ToStartEvent()
13567 {
13568     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13569         /* to optimize, we temporarily turn off analysis mode while we undo
13570          * all the moves. Otherwise we get analysis output after each undo.
13571          */
13572         if (first.analysisSupport) {
13573           SendToProgram("exit\nforce\n", &first);
13574           first.analyzing = FALSE;
13575         }
13576     }
13577
13578     if (gameMode == IcsExamining && !pausing) {
13579         SendToICS(ics_prefix);
13580         SendToICS("backward 999999\n");
13581     } else {
13582         BackwardInner(backwardMostMove);
13583     }
13584
13585     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13586         /* we have fed all the moves, so reactivate analysis mode */
13587         SendToProgram("analyze\n", &first);
13588         first.analyzing = TRUE;
13589         /*first.maybeThinking = TRUE;*/
13590         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13591     }
13592 }
13593
13594 void
13595 ToNrEvent(int to)
13596 {
13597   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13598   if (to >= forwardMostMove) to = forwardMostMove;
13599   if (to <= backwardMostMove) to = backwardMostMove;
13600   if (to < currentMove) {
13601     BackwardInner(to);
13602   } else {
13603     ForwardInner(to);
13604   }
13605 }
13606
13607 void
13608 RevertEvent(Boolean annotate)
13609 {
13610     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13611         return;
13612     }
13613     if (gameMode != IcsExamining) {
13614         DisplayError(_("You are not examining a game"), 0);
13615         return;
13616     }
13617     if (pausing) {
13618         DisplayError(_("You can't revert while pausing"), 0);
13619         return;
13620     }
13621     SendToICS(ics_prefix);
13622     SendToICS("revert\n");
13623 }
13624
13625 void
13626 RetractMoveEvent()
13627 {
13628     switch (gameMode) {
13629       case MachinePlaysWhite:
13630       case MachinePlaysBlack:
13631         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13632             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13633             return;
13634         }
13635         if (forwardMostMove < 2) return;
13636         currentMove = forwardMostMove = forwardMostMove - 2;
13637         whiteTimeRemaining = timeRemaining[0][currentMove];
13638         blackTimeRemaining = timeRemaining[1][currentMove];
13639         DisplayBothClocks();
13640         DisplayMove(currentMove - 1);
13641         ClearHighlights();/*!! could figure this out*/
13642         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13643         SendToProgram("remove\n", &first);
13644         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13645         break;
13646
13647       case BeginningOfGame:
13648       default:
13649         break;
13650
13651       case IcsPlayingWhite:
13652       case IcsPlayingBlack:
13653         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13654             SendToICS(ics_prefix);
13655             SendToICS("takeback 2\n");
13656         } else {
13657             SendToICS(ics_prefix);
13658             SendToICS("takeback 1\n");
13659         }
13660         break;
13661     }
13662 }
13663
13664 void
13665 MoveNowEvent()
13666 {
13667     ChessProgramState *cps;
13668
13669     switch (gameMode) {
13670       case MachinePlaysWhite:
13671         if (!WhiteOnMove(forwardMostMove)) {
13672             DisplayError(_("It is your turn"), 0);
13673             return;
13674         }
13675         cps = &first;
13676         break;
13677       case MachinePlaysBlack:
13678         if (WhiteOnMove(forwardMostMove)) {
13679             DisplayError(_("It is your turn"), 0);
13680             return;
13681         }
13682         cps = &first;
13683         break;
13684       case TwoMachinesPlay:
13685         if (WhiteOnMove(forwardMostMove) ==
13686             (first.twoMachinesColor[0] == 'w')) {
13687             cps = &first;
13688         } else {
13689             cps = &second;
13690         }
13691         break;
13692       case BeginningOfGame:
13693       default:
13694         return;
13695     }
13696     SendToProgram("?\n", cps);
13697 }
13698
13699 void
13700 TruncateGameEvent()
13701 {
13702     EditGameEvent();
13703     if (gameMode != EditGame) return;
13704     TruncateGame();
13705 }
13706
13707 void
13708 TruncateGame()
13709 {
13710     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13711     if (forwardMostMove > currentMove) {
13712         if (gameInfo.resultDetails != NULL) {
13713             free(gameInfo.resultDetails);
13714             gameInfo.resultDetails = NULL;
13715             gameInfo.result = GameUnfinished;
13716         }
13717         forwardMostMove = currentMove;
13718         HistorySet(parseList, backwardMostMove, forwardMostMove,
13719                    currentMove-1);
13720     }
13721 }
13722
13723 void
13724 HintEvent()
13725 {
13726     if (appData.noChessProgram) return;
13727     switch (gameMode) {
13728       case MachinePlaysWhite:
13729         if (WhiteOnMove(forwardMostMove)) {
13730             DisplayError(_("Wait until your turn"), 0);
13731             return;
13732         }
13733         break;
13734       case BeginningOfGame:
13735       case MachinePlaysBlack:
13736         if (!WhiteOnMove(forwardMostMove)) {
13737             DisplayError(_("Wait until your turn"), 0);
13738             return;
13739         }
13740         break;
13741       default:
13742         DisplayError(_("No hint available"), 0);
13743         return;
13744     }
13745     SendToProgram("hint\n", &first);
13746     hintRequested = TRUE;
13747 }
13748
13749 void
13750 BookEvent()
13751 {
13752     if (appData.noChessProgram) return;
13753     switch (gameMode) {
13754       case MachinePlaysWhite:
13755         if (WhiteOnMove(forwardMostMove)) {
13756             DisplayError(_("Wait until your turn"), 0);
13757             return;
13758         }
13759         break;
13760       case BeginningOfGame:
13761       case MachinePlaysBlack:
13762         if (!WhiteOnMove(forwardMostMove)) {
13763             DisplayError(_("Wait until your turn"), 0);
13764             return;
13765         }
13766         break;
13767       case EditPosition:
13768         EditPositionDone(TRUE);
13769         break;
13770       case TwoMachinesPlay:
13771         return;
13772       default:
13773         break;
13774     }
13775     SendToProgram("bk\n", &first);
13776     bookOutput[0] = NULLCHAR;
13777     bookRequested = TRUE;
13778 }
13779
13780 void
13781 AboutGameEvent()
13782 {
13783     char *tags = PGNTags(&gameInfo);
13784     TagsPopUp(tags, CmailMsg());
13785     free(tags);
13786 }
13787
13788 /* end button procedures */
13789
13790 void
13791 PrintPosition(fp, move)
13792      FILE *fp;
13793      int move;
13794 {
13795     int i, j;
13796
13797     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13798         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13799             char c = PieceToChar(boards[move][i][j]);
13800             fputc(c == 'x' ? '.' : c, fp);
13801             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13802         }
13803     }
13804     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13805       fprintf(fp, "white to play\n");
13806     else
13807       fprintf(fp, "black to play\n");
13808 }
13809
13810 void
13811 PrintOpponents(fp)
13812      FILE *fp;
13813 {
13814     if (gameInfo.white != NULL) {
13815         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13816     } else {
13817         fprintf(fp, "\n");
13818     }
13819 }
13820
13821 /* Find last component of program's own name, using some heuristics */
13822 void
13823 TidyProgramName(prog, host, buf)
13824      char *prog, *host, buf[MSG_SIZ];
13825 {
13826     char *p, *q;
13827     int local = (strcmp(host, "localhost") == 0);
13828     while (!local && (p = strchr(prog, ';')) != NULL) {
13829         p++;
13830         while (*p == ' ') p++;
13831         prog = p;
13832     }
13833     if (*prog == '"' || *prog == '\'') {
13834         q = strchr(prog + 1, *prog);
13835     } else {
13836         q = strchr(prog, ' ');
13837     }
13838     if (q == NULL) q = prog + strlen(prog);
13839     p = q;
13840     while (p >= prog && *p != '/' && *p != '\\') p--;
13841     p++;
13842     if(p == prog && *p == '"') p++;
13843     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13844     memcpy(buf, p, q - p);
13845     buf[q - p] = NULLCHAR;
13846     if (!local) {
13847         strcat(buf, "@");
13848         strcat(buf, host);
13849     }
13850 }
13851
13852 char *
13853 TimeControlTagValue()
13854 {
13855     char buf[MSG_SIZ];
13856     if (!appData.clockMode) {
13857       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13858     } else if (movesPerSession > 0) {
13859       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13860     } else if (timeIncrement == 0) {
13861       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13862     } else {
13863       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13864     }
13865     return StrSave(buf);
13866 }
13867
13868 void
13869 SetGameInfo()
13870 {
13871     /* This routine is used only for certain modes */
13872     VariantClass v = gameInfo.variant;
13873     ChessMove r = GameUnfinished;
13874     char *p = NULL;
13875
13876     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13877         r = gameInfo.result;
13878         p = gameInfo.resultDetails;
13879         gameInfo.resultDetails = NULL;
13880     }
13881     ClearGameInfo(&gameInfo);
13882     gameInfo.variant = v;
13883
13884     switch (gameMode) {
13885       case MachinePlaysWhite:
13886         gameInfo.event = StrSave( appData.pgnEventHeader );
13887         gameInfo.site = StrSave(HostName());
13888         gameInfo.date = PGNDate();
13889         gameInfo.round = StrSave("-");
13890         gameInfo.white = StrSave(first.tidy);
13891         gameInfo.black = StrSave(UserName());
13892         gameInfo.timeControl = TimeControlTagValue();
13893         break;
13894
13895       case MachinePlaysBlack:
13896         gameInfo.event = StrSave( appData.pgnEventHeader );
13897         gameInfo.site = StrSave(HostName());
13898         gameInfo.date = PGNDate();
13899         gameInfo.round = StrSave("-");
13900         gameInfo.white = StrSave(UserName());
13901         gameInfo.black = StrSave(first.tidy);
13902         gameInfo.timeControl = TimeControlTagValue();
13903         break;
13904
13905       case TwoMachinesPlay:
13906         gameInfo.event = StrSave( appData.pgnEventHeader );
13907         gameInfo.site = StrSave(HostName());
13908         gameInfo.date = PGNDate();
13909         if (roundNr > 0) {
13910             char buf[MSG_SIZ];
13911             snprintf(buf, MSG_SIZ, "%d", roundNr);
13912             gameInfo.round = StrSave(buf);
13913         } else {
13914             gameInfo.round = StrSave("-");
13915         }
13916         if (first.twoMachinesColor[0] == 'w') {
13917             gameInfo.white = StrSave(first.tidy);
13918             gameInfo.black = StrSave(second.tidy);
13919         } else {
13920             gameInfo.white = StrSave(second.tidy);
13921             gameInfo.black = StrSave(first.tidy);
13922         }
13923         gameInfo.timeControl = TimeControlTagValue();
13924         break;
13925
13926       case EditGame:
13927         gameInfo.event = StrSave("Edited game");
13928         gameInfo.site = StrSave(HostName());
13929         gameInfo.date = PGNDate();
13930         gameInfo.round = StrSave("-");
13931         gameInfo.white = StrSave("-");
13932         gameInfo.black = StrSave("-");
13933         gameInfo.result = r;
13934         gameInfo.resultDetails = p;
13935         break;
13936
13937       case EditPosition:
13938         gameInfo.event = StrSave("Edited position");
13939         gameInfo.site = StrSave(HostName());
13940         gameInfo.date = PGNDate();
13941         gameInfo.round = StrSave("-");
13942         gameInfo.white = StrSave("-");
13943         gameInfo.black = StrSave("-");
13944         break;
13945
13946       case IcsPlayingWhite:
13947       case IcsPlayingBlack:
13948       case IcsObserving:
13949       case IcsExamining:
13950         break;
13951
13952       case PlayFromGameFile:
13953         gameInfo.event = StrSave("Game from non-PGN file");
13954         gameInfo.site = StrSave(HostName());
13955         gameInfo.date = PGNDate();
13956         gameInfo.round = StrSave("-");
13957         gameInfo.white = StrSave("?");
13958         gameInfo.black = StrSave("?");
13959         break;
13960
13961       default:
13962         break;
13963     }
13964 }
13965
13966 void
13967 ReplaceComment(index, text)
13968      int index;
13969      char *text;
13970 {
13971     int len;
13972     char *p;
13973     float score;
13974
13975     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13976        pvInfoList[index-1].depth == len &&
13977        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13978        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13979     while (*text == '\n') text++;
13980     len = strlen(text);
13981     while (len > 0 && text[len - 1] == '\n') len--;
13982
13983     if (commentList[index] != NULL)
13984       free(commentList[index]);
13985
13986     if (len == 0) {
13987         commentList[index] = NULL;
13988         return;
13989     }
13990   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13991       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13992       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13993     commentList[index] = (char *) malloc(len + 2);
13994     strncpy(commentList[index], text, len);
13995     commentList[index][len] = '\n';
13996     commentList[index][len + 1] = NULLCHAR;
13997   } else {
13998     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13999     char *p;
14000     commentList[index] = (char *) malloc(len + 7);
14001     safeStrCpy(commentList[index], "{\n", 3);
14002     safeStrCpy(commentList[index]+2, text, len+1);
14003     commentList[index][len+2] = NULLCHAR;
14004     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14005     strcat(commentList[index], "\n}\n");
14006   }
14007 }
14008
14009 void
14010 CrushCRs(text)
14011      char *text;
14012 {
14013   char *p = text;
14014   char *q = text;
14015   char ch;
14016
14017   do {
14018     ch = *p++;
14019     if (ch == '\r') continue;
14020     *q++ = ch;
14021   } while (ch != '\0');
14022 }
14023
14024 void
14025 AppendComment(index, text, addBraces)
14026      int index;
14027      char *text;
14028      Boolean addBraces; // [HGM] braces: tells if we should add {}
14029 {
14030     int oldlen, len;
14031     char *old;
14032
14033 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14034     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14035
14036     CrushCRs(text);
14037     while (*text == '\n') text++;
14038     len = strlen(text);
14039     while (len > 0 && text[len - 1] == '\n') len--;
14040
14041     if (len == 0) return;
14042
14043     if (commentList[index] != NULL) {
14044         old = commentList[index];
14045         oldlen = strlen(old);
14046         while(commentList[index][oldlen-1] ==  '\n')
14047           commentList[index][--oldlen] = NULLCHAR;
14048         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14049         safeStrCpy(commentList[index], old, oldlen + len + 6);
14050         free(old);
14051         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14052         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14053           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14054           while (*text == '\n') { text++; len--; }
14055           commentList[index][--oldlen] = NULLCHAR;
14056       }
14057         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14058         else          strcat(commentList[index], "\n");
14059         strcat(commentList[index], text);
14060         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14061         else          strcat(commentList[index], "\n");
14062     } else {
14063         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14064         if(addBraces)
14065           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14066         else commentList[index][0] = NULLCHAR;
14067         strcat(commentList[index], text);
14068         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14069         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14070     }
14071 }
14072
14073 static char * FindStr( char * text, char * sub_text )
14074 {
14075     char * result = strstr( text, sub_text );
14076
14077     if( result != NULL ) {
14078         result += strlen( sub_text );
14079     }
14080
14081     return result;
14082 }
14083
14084 /* [AS] Try to extract PV info from PGN comment */
14085 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14086 char *GetInfoFromComment( int index, char * text )
14087 {
14088     char * sep = text, *p;
14089
14090     if( text != NULL && index > 0 ) {
14091         int score = 0;
14092         int depth = 0;
14093         int time = -1, sec = 0, deci;
14094         char * s_eval = FindStr( text, "[%eval " );
14095         char * s_emt = FindStr( text, "[%emt " );
14096
14097         if( s_eval != NULL || s_emt != NULL ) {
14098             /* New style */
14099             char delim;
14100
14101             if( s_eval != NULL ) {
14102                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14103                     return text;
14104                 }
14105
14106                 if( delim != ']' ) {
14107                     return text;
14108                 }
14109             }
14110
14111             if( s_emt != NULL ) {
14112             }
14113                 return text;
14114         }
14115         else {
14116             /* We expect something like: [+|-]nnn.nn/dd */
14117             int score_lo = 0;
14118
14119             if(*text != '{') return text; // [HGM] braces: must be normal comment
14120
14121             sep = strchr( text, '/' );
14122             if( sep == NULL || sep < (text+4) ) {
14123                 return text;
14124             }
14125
14126             p = text;
14127             if(p[1] == '(') { // comment starts with PV
14128                p = strchr(p, ')'); // locate end of PV
14129                if(p == NULL || sep < p+5) return text;
14130                // at this point we have something like "{(.*) +0.23/6 ..."
14131                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14132                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14133                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14134             }
14135             time = -1; sec = -1; deci = -1;
14136             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14137                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14138                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14139                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14140                 return text;
14141             }
14142
14143             if( score_lo < 0 || score_lo >= 100 ) {
14144                 return text;
14145             }
14146
14147             if(sec >= 0) time = 600*time + 10*sec; else
14148             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14149
14150             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14151
14152             /* [HGM] PV time: now locate end of PV info */
14153             while( *++sep >= '0' && *sep <= '9'); // strip depth
14154             if(time >= 0)
14155             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14156             if(sec >= 0)
14157             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14158             if(deci >= 0)
14159             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14160             while(*sep == ' ') sep++;
14161         }
14162
14163         if( depth <= 0 ) {
14164             return text;
14165         }
14166
14167         if( time < 0 ) {
14168             time = -1;
14169         }
14170
14171         pvInfoList[index-1].depth = depth;
14172         pvInfoList[index-1].score = score;
14173         pvInfoList[index-1].time  = 10*time; // centi-sec
14174         if(*sep == '}') *sep = 0; else *--sep = '{';
14175         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14176     }
14177     return sep;
14178 }
14179
14180 void
14181 SendToProgram(message, cps)
14182      char *message;
14183      ChessProgramState *cps;
14184 {
14185     int count, outCount, error;
14186     char buf[MSG_SIZ];
14187
14188     if (cps->pr == NULL) return;
14189     Attention(cps);
14190
14191     if (appData.debugMode) {
14192         TimeMark now;
14193         GetTimeMark(&now);
14194         fprintf(debugFP, "%ld >%-6s: %s",
14195                 SubtractTimeMarks(&now, &programStartTime),
14196                 cps->which, message);
14197     }
14198
14199     count = strlen(message);
14200     outCount = OutputToProcess(cps->pr, message, count, &error);
14201     if (outCount < count && !exiting
14202                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14203       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14204       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14205         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14206             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14207                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14208                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14209                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14210             } else {
14211                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14212                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14213                 gameInfo.result = res;
14214             }
14215             gameInfo.resultDetails = StrSave(buf);
14216         }
14217         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14218         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14219     }
14220 }
14221
14222 void
14223 ReceiveFromProgram(isr, closure, message, count, error)
14224      InputSourceRef isr;
14225      VOIDSTAR closure;
14226      char *message;
14227      int count;
14228      int error;
14229 {
14230     char *end_str;
14231     char buf[MSG_SIZ];
14232     ChessProgramState *cps = (ChessProgramState *)closure;
14233
14234     if (isr != cps->isr) return; /* Killed intentionally */
14235     if (count <= 0) {
14236         if (count == 0) {
14237             RemoveInputSource(cps->isr);
14238             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14239                     _(cps->which), cps->program);
14240         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14241                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14242                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14243                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14244                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14245                 } else {
14246                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14247                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14248                     gameInfo.result = res;
14249                 }
14250                 gameInfo.resultDetails = StrSave(buf);
14251             }
14252             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14253             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14254         } else {
14255             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14256                     _(cps->which), cps->program);
14257             RemoveInputSource(cps->isr);
14258
14259             /* [AS] Program is misbehaving badly... kill it */
14260             if( count == -2 ) {
14261                 DestroyChildProcess( cps->pr, 9 );
14262                 cps->pr = NoProc;
14263             }
14264
14265             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14266         }
14267         return;
14268     }
14269
14270     if ((end_str = strchr(message, '\r')) != NULL)
14271       *end_str = NULLCHAR;
14272     if ((end_str = strchr(message, '\n')) != NULL)
14273       *end_str = NULLCHAR;
14274
14275     if (appData.debugMode) {
14276         TimeMark now; int print = 1;
14277         char *quote = ""; char c; int i;
14278
14279         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14280                 char start = message[0];
14281                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14282                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14283                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14284                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14285                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14286                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14287                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14288                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14289                    sscanf(message, "hint: %c", &c)!=1 && 
14290                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14291                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14292                     print = (appData.engineComments >= 2);
14293                 }
14294                 message[0] = start; // restore original message
14295         }
14296         if(print) {
14297                 GetTimeMark(&now);
14298                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14299                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14300                         quote,
14301                         message);
14302         }
14303     }
14304
14305     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14306     if (appData.icsEngineAnalyze) {
14307         if (strstr(message, "whisper") != NULL ||
14308              strstr(message, "kibitz") != NULL ||
14309             strstr(message, "tellics") != NULL) return;
14310     }
14311
14312     HandleMachineMove(message, cps);
14313 }
14314
14315
14316 void
14317 SendTimeControl(cps, mps, tc, inc, sd, st)
14318      ChessProgramState *cps;
14319      int mps, inc, sd, st;
14320      long tc;
14321 {
14322     char buf[MSG_SIZ];
14323     int seconds;
14324
14325     if( timeControl_2 > 0 ) {
14326         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14327             tc = timeControl_2;
14328         }
14329     }
14330     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14331     inc /= cps->timeOdds;
14332     st  /= cps->timeOdds;
14333
14334     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14335
14336     if (st > 0) {
14337       /* Set exact time per move, normally using st command */
14338       if (cps->stKludge) {
14339         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14340         seconds = st % 60;
14341         if (seconds == 0) {
14342           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14343         } else {
14344           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14345         }
14346       } else {
14347         snprintf(buf, MSG_SIZ, "st %d\n", st);
14348       }
14349     } else {
14350       /* Set conventional or incremental time control, using level command */
14351       if (seconds == 0) {
14352         /* Note old gnuchess bug -- minutes:seconds used to not work.
14353            Fixed in later versions, but still avoid :seconds
14354            when seconds is 0. */
14355         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14356       } else {
14357         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14358                  seconds, inc/1000.);
14359       }
14360     }
14361     SendToProgram(buf, cps);
14362
14363     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14364     /* Orthogonally, limit search to given depth */
14365     if (sd > 0) {
14366       if (cps->sdKludge) {
14367         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14368       } else {
14369         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14370       }
14371       SendToProgram(buf, cps);
14372     }
14373
14374     if(cps->nps >= 0) { /* [HGM] nps */
14375         if(cps->supportsNPS == FALSE)
14376           cps->nps = -1; // don't use if engine explicitly says not supported!
14377         else {
14378           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14379           SendToProgram(buf, cps);
14380         }
14381     }
14382 }
14383
14384 ChessProgramState *WhitePlayer()
14385 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14386 {
14387     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14388        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14389         return &second;
14390     return &first;
14391 }
14392
14393 void
14394 SendTimeRemaining(cps, machineWhite)
14395      ChessProgramState *cps;
14396      int /*boolean*/ machineWhite;
14397 {
14398     char message[MSG_SIZ];
14399     long time, otime;
14400
14401     /* Note: this routine must be called when the clocks are stopped
14402        or when they have *just* been set or switched; otherwise
14403        it will be off by the time since the current tick started.
14404     */
14405     if (machineWhite) {
14406         time = whiteTimeRemaining / 10;
14407         otime = blackTimeRemaining / 10;
14408     } else {
14409         time = blackTimeRemaining / 10;
14410         otime = whiteTimeRemaining / 10;
14411     }
14412     /* [HGM] translate opponent's time by time-odds factor */
14413     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14414     if (appData.debugMode) {
14415         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14416     }
14417
14418     if (time <= 0) time = 1;
14419     if (otime <= 0) otime = 1;
14420
14421     snprintf(message, MSG_SIZ, "time %ld\n", time);
14422     SendToProgram(message, cps);
14423
14424     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14425     SendToProgram(message, cps);
14426 }
14427
14428 int
14429 BoolFeature(p, name, loc, cps)
14430      char **p;
14431      char *name;
14432      int *loc;
14433      ChessProgramState *cps;
14434 {
14435   char buf[MSG_SIZ];
14436   int len = strlen(name);
14437   int val;
14438
14439   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14440     (*p) += len + 1;
14441     sscanf(*p, "%d", &val);
14442     *loc = (val != 0);
14443     while (**p && **p != ' ')
14444       (*p)++;
14445     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14446     SendToProgram(buf, cps);
14447     return TRUE;
14448   }
14449   return FALSE;
14450 }
14451
14452 int
14453 IntFeature(p, name, loc, cps)
14454      char **p;
14455      char *name;
14456      int *loc;
14457      ChessProgramState *cps;
14458 {
14459   char buf[MSG_SIZ];
14460   int len = strlen(name);
14461   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14462     (*p) += len + 1;
14463     sscanf(*p, "%d", loc);
14464     while (**p && **p != ' ') (*p)++;
14465     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14466     SendToProgram(buf, cps);
14467     return TRUE;
14468   }
14469   return FALSE;
14470 }
14471
14472 int
14473 StringFeature(p, name, loc, cps)
14474      char **p;
14475      char *name;
14476      char loc[];
14477      ChessProgramState *cps;
14478 {
14479   char buf[MSG_SIZ];
14480   int len = strlen(name);
14481   if (strncmp((*p), name, len) == 0
14482       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14483     (*p) += len + 2;
14484     sscanf(*p, "%[^\"]", loc);
14485     while (**p && **p != '\"') (*p)++;
14486     if (**p == '\"') (*p)++;
14487     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14488     SendToProgram(buf, cps);
14489     return TRUE;
14490   }
14491   return FALSE;
14492 }
14493
14494 int
14495 ParseOption(Option *opt, ChessProgramState *cps)
14496 // [HGM] options: process the string that defines an engine option, and determine
14497 // name, type, default value, and allowed value range
14498 {
14499         char *p, *q, buf[MSG_SIZ];
14500         int n, min = (-1)<<31, max = 1<<31, def;
14501
14502         if(p = strstr(opt->name, " -spin ")) {
14503             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14504             if(max < min) max = min; // enforce consistency
14505             if(def < min) def = min;
14506             if(def > max) def = max;
14507             opt->value = def;
14508             opt->min = min;
14509             opt->max = max;
14510             opt->type = Spin;
14511         } else if((p = strstr(opt->name, " -slider "))) {
14512             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14513             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14514             if(max < min) max = min; // enforce consistency
14515             if(def < min) def = min;
14516             if(def > max) def = max;
14517             opt->value = def;
14518             opt->min = min;
14519             opt->max = max;
14520             opt->type = Spin; // Slider;
14521         } else if((p = strstr(opt->name, " -string "))) {
14522             opt->textValue = p+9;
14523             opt->type = TextBox;
14524         } else if((p = strstr(opt->name, " -file "))) {
14525             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14526             opt->textValue = p+7;
14527             opt->type = FileName; // FileName;
14528         } else if((p = strstr(opt->name, " -path "))) {
14529             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14530             opt->textValue = p+7;
14531             opt->type = PathName; // PathName;
14532         } else if(p = strstr(opt->name, " -check ")) {
14533             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14534             opt->value = (def != 0);
14535             opt->type = CheckBox;
14536         } else if(p = strstr(opt->name, " -combo ")) {
14537             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14538             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14539             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14540             opt->value = n = 0;
14541             while(q = StrStr(q, " /// ")) {
14542                 n++; *q = 0;    // count choices, and null-terminate each of them
14543                 q += 5;
14544                 if(*q == '*') { // remember default, which is marked with * prefix
14545                     q++;
14546                     opt->value = n;
14547                 }
14548                 cps->comboList[cps->comboCnt++] = q;
14549             }
14550             cps->comboList[cps->comboCnt++] = NULL;
14551             opt->max = n + 1;
14552             opt->type = ComboBox;
14553         } else if(p = strstr(opt->name, " -button")) {
14554             opt->type = Button;
14555         } else if(p = strstr(opt->name, " -save")) {
14556             opt->type = SaveButton;
14557         } else return FALSE;
14558         *p = 0; // terminate option name
14559         // now look if the command-line options define a setting for this engine option.
14560         if(cps->optionSettings && cps->optionSettings[0])
14561             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14562         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14563           snprintf(buf, MSG_SIZ, "option %s", p);
14564                 if(p = strstr(buf, ",")) *p = 0;
14565                 if(q = strchr(buf, '=')) switch(opt->type) {
14566                     case ComboBox:
14567                         for(n=0; n<opt->max; n++)
14568                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14569                         break;
14570                     case TextBox:
14571                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14572                         break;
14573                     case Spin:
14574                     case CheckBox:
14575                         opt->value = atoi(q+1);
14576                     default:
14577                         break;
14578                 }
14579                 strcat(buf, "\n");
14580                 SendToProgram(buf, cps);
14581         }
14582         return TRUE;
14583 }
14584
14585 void
14586 FeatureDone(cps, val)
14587      ChessProgramState* cps;
14588      int val;
14589 {
14590   DelayedEventCallback cb = GetDelayedEvent();
14591   if ((cb == InitBackEnd3 && cps == &first) ||
14592       (cb == SettingsMenuIfReady && cps == &second) ||
14593       (cb == LoadEngine) ||
14594       (cb == TwoMachinesEventIfReady)) {
14595     CancelDelayedEvent();
14596     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14597   }
14598   cps->initDone = val;
14599 }
14600
14601 /* Parse feature command from engine */
14602 void
14603 ParseFeatures(args, cps)
14604      char* args;
14605      ChessProgramState *cps;
14606 {
14607   char *p = args;
14608   char *q;
14609   int val;
14610   char buf[MSG_SIZ];
14611
14612   for (;;) {
14613     while (*p == ' ') p++;
14614     if (*p == NULLCHAR) return;
14615
14616     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14617     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14618     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14619     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14620     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14621     if (BoolFeature(&p, "reuse", &val, cps)) {
14622       /* Engine can disable reuse, but can't enable it if user said no */
14623       if (!val) cps->reuse = FALSE;
14624       continue;
14625     }
14626     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14627     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14628       if (gameMode == TwoMachinesPlay) {
14629         DisplayTwoMachinesTitle();
14630       } else {
14631         DisplayTitle("");
14632       }
14633       continue;
14634     }
14635     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14636     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14637     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14638     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14639     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14640     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14641     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14642     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14643     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14644     if (IntFeature(&p, "done", &val, cps)) {
14645       FeatureDone(cps, val);
14646       continue;
14647     }
14648     /* Added by Tord: */
14649     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14650     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14651     /* End of additions by Tord */
14652
14653     /* [HGM] added features: */
14654     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14655     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14656     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14657     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14658     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14659     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14660     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14661         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14662           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14663             SendToProgram(buf, cps);
14664             continue;
14665         }
14666         if(cps->nrOptions >= MAX_OPTIONS) {
14667             cps->nrOptions--;
14668             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14669             DisplayError(buf, 0);
14670         }
14671         continue;
14672     }
14673     /* End of additions by HGM */
14674
14675     /* unknown feature: complain and skip */
14676     q = p;
14677     while (*q && *q != '=') q++;
14678     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14679     SendToProgram(buf, cps);
14680     p = q;
14681     if (*p == '=') {
14682       p++;
14683       if (*p == '\"') {
14684         p++;
14685         while (*p && *p != '\"') p++;
14686         if (*p == '\"') p++;
14687       } else {
14688         while (*p && *p != ' ') p++;
14689       }
14690     }
14691   }
14692
14693 }
14694
14695 void
14696 PeriodicUpdatesEvent(newState)
14697      int newState;
14698 {
14699     if (newState == appData.periodicUpdates)
14700       return;
14701
14702     appData.periodicUpdates=newState;
14703
14704     /* Display type changes, so update it now */
14705 //    DisplayAnalysis();
14706
14707     /* Get the ball rolling again... */
14708     if (newState) {
14709         AnalysisPeriodicEvent(1);
14710         StartAnalysisClock();
14711     }
14712 }
14713
14714 void
14715 PonderNextMoveEvent(newState)
14716      int newState;
14717 {
14718     if (newState == appData.ponderNextMove) return;
14719     if (gameMode == EditPosition) EditPositionDone(TRUE);
14720     if (newState) {
14721         SendToProgram("hard\n", &first);
14722         if (gameMode == TwoMachinesPlay) {
14723             SendToProgram("hard\n", &second);
14724         }
14725     } else {
14726         SendToProgram("easy\n", &first);
14727         thinkOutput[0] = NULLCHAR;
14728         if (gameMode == TwoMachinesPlay) {
14729             SendToProgram("easy\n", &second);
14730         }
14731     }
14732     appData.ponderNextMove = newState;
14733 }
14734
14735 void
14736 NewSettingEvent(option, feature, command, value)
14737      char *command;
14738      int option, value, *feature;
14739 {
14740     char buf[MSG_SIZ];
14741
14742     if (gameMode == EditPosition) EditPositionDone(TRUE);
14743     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14744     if(feature == NULL || *feature) SendToProgram(buf, &first);
14745     if (gameMode == TwoMachinesPlay) {
14746         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14747     }
14748 }
14749
14750 void
14751 ShowThinkingEvent()
14752 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14753 {
14754     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14755     int newState = appData.showThinking
14756         // [HGM] thinking: other features now need thinking output as well
14757         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14758
14759     if (oldState == newState) return;
14760     oldState = newState;
14761     if (gameMode == EditPosition) EditPositionDone(TRUE);
14762     if (oldState) {
14763         SendToProgram("post\n", &first);
14764         if (gameMode == TwoMachinesPlay) {
14765             SendToProgram("post\n", &second);
14766         }
14767     } else {
14768         SendToProgram("nopost\n", &first);
14769         thinkOutput[0] = NULLCHAR;
14770         if (gameMode == TwoMachinesPlay) {
14771             SendToProgram("nopost\n", &second);
14772         }
14773     }
14774 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14775 }
14776
14777 void
14778 AskQuestionEvent(title, question, replyPrefix, which)
14779      char *title; char *question; char *replyPrefix; char *which;
14780 {
14781   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14782   if (pr == NoProc) return;
14783   AskQuestion(title, question, replyPrefix, pr);
14784 }
14785
14786 void
14787 TypeInEvent(char firstChar)
14788 {
14789     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14790         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14791         gameMode == AnalyzeMode || gameMode == EditGame || \r
14792         gameMode == EditPosition || gameMode == IcsExamining ||\r
14793         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14794         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14795                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14796                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14797         gameMode == Training) PopUpMoveDialog(firstChar);
14798 }
14799
14800 void
14801 TypeInDoneEvent(char *move)
14802 {
14803         Board board;
14804         int n, fromX, fromY, toX, toY;
14805         char promoChar;
14806         ChessMove moveType;\r
14807
14808         // [HGM] FENedit\r
14809         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14810                 EditPositionPasteFEN(move);\r
14811                 return;\r
14812         }\r
14813         // [HGM] movenum: allow move number to be typed in any mode\r
14814         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14815           ToNrEvent(2*n-1);\r
14816           return;\r
14817         }\r
14818
14819       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14820         gameMode != Training) {\r
14821         DisplayMoveError(_("Displayed move is not current"));\r
14822       } else {\r
14823         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14824           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14825         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14826         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14827           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14828           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14829         } else {\r
14830           DisplayMoveError(_("Could not parse move"));\r
14831         }
14832       }\r
14833 }\r
14834
14835 void
14836 DisplayMove(moveNumber)
14837      int moveNumber;
14838 {
14839     char message[MSG_SIZ];
14840     char res[MSG_SIZ];
14841     char cpThinkOutput[MSG_SIZ];
14842
14843     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14844
14845     if (moveNumber == forwardMostMove - 1 ||
14846         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14847
14848         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14849
14850         if (strchr(cpThinkOutput, '\n')) {
14851             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14852         }
14853     } else {
14854         *cpThinkOutput = NULLCHAR;
14855     }
14856
14857     /* [AS] Hide thinking from human user */
14858     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14859         *cpThinkOutput = NULLCHAR;
14860         if( thinkOutput[0] != NULLCHAR ) {
14861             int i;
14862
14863             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14864                 cpThinkOutput[i] = '.';
14865             }
14866             cpThinkOutput[i] = NULLCHAR;
14867             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14868         }
14869     }
14870
14871     if (moveNumber == forwardMostMove - 1 &&
14872         gameInfo.resultDetails != NULL) {
14873         if (gameInfo.resultDetails[0] == NULLCHAR) {
14874           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14875         } else {
14876           snprintf(res, MSG_SIZ, " {%s} %s",
14877                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14878         }
14879     } else {
14880         res[0] = NULLCHAR;
14881     }
14882
14883     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14884         DisplayMessage(res, cpThinkOutput);
14885     } else {
14886       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14887                 WhiteOnMove(moveNumber) ? " " : ".. ",
14888                 parseList[moveNumber], res);
14889         DisplayMessage(message, cpThinkOutput);
14890     }
14891 }
14892
14893 void
14894 DisplayComment(moveNumber, text)
14895      int moveNumber;
14896      char *text;
14897 {
14898     char title[MSG_SIZ];
14899     char buf[8000]; // comment can be long!
14900     int score, depth;
14901
14902     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14903       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14904     } else {
14905       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14906               WhiteOnMove(moveNumber) ? " " : ".. ",
14907               parseList[moveNumber]);
14908     }
14909     // [HGM] PV info: display PV info together with (or as) comment
14910     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14911       if(text == NULL) text = "";
14912       score = pvInfoList[moveNumber].score;
14913       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14914               depth, (pvInfoList[moveNumber].time+50)/100, text);
14915       text = buf;
14916     }
14917     if (text != NULL && (appData.autoDisplayComment || commentUp))
14918         CommentPopUp(title, text);
14919 }
14920
14921 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14922  * might be busy thinking or pondering.  It can be omitted if your
14923  * gnuchess is configured to stop thinking immediately on any user
14924  * input.  However, that gnuchess feature depends on the FIONREAD
14925  * ioctl, which does not work properly on some flavors of Unix.
14926  */
14927 void
14928 Attention(cps)
14929      ChessProgramState *cps;
14930 {
14931 #if ATTENTION
14932     if (!cps->useSigint) return;
14933     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14934     switch (gameMode) {
14935       case MachinePlaysWhite:
14936       case MachinePlaysBlack:
14937       case TwoMachinesPlay:
14938       case IcsPlayingWhite:
14939       case IcsPlayingBlack:
14940       case AnalyzeMode:
14941       case AnalyzeFile:
14942         /* Skip if we know it isn't thinking */
14943         if (!cps->maybeThinking) return;
14944         if (appData.debugMode)
14945           fprintf(debugFP, "Interrupting %s\n", cps->which);
14946         InterruptChildProcess(cps->pr);
14947         cps->maybeThinking = FALSE;
14948         break;
14949       default:
14950         break;
14951     }
14952 #endif /*ATTENTION*/
14953 }
14954
14955 int
14956 CheckFlags()
14957 {
14958     if (whiteTimeRemaining <= 0) {
14959         if (!whiteFlag) {
14960             whiteFlag = TRUE;
14961             if (appData.icsActive) {
14962                 if (appData.autoCallFlag &&
14963                     gameMode == IcsPlayingBlack && !blackFlag) {
14964                   SendToICS(ics_prefix);
14965                   SendToICS("flag\n");
14966                 }
14967             } else {
14968                 if (blackFlag) {
14969                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14970                 } else {
14971                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14972                     if (appData.autoCallFlag) {
14973                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14974                         return TRUE;
14975                     }
14976                 }
14977             }
14978         }
14979     }
14980     if (blackTimeRemaining <= 0) {
14981         if (!blackFlag) {
14982             blackFlag = TRUE;
14983             if (appData.icsActive) {
14984                 if (appData.autoCallFlag &&
14985                     gameMode == IcsPlayingWhite && !whiteFlag) {
14986                   SendToICS(ics_prefix);
14987                   SendToICS("flag\n");
14988                 }
14989             } else {
14990                 if (whiteFlag) {
14991                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14992                 } else {
14993                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14994                     if (appData.autoCallFlag) {
14995                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14996                         return TRUE;
14997                     }
14998                 }
14999             }
15000         }
15001     }
15002     return FALSE;
15003 }
15004
15005 void
15006 CheckTimeControl()
15007 {
15008     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15009         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15010
15011     /*
15012      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15013      */
15014     if ( !WhiteOnMove(forwardMostMove) ) {
15015         /* White made time control */
15016         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15017         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15018         /* [HGM] time odds: correct new time quota for time odds! */
15019                                             / WhitePlayer()->timeOdds;
15020         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15021     } else {
15022         lastBlack -= blackTimeRemaining;
15023         /* Black made time control */
15024         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15025                                             / WhitePlayer()->other->timeOdds;
15026         lastWhite = whiteTimeRemaining;
15027     }
15028 }
15029
15030 void
15031 DisplayBothClocks()
15032 {
15033     int wom = gameMode == EditPosition ?
15034       !blackPlaysFirst : WhiteOnMove(currentMove);
15035     DisplayWhiteClock(whiteTimeRemaining, wom);
15036     DisplayBlackClock(blackTimeRemaining, !wom);
15037 }
15038
15039
15040 /* Timekeeping seems to be a portability nightmare.  I think everyone
15041    has ftime(), but I'm really not sure, so I'm including some ifdefs
15042    to use other calls if you don't.  Clocks will be less accurate if
15043    you have neither ftime nor gettimeofday.
15044 */
15045
15046 /* VS 2008 requires the #include outside of the function */
15047 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15048 #include <sys/timeb.h>
15049 #endif
15050
15051 /* Get the current time as a TimeMark */
15052 void
15053 GetTimeMark(tm)
15054      TimeMark *tm;
15055 {
15056 #if HAVE_GETTIMEOFDAY
15057
15058     struct timeval timeVal;
15059     struct timezone timeZone;
15060
15061     gettimeofday(&timeVal, &timeZone);
15062     tm->sec = (long) timeVal.tv_sec;
15063     tm->ms = (int) (timeVal.tv_usec / 1000L);
15064
15065 #else /*!HAVE_GETTIMEOFDAY*/
15066 #if HAVE_FTIME
15067
15068 // include <sys/timeb.h> / moved to just above start of function
15069     struct timeb timeB;
15070
15071     ftime(&timeB);
15072     tm->sec = (long) timeB.time;
15073     tm->ms = (int) timeB.millitm;
15074
15075 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15076     tm->sec = (long) time(NULL);
15077     tm->ms = 0;
15078 #endif
15079 #endif
15080 }
15081
15082 /* Return the difference in milliseconds between two
15083    time marks.  We assume the difference will fit in a long!
15084 */
15085 long
15086 SubtractTimeMarks(tm2, tm1)
15087      TimeMark *tm2, *tm1;
15088 {
15089     return 1000L*(tm2->sec - tm1->sec) +
15090            (long) (tm2->ms - tm1->ms);
15091 }
15092
15093
15094 /*
15095  * Code to manage the game clocks.
15096  *
15097  * In tournament play, black starts the clock and then white makes a move.
15098  * We give the human user a slight advantage if he is playing white---the
15099  * clocks don't run until he makes his first move, so it takes zero time.
15100  * Also, we don't account for network lag, so we could get out of sync
15101  * with GNU Chess's clock -- but then, referees are always right.
15102  */
15103
15104 static TimeMark tickStartTM;
15105 static long intendedTickLength;
15106
15107 long
15108 NextTickLength(timeRemaining)
15109      long timeRemaining;
15110 {
15111     long nominalTickLength, nextTickLength;
15112
15113     if (timeRemaining > 0L && timeRemaining <= 10000L)
15114       nominalTickLength = 100L;
15115     else
15116       nominalTickLength = 1000L;
15117     nextTickLength = timeRemaining % nominalTickLength;
15118     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15119
15120     return nextTickLength;
15121 }
15122
15123 /* Adjust clock one minute up or down */
15124 void
15125 AdjustClock(Boolean which, int dir)
15126 {
15127     if(which) blackTimeRemaining += 60000*dir;
15128     else      whiteTimeRemaining += 60000*dir;
15129     DisplayBothClocks();
15130 }
15131
15132 /* Stop clocks and reset to a fresh time control */
15133 void
15134 ResetClocks()
15135 {
15136     (void) StopClockTimer();
15137     if (appData.icsActive) {
15138         whiteTimeRemaining = blackTimeRemaining = 0;
15139     } else if (searchTime) {
15140         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15141         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15142     } else { /* [HGM] correct new time quote for time odds */
15143         whiteTC = blackTC = fullTimeControlString;
15144         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15145         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15146     }
15147     if (whiteFlag || blackFlag) {
15148         DisplayTitle("");
15149         whiteFlag = blackFlag = FALSE;
15150     }
15151     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15152     DisplayBothClocks();
15153 }
15154
15155 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15156
15157 /* Decrement running clock by amount of time that has passed */
15158 void
15159 DecrementClocks()
15160 {
15161     long timeRemaining;
15162     long lastTickLength, fudge;
15163     TimeMark now;
15164
15165     if (!appData.clockMode) return;
15166     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15167
15168     GetTimeMark(&now);
15169
15170     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15171
15172     /* Fudge if we woke up a little too soon */
15173     fudge = intendedTickLength - lastTickLength;
15174     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15175
15176     if (WhiteOnMove(forwardMostMove)) {
15177         if(whiteNPS >= 0) lastTickLength = 0;
15178         timeRemaining = whiteTimeRemaining -= lastTickLength;
15179         if(timeRemaining < 0 && !appData.icsActive) {
15180             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15181             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15182                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15183                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15184             }
15185         }
15186         DisplayWhiteClock(whiteTimeRemaining - fudge,
15187                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15188     } else {
15189         if(blackNPS >= 0) lastTickLength = 0;
15190         timeRemaining = blackTimeRemaining -= lastTickLength;
15191         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15192             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15193             if(suddenDeath) {
15194                 blackStartMove = forwardMostMove;
15195                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15196             }
15197         }
15198         DisplayBlackClock(blackTimeRemaining - fudge,
15199                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15200     }
15201     if (CheckFlags()) return;
15202
15203     tickStartTM = now;
15204     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15205     StartClockTimer(intendedTickLength);
15206
15207     /* if the time remaining has fallen below the alarm threshold, sound the
15208      * alarm. if the alarm has sounded and (due to a takeback or time control
15209      * with increment) the time remaining has increased to a level above the
15210      * threshold, reset the alarm so it can sound again.
15211      */
15212
15213     if (appData.icsActive && appData.icsAlarm) {
15214
15215         /* make sure we are dealing with the user's clock */
15216         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15217                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15218            )) return;
15219
15220         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15221             alarmSounded = FALSE;
15222         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15223             PlayAlarmSound();
15224             alarmSounded = TRUE;
15225         }
15226     }
15227 }
15228
15229
15230 /* A player has just moved, so stop the previously running
15231    clock and (if in clock mode) start the other one.
15232    We redisplay both clocks in case we're in ICS mode, because
15233    ICS gives us an update to both clocks after every move.
15234    Note that this routine is called *after* forwardMostMove
15235    is updated, so the last fractional tick must be subtracted
15236    from the color that is *not* on move now.
15237 */
15238 void
15239 SwitchClocks(int newMoveNr)
15240 {
15241     long lastTickLength;
15242     TimeMark now;
15243     int flagged = FALSE;
15244
15245     GetTimeMark(&now);
15246
15247     if (StopClockTimer() && appData.clockMode) {
15248         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15249         if (!WhiteOnMove(forwardMostMove)) {
15250             if(blackNPS >= 0) lastTickLength = 0;
15251             blackTimeRemaining -= lastTickLength;
15252            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15253 //         if(pvInfoList[forwardMostMove].time == -1)
15254                  pvInfoList[forwardMostMove].time =               // use GUI time
15255                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15256         } else {
15257            if(whiteNPS >= 0) lastTickLength = 0;
15258            whiteTimeRemaining -= lastTickLength;
15259            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15260 //         if(pvInfoList[forwardMostMove].time == -1)
15261                  pvInfoList[forwardMostMove].time =
15262                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15263         }
15264         flagged = CheckFlags();
15265     }
15266     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15267     CheckTimeControl();
15268
15269     if (flagged || !appData.clockMode) return;
15270
15271     switch (gameMode) {
15272       case MachinePlaysBlack:
15273       case MachinePlaysWhite:
15274       case BeginningOfGame:
15275         if (pausing) return;
15276         break;
15277
15278       case EditGame:
15279       case PlayFromGameFile:
15280       case IcsExamining:
15281         return;
15282
15283       default:
15284         break;
15285     }
15286
15287     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15288         if(WhiteOnMove(forwardMostMove))
15289              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15290         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15291     }
15292
15293     tickStartTM = now;
15294     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15295       whiteTimeRemaining : blackTimeRemaining);
15296     StartClockTimer(intendedTickLength);
15297 }
15298
15299
15300 /* Stop both clocks */
15301 void
15302 StopClocks()
15303 {
15304     long lastTickLength;
15305     TimeMark now;
15306
15307     if (!StopClockTimer()) return;
15308     if (!appData.clockMode) return;
15309
15310     GetTimeMark(&now);
15311
15312     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15313     if (WhiteOnMove(forwardMostMove)) {
15314         if(whiteNPS >= 0) lastTickLength = 0;
15315         whiteTimeRemaining -= lastTickLength;
15316         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15317     } else {
15318         if(blackNPS >= 0) lastTickLength = 0;
15319         blackTimeRemaining -= lastTickLength;
15320         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15321     }
15322     CheckFlags();
15323 }
15324
15325 /* Start clock of player on move.  Time may have been reset, so
15326    if clock is already running, stop and restart it. */
15327 void
15328 StartClocks()
15329 {
15330     (void) StopClockTimer(); /* in case it was running already */
15331     DisplayBothClocks();
15332     if (CheckFlags()) return;
15333
15334     if (!appData.clockMode) return;
15335     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15336
15337     GetTimeMark(&tickStartTM);
15338     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15339       whiteTimeRemaining : blackTimeRemaining);
15340
15341    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15342     whiteNPS = blackNPS = -1;
15343     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15344        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15345         whiteNPS = first.nps;
15346     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15347        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15348         blackNPS = first.nps;
15349     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15350         whiteNPS = second.nps;
15351     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15352         blackNPS = second.nps;
15353     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15354
15355     StartClockTimer(intendedTickLength);
15356 }
15357
15358 char *
15359 TimeString(ms)
15360      long ms;
15361 {
15362     long second, minute, hour, day;
15363     char *sign = "";
15364     static char buf[32];
15365
15366     if (ms > 0 && ms <= 9900) {
15367       /* convert milliseconds to tenths, rounding up */
15368       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15369
15370       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15371       return buf;
15372     }
15373
15374     /* convert milliseconds to seconds, rounding up */
15375     /* use floating point to avoid strangeness of integer division
15376        with negative dividends on many machines */
15377     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15378
15379     if (second < 0) {
15380         sign = "-";
15381         second = -second;
15382     }
15383
15384     day = second / (60 * 60 * 24);
15385     second = second % (60 * 60 * 24);
15386     hour = second / (60 * 60);
15387     second = second % (60 * 60);
15388     minute = second / 60;
15389     second = second % 60;
15390
15391     if (day > 0)
15392       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15393               sign, day, hour, minute, second);
15394     else if (hour > 0)
15395       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15396     else
15397       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15398
15399     return buf;
15400 }
15401
15402
15403 /*
15404  * This is necessary because some C libraries aren't ANSI C compliant yet.
15405  */
15406 char *
15407 StrStr(string, match)
15408      char *string, *match;
15409 {
15410     int i, length;
15411
15412     length = strlen(match);
15413
15414     for (i = strlen(string) - length; i >= 0; i--, string++)
15415       if (!strncmp(match, string, length))
15416         return string;
15417
15418     return NULL;
15419 }
15420
15421 char *
15422 StrCaseStr(string, match)
15423      char *string, *match;
15424 {
15425     int i, j, length;
15426
15427     length = strlen(match);
15428
15429     for (i = strlen(string) - length; i >= 0; i--, string++) {
15430         for (j = 0; j < length; j++) {
15431             if (ToLower(match[j]) != ToLower(string[j]))
15432               break;
15433         }
15434         if (j == length) return string;
15435     }
15436
15437     return NULL;
15438 }
15439
15440 #ifndef _amigados
15441 int
15442 StrCaseCmp(s1, s2)
15443      char *s1, *s2;
15444 {
15445     char c1, c2;
15446
15447     for (;;) {
15448         c1 = ToLower(*s1++);
15449         c2 = ToLower(*s2++);
15450         if (c1 > c2) return 1;
15451         if (c1 < c2) return -1;
15452         if (c1 == NULLCHAR) return 0;
15453     }
15454 }
15455
15456
15457 int
15458 ToLower(c)
15459      int c;
15460 {
15461     return isupper(c) ? tolower(c) : c;
15462 }
15463
15464
15465 int
15466 ToUpper(c)
15467      int c;
15468 {
15469     return islower(c) ? toupper(c) : c;
15470 }
15471 #endif /* !_amigados    */
15472
15473 char *
15474 StrSave(s)
15475      char *s;
15476 {
15477   char *ret;
15478
15479   if ((ret = (char *) malloc(strlen(s) + 1)))
15480     {
15481       safeStrCpy(ret, s, strlen(s)+1);
15482     }
15483   return ret;
15484 }
15485
15486 char *
15487 StrSavePtr(s, savePtr)
15488      char *s, **savePtr;
15489 {
15490     if (*savePtr) {
15491         free(*savePtr);
15492     }
15493     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15494       safeStrCpy(*savePtr, s, strlen(s)+1);
15495     }
15496     return(*savePtr);
15497 }
15498
15499 char *
15500 PGNDate()
15501 {
15502     time_t clock;
15503     struct tm *tm;
15504     char buf[MSG_SIZ];
15505
15506     clock = time((time_t *)NULL);
15507     tm = localtime(&clock);
15508     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15509             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15510     return StrSave(buf);
15511 }
15512
15513
15514 char *
15515 PositionToFEN(move, overrideCastling)
15516      int move;
15517      char *overrideCastling;
15518 {
15519     int i, j, fromX, fromY, toX, toY;
15520     int whiteToPlay;
15521     char buf[128];
15522     char *p, *q;
15523     int emptycount;
15524     ChessSquare piece;
15525
15526     whiteToPlay = (gameMode == EditPosition) ?
15527       !blackPlaysFirst : (move % 2 == 0);
15528     p = buf;
15529
15530     /* Piece placement data */
15531     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15532         emptycount = 0;
15533         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15534             if (boards[move][i][j] == EmptySquare) {
15535                 emptycount++;
15536             } else { ChessSquare piece = boards[move][i][j];
15537                 if (emptycount > 0) {
15538                     if(emptycount<10) /* [HGM] can be >= 10 */
15539                         *p++ = '0' + emptycount;
15540                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15541                     emptycount = 0;
15542                 }
15543                 if(PieceToChar(piece) == '+') {
15544                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15545                     *p++ = '+';
15546                     piece = (ChessSquare)(DEMOTED piece);
15547                 }
15548                 *p++ = PieceToChar(piece);
15549                 if(p[-1] == '~') {
15550                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15551                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15552                     *p++ = '~';
15553                 }
15554             }
15555         }
15556         if (emptycount > 0) {
15557             if(emptycount<10) /* [HGM] can be >= 10 */
15558                 *p++ = '0' + emptycount;
15559             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15560             emptycount = 0;
15561         }
15562         *p++ = '/';
15563     }
15564     *(p - 1) = ' ';
15565
15566     /* [HGM] print Crazyhouse or Shogi holdings */
15567     if( gameInfo.holdingsWidth ) {
15568         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15569         q = p;
15570         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15571             piece = boards[move][i][BOARD_WIDTH-1];
15572             if( piece != EmptySquare )
15573               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15574                   *p++ = PieceToChar(piece);
15575         }
15576         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15577             piece = boards[move][BOARD_HEIGHT-i-1][0];
15578             if( piece != EmptySquare )
15579               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15580                   *p++ = PieceToChar(piece);
15581         }
15582
15583         if( q == p ) *p++ = '-';
15584         *p++ = ']';
15585         *p++ = ' ';
15586     }
15587
15588     /* Active color */
15589     *p++ = whiteToPlay ? 'w' : 'b';
15590     *p++ = ' ';
15591
15592   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15593     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15594   } else {
15595   if(nrCastlingRights) {
15596      q = p;
15597      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15598        /* [HGM] write directly from rights */
15599            if(boards[move][CASTLING][2] != NoRights &&
15600               boards[move][CASTLING][0] != NoRights   )
15601                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15602            if(boards[move][CASTLING][2] != NoRights &&
15603               boards[move][CASTLING][1] != NoRights   )
15604                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15605            if(boards[move][CASTLING][5] != NoRights &&
15606               boards[move][CASTLING][3] != NoRights   )
15607                 *p++ = boards[move][CASTLING][3] + AAA;
15608            if(boards[move][CASTLING][5] != NoRights &&
15609               boards[move][CASTLING][4] != NoRights   )
15610                 *p++ = boards[move][CASTLING][4] + AAA;
15611      } else {
15612
15613         /* [HGM] write true castling rights */
15614         if( nrCastlingRights == 6 ) {
15615             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15616                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15617             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15618                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15619             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15620                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15621             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15622                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15623         }
15624      }
15625      if (q == p) *p++ = '-'; /* No castling rights */
15626      *p++ = ' ';
15627   }
15628
15629   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15630      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15631     /* En passant target square */
15632     if (move > backwardMostMove) {
15633         fromX = moveList[move - 1][0] - AAA;
15634         fromY = moveList[move - 1][1] - ONE;
15635         toX = moveList[move - 1][2] - AAA;
15636         toY = moveList[move - 1][3] - ONE;
15637         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15638             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15639             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15640             fromX == toX) {
15641             /* 2-square pawn move just happened */
15642             *p++ = toX + AAA;
15643             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15644         } else {
15645             *p++ = '-';
15646         }
15647     } else if(move == backwardMostMove) {
15648         // [HGM] perhaps we should always do it like this, and forget the above?
15649         if((signed char)boards[move][EP_STATUS] >= 0) {
15650             *p++ = boards[move][EP_STATUS] + AAA;
15651             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15652         } else {
15653             *p++ = '-';
15654         }
15655     } else {
15656         *p++ = '-';
15657     }
15658     *p++ = ' ';
15659   }
15660   }
15661
15662     /* [HGM] find reversible plies */
15663     {   int i = 0, j=move;
15664
15665         if (appData.debugMode) { int k;
15666             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15667             for(k=backwardMostMove; k<=forwardMostMove; k++)
15668                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15669
15670         }
15671
15672         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15673         if( j == backwardMostMove ) i += initialRulePlies;
15674         sprintf(p, "%d ", i);
15675         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15676     }
15677     /* Fullmove number */
15678     sprintf(p, "%d", (move / 2) + 1);
15679
15680     return StrSave(buf);
15681 }
15682
15683 Boolean
15684 ParseFEN(board, blackPlaysFirst, fen)
15685     Board board;
15686      int *blackPlaysFirst;
15687      char *fen;
15688 {
15689     int i, j;
15690     char *p, c;
15691     int emptycount;
15692     ChessSquare piece;
15693
15694     p = fen;
15695
15696     /* [HGM] by default clear Crazyhouse holdings, if present */
15697     if(gameInfo.holdingsWidth) {
15698        for(i=0; i<BOARD_HEIGHT; i++) {
15699            board[i][0]             = EmptySquare; /* black holdings */
15700            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15701            board[i][1]             = (ChessSquare) 0; /* black counts */
15702            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15703        }
15704     }
15705
15706     /* Piece placement data */
15707     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15708         j = 0;
15709         for (;;) {
15710             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15711                 if (*p == '/') p++;
15712                 emptycount = gameInfo.boardWidth - j;
15713                 while (emptycount--)
15714                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15715                 break;
15716 #if(BOARD_FILES >= 10)
15717             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15718                 p++; emptycount=10;
15719                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15720                 while (emptycount--)
15721                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15722 #endif
15723             } else if (isdigit(*p)) {
15724                 emptycount = *p++ - '0';
15725                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15726                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15727                 while (emptycount--)
15728                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15729             } else if (*p == '+' || isalpha(*p)) {
15730                 if (j >= gameInfo.boardWidth) return FALSE;
15731                 if(*p=='+') {
15732                     piece = CharToPiece(*++p);
15733                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15734                     piece = (ChessSquare) (PROMOTED piece ); p++;
15735                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15736                 } else piece = CharToPiece(*p++);
15737
15738                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15739                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15740                     piece = (ChessSquare) (PROMOTED piece);
15741                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15742                     p++;
15743                 }
15744                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15745             } else {
15746                 return FALSE;
15747             }
15748         }
15749     }
15750     while (*p == '/' || *p == ' ') p++;
15751
15752     /* [HGM] look for Crazyhouse holdings here */
15753     while(*p==' ') p++;
15754     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15755         if(*p == '[') p++;
15756         if(*p == '-' ) p++; /* empty holdings */ else {
15757             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15758             /* if we would allow FEN reading to set board size, we would   */
15759             /* have to add holdings and shift the board read so far here   */
15760             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15761                 p++;
15762                 if((int) piece >= (int) BlackPawn ) {
15763                     i = (int)piece - (int)BlackPawn;
15764                     i = PieceToNumber((ChessSquare)i);
15765                     if( i >= gameInfo.holdingsSize ) return FALSE;
15766                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15767                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15768                 } else {
15769                     i = (int)piece - (int)WhitePawn;
15770                     i = PieceToNumber((ChessSquare)i);
15771                     if( i >= gameInfo.holdingsSize ) return FALSE;
15772                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15773                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15774                 }
15775             }
15776         }
15777         if(*p == ']') p++;
15778     }
15779
15780     while(*p == ' ') p++;
15781
15782     /* Active color */
15783     c = *p++;
15784     if(appData.colorNickNames) {
15785       if( c == appData.colorNickNames[0] ) c = 'w'; else
15786       if( c == appData.colorNickNames[1] ) c = 'b';
15787     }
15788     switch (c) {
15789       case 'w':
15790         *blackPlaysFirst = FALSE;
15791         break;
15792       case 'b':
15793         *blackPlaysFirst = TRUE;
15794         break;
15795       default:
15796         return FALSE;
15797     }
15798
15799     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15800     /* return the extra info in global variiables             */
15801
15802     /* set defaults in case FEN is incomplete */
15803     board[EP_STATUS] = EP_UNKNOWN;
15804     for(i=0; i<nrCastlingRights; i++ ) {
15805         board[CASTLING][i] =
15806             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15807     }   /* assume possible unless obviously impossible */
15808     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15809     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15810     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15811                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15812     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15813     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15814     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15815                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15816     FENrulePlies = 0;
15817
15818     while(*p==' ') p++;
15819     if(nrCastlingRights) {
15820       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15821           /* castling indicator present, so default becomes no castlings */
15822           for(i=0; i<nrCastlingRights; i++ ) {
15823                  board[CASTLING][i] = NoRights;
15824           }
15825       }
15826       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15827              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15828              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15829              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15830         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15831
15832         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15833             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15834             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15835         }
15836         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15837             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15838         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15839                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15840         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15841                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15842         switch(c) {
15843           case'K':
15844               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15845               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15846               board[CASTLING][2] = whiteKingFile;
15847               break;
15848           case'Q':
15849               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15850               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15851               board[CASTLING][2] = whiteKingFile;
15852               break;
15853           case'k':
15854               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15855               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15856               board[CASTLING][5] = blackKingFile;
15857               break;
15858           case'q':
15859               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15860               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15861               board[CASTLING][5] = blackKingFile;
15862           case '-':
15863               break;
15864           default: /* FRC castlings */
15865               if(c >= 'a') { /* black rights */
15866                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15867                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15868                   if(i == BOARD_RGHT) break;
15869                   board[CASTLING][5] = i;
15870                   c -= AAA;
15871                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15872                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15873                   if(c > i)
15874                       board[CASTLING][3] = c;
15875                   else
15876                       board[CASTLING][4] = c;
15877               } else { /* white rights */
15878                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15879                     if(board[0][i] == WhiteKing) break;
15880                   if(i == BOARD_RGHT) break;
15881                   board[CASTLING][2] = i;
15882                   c -= AAA - 'a' + 'A';
15883                   if(board[0][c] >= WhiteKing) break;
15884                   if(c > i)
15885                       board[CASTLING][0] = c;
15886                   else
15887                       board[CASTLING][1] = c;
15888               }
15889         }
15890       }
15891       for(i=0; i<nrCastlingRights; i++)
15892         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15893     if (appData.debugMode) {
15894         fprintf(debugFP, "FEN castling rights:");
15895         for(i=0; i<nrCastlingRights; i++)
15896         fprintf(debugFP, " %d", board[CASTLING][i]);
15897         fprintf(debugFP, "\n");
15898     }
15899
15900       while(*p==' ') p++;
15901     }
15902
15903     /* read e.p. field in games that know e.p. capture */
15904     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15905        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15906       if(*p=='-') {
15907         p++; board[EP_STATUS] = EP_NONE;
15908       } else {
15909          char c = *p++ - AAA;
15910
15911          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15912          if(*p >= '0' && *p <='9') p++;
15913          board[EP_STATUS] = c;
15914       }
15915     }
15916
15917
15918     if(sscanf(p, "%d", &i) == 1) {
15919         FENrulePlies = i; /* 50-move ply counter */
15920         /* (The move number is still ignored)    */
15921     }
15922
15923     return TRUE;
15924 }
15925
15926 void
15927 EditPositionPasteFEN(char *fen)
15928 {
15929   if (fen != NULL) {
15930     Board initial_position;
15931
15932     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15933       DisplayError(_("Bad FEN position in clipboard"), 0);
15934       return ;
15935     } else {
15936       int savedBlackPlaysFirst = blackPlaysFirst;
15937       EditPositionEvent();
15938       blackPlaysFirst = savedBlackPlaysFirst;
15939       CopyBoard(boards[0], initial_position);
15940       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15941       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15942       DisplayBothClocks();
15943       DrawPosition(FALSE, boards[currentMove]);
15944     }
15945   }
15946 }
15947
15948 static char cseq[12] = "\\   ";
15949
15950 Boolean set_cont_sequence(char *new_seq)
15951 {
15952     int len;
15953     Boolean ret;
15954
15955     // handle bad attempts to set the sequence
15956         if (!new_seq)
15957                 return 0; // acceptable error - no debug
15958
15959     len = strlen(new_seq);
15960     ret = (len > 0) && (len < sizeof(cseq));
15961     if (ret)
15962       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15963     else if (appData.debugMode)
15964       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15965     return ret;
15966 }
15967
15968 /*
15969     reformat a source message so words don't cross the width boundary.  internal
15970     newlines are not removed.  returns the wrapped size (no null character unless
15971     included in source message).  If dest is NULL, only calculate the size required
15972     for the dest buffer.  lp argument indicats line position upon entry, and it's
15973     passed back upon exit.
15974 */
15975 int wrap(char *dest, char *src, int count, int width, int *lp)
15976 {
15977     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15978
15979     cseq_len = strlen(cseq);
15980     old_line = line = *lp;
15981     ansi = len = clen = 0;
15982
15983     for (i=0; i < count; i++)
15984     {
15985         if (src[i] == '\033')
15986             ansi = 1;
15987
15988         // if we hit the width, back up
15989         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15990         {
15991             // store i & len in case the word is too long
15992             old_i = i, old_len = len;
15993
15994             // find the end of the last word
15995             while (i && src[i] != ' ' && src[i] != '\n')
15996             {
15997                 i--;
15998                 len--;
15999             }
16000
16001             // word too long?  restore i & len before splitting it
16002             if ((old_i-i+clen) >= width)
16003             {
16004                 i = old_i;
16005                 len = old_len;
16006             }
16007
16008             // extra space?
16009             if (i && src[i-1] == ' ')
16010                 len--;
16011
16012             if (src[i] != ' ' && src[i] != '\n')
16013             {
16014                 i--;
16015                 if (len)
16016                     len--;
16017             }
16018
16019             // now append the newline and continuation sequence
16020             if (dest)
16021                 dest[len] = '\n';
16022             len++;
16023             if (dest)
16024                 strncpy(dest+len, cseq, cseq_len);
16025             len += cseq_len;
16026             line = cseq_len;
16027             clen = cseq_len;
16028             continue;
16029         }
16030
16031         if (dest)
16032             dest[len] = src[i];
16033         len++;
16034         if (!ansi)
16035             line++;
16036         if (src[i] == '\n')
16037             line = 0;
16038         if (src[i] == 'm')
16039             ansi = 0;
16040     }
16041     if (dest && appData.debugMode)
16042     {
16043         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16044             count, width, line, len, *lp);
16045         show_bytes(debugFP, src, count);
16046         fprintf(debugFP, "\ndest: ");
16047         show_bytes(debugFP, dest, len);
16048         fprintf(debugFP, "\n");
16049     }
16050     *lp = dest ? line : old_line;
16051
16052     return len;
16053 }
16054
16055 // [HGM] vari: routines for shelving variations
16056
16057 void
16058 PushTail(int firstMove, int lastMove)
16059 {
16060         int i, j, nrMoves = lastMove - firstMove;
16061
16062         if(appData.icsActive) { // only in local mode
16063                 forwardMostMove = currentMove; // mimic old ICS behavior
16064                 return;
16065         }
16066         if(storedGames >= MAX_VARIATIONS-1) return;
16067
16068         // push current tail of game on stack
16069         savedResult[storedGames] = gameInfo.result;
16070         savedDetails[storedGames] = gameInfo.resultDetails;
16071         gameInfo.resultDetails = NULL;
16072         savedFirst[storedGames] = firstMove;
16073         savedLast [storedGames] = lastMove;
16074         savedFramePtr[storedGames] = framePtr;
16075         framePtr -= nrMoves; // reserve space for the boards
16076         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16077             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16078             for(j=0; j<MOVE_LEN; j++)
16079                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16080             for(j=0; j<2*MOVE_LEN; j++)
16081                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16082             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16083             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16084             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16085             pvInfoList[firstMove+i-1].depth = 0;
16086             commentList[framePtr+i] = commentList[firstMove+i];
16087             commentList[firstMove+i] = NULL;
16088         }
16089
16090         storedGames++;
16091         forwardMostMove = firstMove; // truncate game so we can start variation
16092         if(storedGames == 1) GreyRevert(FALSE);
16093 }
16094
16095 Boolean
16096 PopTail(Boolean annotate)
16097 {
16098         int i, j, nrMoves;
16099         char buf[8000], moveBuf[20];
16100
16101         if(appData.icsActive) return FALSE; // only in local mode
16102         if(!storedGames) return FALSE; // sanity
16103         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16104
16105         storedGames--;
16106         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16107         nrMoves = savedLast[storedGames] - currentMove;
16108         if(annotate) {
16109                 int cnt = 10;
16110                 if(!WhiteOnMove(currentMove))
16111                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16112                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16113                 for(i=currentMove; i<forwardMostMove; i++) {
16114                         if(WhiteOnMove(i))
16115                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16116                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16117                         strcat(buf, moveBuf);
16118                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16119                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16120                 }
16121                 strcat(buf, ")");
16122         }
16123         for(i=1; i<=nrMoves; i++) { // copy last variation back
16124             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16125             for(j=0; j<MOVE_LEN; j++)
16126                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16127             for(j=0; j<2*MOVE_LEN; j++)
16128                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16129             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16130             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16131             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16132             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16133             commentList[currentMove+i] = commentList[framePtr+i];
16134             commentList[framePtr+i] = NULL;
16135         }
16136         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16137         framePtr = savedFramePtr[storedGames];
16138         gameInfo.result = savedResult[storedGames];
16139         if(gameInfo.resultDetails != NULL) {
16140             free(gameInfo.resultDetails);
16141       }
16142         gameInfo.resultDetails = savedDetails[storedGames];
16143         forwardMostMove = currentMove + nrMoves;
16144         if(storedGames == 0) GreyRevert(TRUE);
16145         return TRUE;
16146 }
16147
16148 void
16149 CleanupTail()
16150 {       // remove all shelved variations
16151         int i;
16152         for(i=0; i<storedGames; i++) {
16153             if(savedDetails[i])
16154                 free(savedDetails[i]);
16155             savedDetails[i] = NULL;
16156         }
16157         for(i=framePtr; i<MAX_MOVES; i++) {
16158                 if(commentList[i]) free(commentList[i]);
16159                 commentList[i] = NULL;
16160         }
16161         framePtr = MAX_MOVES-1;
16162         storedGames = 0;
16163 }
16164
16165 void
16166 LoadVariation(int index, char *text)
16167 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16168         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16169         int level = 0, move;
16170
16171         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16172         // first find outermost bracketing variation
16173         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16174             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16175                 if(*p == '{') wait = '}'; else
16176                 if(*p == '[') wait = ']'; else
16177                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16178                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16179             }
16180             if(*p == wait) wait = NULLCHAR; // closing ]} found
16181             p++;
16182         }
16183         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16184         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16185         end[1] = NULLCHAR; // clip off comment beyond variation
16186         ToNrEvent(currentMove-1);
16187         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16188         // kludge: use ParsePV() to append variation to game
16189         move = currentMove;
16190         ParsePV(start, TRUE);
16191         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16192         ClearPremoveHighlights();
16193         CommentPopDown();
16194         ToNrEvent(currentMove+1);
16195 }
16196