Lock game an position file during writing
[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
63 #else
64
65 #define DoSleep( n ) if( (n) >= 0) sleep(n)
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 /* A point in time */
151 typedef struct {
152     long sec;  /* Assuming this is >= 32 bits */
153     int ms;    /* Assuming this is >= 16 bits */
154 } TimeMark;
155
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158                          char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160                       char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
172                                                                                 Board board));
173 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
174 void ShowMove P((int fromX, int fromY, int toX, int toY));
175 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
176                    /*char*/int promoChar));
177 void BackwardInner P((int target));
178 void ForwardInner P((int target));
179 int Adjudicate P((ChessProgramState *cps));
180 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
181 void EditPositionDone P((Boolean fakeRights));
182 void PrintOpponents P((FILE *fp));
183 void PrintPosition P((FILE *fp, int move));
184 void StartChessProgram P((ChessProgramState *cps));
185 void SendToProgram P((char *message, ChessProgramState *cps));
186 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
187 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
188                            char *buf, int count, int error));
189 void SendTimeControl P((ChessProgramState *cps,
190                         int mps, long tc, int inc, int sd, int st));
191 char *TimeControlTagValue P((void));
192 void Attention P((ChessProgramState *cps));
193 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
194 void ResurrectChessProgram P((void));
195 void DisplayComment P((int moveNumber, char *text));
196 void DisplayMove P((int moveNumber));
197
198 void ParseGameHistory P((char *game));
199 void ParseBoard12 P((char *string));
200 void KeepAlive P((void));
201 void StartClocks P((void));
202 void SwitchClocks P((int nr));
203 void StopClocks P((void));
204 void ResetClocks P((void));
205 char *PGNDate P((void));
206 void SetGameInfo P((void));
207 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
208 int RegisterMove P((void));
209 void MakeRegisteredMove P((void));
210 void TruncateGame P((void));
211 int looking_at P((char *, int *, char *));
212 void CopyPlayerNameIntoFileName P((char **, char *));
213 char *SavePart P((char *));
214 int SaveGameOldStyle P((FILE *));
215 int SaveGamePGN P((FILE *));
216 void GetTimeMark P((TimeMark *));
217 long SubtractTimeMarks P((TimeMark *, TimeMark *));
218 int CheckFlags P((void));
219 long NextTickLength P((long));
220 void CheckTimeControl P((void));
221 void show_bytes P((FILE *, char *, int));
222 int string_to_rating P((char *str));
223 void ParseFeatures P((char* args, ChessProgramState *cps));
224 void InitBackEnd3 P((void));
225 void FeatureDone P((ChessProgramState* cps, int val));
226 void InitChessProgram P((ChessProgramState *cps, int setup));
227 void OutputKibitz(int window, char *text);
228 int PerpetualChase(int first, int last);
229 int EngineOutputIsUp();
230 void InitDrawingSizes(int x, int y);
231
232 #ifdef WIN32
233        extern void ConsoleCreate();
234 #endif
235
236 ChessProgramState *WhitePlayer();
237 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
238 int VerifyDisplayMode P(());
239
240 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
241 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
242 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
243 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
244 void ics_update_width P((int new_width));
245 extern char installDir[MSG_SIZ];
246 VariantClass startVariant; /* [HGM] nicks: initial variant */
247
248 extern int tinyLayout, smallLayout;
249 ChessProgramStats programStats;
250 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
251 int endPV = -1;
252 static int exiting = 0; /* [HGM] moved to top */
253 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
254 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
255 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
256 int partnerHighlight[2];
257 Boolean partnerBoardValid = 0;
258 char partnerStatus[MSG_SIZ];
259 Boolean partnerUp;
260 Boolean originalFlip;
261 Boolean twoBoards = 0;
262 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
263 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
264 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
265 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
266 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
267 int opponentKibitzes;
268 int lastSavedGame; /* [HGM] save: ID of game */
269 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
270 extern int chatCount;
271 int chattingPartner;
272 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277
278 /* States for ics_getting_history */
279 #define H_FALSE 0
280 #define H_REQUESTED 1
281 #define H_GOT_REQ_HEADER 2
282 #define H_GOT_UNREQ_HEADER 3
283 #define H_GETTING_MOVES 4
284 #define H_GOT_UNWANTED_HEADER 5
285
286 /* whosays values for GameEnds */
287 #define GE_ICS 0
288 #define GE_ENGINE 1
289 #define GE_PLAYER 2
290 #define GE_FILE 3
291 #define GE_XBOARD 4
292 #define GE_ENGINE1 5
293 #define GE_ENGINE2 6
294
295 /* Maximum number of games in a cmail message */
296 #define CMAIL_MAX_GAMES 20
297
298 /* Different types of move when calling RegisterMove */
299 #define CMAIL_MOVE   0
300 #define CMAIL_RESIGN 1
301 #define CMAIL_DRAW   2
302 #define CMAIL_ACCEPT 3
303
304 /* Different types of result to remember for each game */
305 #define CMAIL_NOT_RESULT 0
306 #define CMAIL_OLD_RESULT 1
307 #define CMAIL_NEW_RESULT 2
308
309 /* Telnet protocol constants */
310 #define TN_WILL 0373
311 #define TN_WONT 0374
312 #define TN_DO   0375
313 #define TN_DONT 0376
314 #define TN_IAC  0377
315 #define TN_ECHO 0001
316 #define TN_SGA  0003
317 #define TN_PORT 23
318
319 char*
320 safeStrCpy( char *dst, const char *src, size_t count )
321 { // [HGM] made safe
322   int i;
323   assert( dst != NULL );
324   assert( src != NULL );
325   assert( count > 0 );
326
327   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
328   if(  i == count && dst[count-1] != NULLCHAR)
329     {
330       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
331       if(appData.debugMode)
332       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
333     }
334
335   return dst;
336 }
337
338 /* Some compiler can't cast u64 to double
339  * This function do the job for us:
340
341  * We use the highest bit for cast, this only
342  * works if the highest bit is not
343  * in use (This should not happen)
344  *
345  * We used this for all compiler
346  */
347 double
348 u64ToDouble(u64 value)
349 {
350   double r;
351   u64 tmp = value & u64Const(0x7fffffffffffffff);
352   r = (double)(s64)tmp;
353   if (value & u64Const(0x8000000000000000))
354        r +=  9.2233720368547758080e18; /* 2^63 */
355  return r;
356 }
357
358 /* Fake up flags for now, as we aren't keeping track of castling
359    availability yet. [HGM] Change of logic: the flag now only
360    indicates the type of castlings allowed by the rule of the game.
361    The actual rights themselves are maintained in the array
362    castlingRights, as part of the game history, and are not probed
363    by this function.
364  */
365 int
366 PosFlags(index)
367 {
368   int flags = F_ALL_CASTLE_OK;
369   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
370   switch (gameInfo.variant) {
371   case VariantSuicide:
372     flags &= ~F_ALL_CASTLE_OK;
373   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
374     flags |= F_IGNORE_CHECK;
375   case VariantLosers:
376     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
377     break;
378   case VariantAtomic:
379     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
380     break;
381   case VariantKriegspiel:
382     flags |= F_KRIEGSPIEL_CAPTURE;
383     break;
384   case VariantCapaRandom:
385   case VariantFischeRandom:
386     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
387   case VariantNoCastle:
388   case VariantShatranj:
389   case VariantCourier:
390   case VariantMakruk:
391     flags &= ~F_ALL_CASTLE_OK;
392     break;
393   default:
394     break;
395   }
396   return flags;
397 }
398
399 FILE *gameFileFP, *debugFP;
400
401 /*
402     [AS] Note: sometimes, the sscanf() function is used to parse the input
403     into a fixed-size buffer. Because of this, we must be prepared to
404     receive strings as long as the size of the input buffer, which is currently
405     set to 4K for Windows and 8K for the rest.
406     So, we must either allocate sufficiently large buffers here, or
407     reduce the size of the input buffer in the input reading part.
408 */
409
410 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
411 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
412 char thinkOutput1[MSG_SIZ*10];
413
414 ChessProgramState first, second;
415
416 /* premove variables */
417 int premoveToX = 0;
418 int premoveToY = 0;
419 int premoveFromX = 0;
420 int premoveFromY = 0;
421 int premovePromoChar = 0;
422 int gotPremove = 0;
423 Boolean alarmSounded;
424 /* end premove variables */
425
426 char *ics_prefix = "$";
427 int ics_type = ICS_GENERIC;
428
429 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
430 int pauseExamForwardMostMove = 0;
431 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
432 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
433 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
434 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
435 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
436 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
437 int whiteFlag = FALSE, blackFlag = FALSE;
438 int userOfferedDraw = FALSE;
439 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
440 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
441 int cmailMoveType[CMAIL_MAX_GAMES];
442 long ics_clock_paused = 0;
443 ProcRef icsPR = NoProc, cmailPR = NoProc;
444 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
445 GameMode gameMode = BeginningOfGame;
446 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
447 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
448 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
449 int hiddenThinkOutputState = 0; /* [AS] */
450 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
451 int adjudicateLossPlies = 6;
452 char white_holding[64], black_holding[64];
453 TimeMark lastNodeCountTime;
454 long lastNodeCount=0;
455 int shiftKey; // [HGM] set by mouse handler
456
457 int have_sent_ICS_logon = 0;
458 int movesPerSession;
459 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
460 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
461 long timeControl_2; /* [AS] Allow separate time controls */
462 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
463 long timeRemaining[2][MAX_MOVES];
464 int matchGame = 0;
465 TimeMark programStartTime;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 int shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
531 };
532
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
538 };
539
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackMan, BlackFerz,
551         BlackKing, BlackMan, BlackKnight, BlackRook }
552 };
553
554
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
561 };
562
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
568 };
569
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
575 };
576
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
582 };
583
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
589 };
590
591 #ifdef GOTHIC
592 ChessSquare GothicArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
594         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
596         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
597 };
598 #else // !GOTHIC
599 #define GothicArray CapablancaArray
600 #endif // !GOTHIC
601
602 #ifdef FALCON
603 ChessSquare FalconArray[2][BOARD_FILES] = {
604     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
605         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
606     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
607         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
608 };
609 #else // !FALCON
610 #define FalconArray CapablancaArray
611 #endif // !FALCON
612
613 #else // !(BOARD_FILES>=10)
614 #define XiangqiPosition FIDEArray
615 #define CapablancaArray FIDEArray
616 #define GothicArray FIDEArray
617 #define GreatArray FIDEArray
618 #endif // !(BOARD_FILES>=10)
619
620 #if (BOARD_FILES>=12)
621 ChessSquare CourierArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
623         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
625         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
626 };
627 #else // !(BOARD_FILES>=12)
628 #define CourierArray CapablancaArray
629 #endif // !(BOARD_FILES>=12)
630
631
632 Board initialPosition;
633
634
635 /* Convert str to a rating. Checks for special cases of "----",
636
637    "++++", etc. Also strips ()'s */
638 int
639 string_to_rating(str)
640   char *str;
641 {
642   while(*str && !isdigit(*str)) ++str;
643   if (!*str)
644     return 0;   /* One of the special "no rating" cases */
645   else
646     return atoi(str);
647 }
648
649 void
650 ClearProgramStats()
651 {
652     /* Init programStats */
653     programStats.movelist[0] = 0;
654     programStats.depth = 0;
655     programStats.nr_moves = 0;
656     programStats.moves_left = 0;
657     programStats.nodes = 0;
658     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
659     programStats.score = 0;
660     programStats.got_only_move = 0;
661     programStats.got_fail = 0;
662     programStats.line_is_book = 0;
663 }
664
665 void
666 CommonEngineInit()
667 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
668     if (appData.firstPlaysBlack) {
669         first.twoMachinesColor = "black\n";
670         second.twoMachinesColor = "white\n";
671     } else {
672         first.twoMachinesColor = "white\n";
673         second.twoMachinesColor = "black\n";
674     }
675
676     first.other = &second;
677     second.other = &first;
678
679     { float norm = 1;
680         if(appData.timeOddsMode) {
681             norm = appData.timeOdds[0];
682             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
683         }
684         first.timeOdds  = appData.timeOdds[0]/norm;
685         second.timeOdds = appData.timeOdds[1]/norm;
686     }
687
688     if(programVersion) free(programVersion);
689     if (appData.noChessProgram) {
690         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
691         sprintf(programVersion, "%s", PACKAGE_STRING);
692     } else {
693       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
694       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
695       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
696     }
697 }
698
699 void
700 UnloadEngine(ChessProgramState *cps)
701 {
702         /* Kill off first chess program */
703         if (cps->isr != NULL)
704           RemoveInputSource(cps->isr);
705         cps->isr = NULL;
706
707         if (cps->pr != NoProc) {
708             ExitAnalyzeMode();
709             DoSleep( appData.delayBeforeQuit );
710             SendToProgram("quit\n", cps);
711             DoSleep( appData.delayAfterQuit );
712             DestroyChildProcess(cps->pr, cps->useSigterm);
713         }
714         cps->pr = NoProc;
715 }
716
717 void
718 ClearOptions(ChessProgramState *cps)
719 {
720     int i;
721     cps->nrOptions = cps->comboCnt = 0;
722     for(i=0; i<MAX_OPTIONS; i++) {
723         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
724         cps->option[i].textValue = 0;
725     }
726 }
727
728 char *engineNames[] = {
729 "first",
730 "second"
731 };
732
733 InitEngine(ChessProgramState *cps, int n)
734 {   // [HGM] all engine initialiation put in a function that does one engine
735
736     ClearOptions(cps);
737
738     cps->which = engineNames[n];
739     cps->maybeThinking = FALSE;
740     cps->pr = NoProc;
741     cps->isr = NULL;
742     cps->sendTime = 2;
743     cps->sendDrawOffers = 1;
744
745     cps->program = appData.chessProgram[n];
746     cps->host = appData.host[n];
747     cps->dir = appData.directory[n];
748     cps->initString = appData.engInitString[n];
749     cps->computerString = appData.computerString[n];
750     cps->useSigint  = TRUE;
751     cps->useSigterm = TRUE;
752     cps->reuse = appData.reuse[n];
753     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
754     cps->useSetboard = FALSE;
755     cps->useSAN = FALSE;
756     cps->usePing = FALSE;
757     cps->lastPing = 0;
758     cps->lastPong = 0;
759     cps->usePlayother = FALSE;
760     cps->useColors = TRUE;
761     cps->useUsermove = FALSE;
762     cps->sendICS = FALSE;
763     cps->sendName = appData.icsActive;
764     cps->sdKludge = FALSE;
765     cps->stKludge = FALSE;
766     TidyProgramName(cps->program, cps->host, cps->tidy);
767     cps->matchWins = 0;
768     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
769     cps->analysisSupport = 2; /* detect */
770     cps->analyzing = FALSE;
771     cps->initDone = FALSE;
772
773     /* New features added by Tord: */
774     cps->useFEN960 = FALSE;
775     cps->useOOCastle = TRUE;
776     /* End of new features added by Tord. */
777     cps->fenOverride  = appData.fenOverride[n];
778
779     /* [HGM] time odds: set factor for each machine */
780     cps->timeOdds  = appData.timeOdds[n];
781
782     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
783     cps->accumulateTC = appData.accumulateTC[n];
784     cps->maxNrOfSessions = 1;
785
786     /* [HGM] debug */
787     cps->debug = FALSE;
788     cps->supportsNPS = UNKNOWN;
789
790     /* [HGM] options */
791     cps->optionSettings  = appData.engOptions[n];
792
793     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
794     cps->isUCI = appData.isUCI[n]; /* [AS] */
795     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
796
797     if (appData.protocolVersion[n] > PROTOVER
798         || appData.protocolVersion[n] < 1)
799       {
800         char buf[MSG_SIZ];
801         int len;
802
803         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
804                        appData.protocolVersion[n]);
805         if( (len > MSG_SIZ) && appData.debugMode )
806           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
807
808         DisplayFatalError(buf, 0, 2);
809       }
810     else
811       {
812         cps->protocolVersion = appData.protocolVersion[n];
813       }
814
815     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
816 }
817
818 ChessProgramState *savCps;
819
820 void
821 LoadEngine()
822 {
823     int i;
824     if(WaitForEngine(savCps, LoadEngine)) return;
825     CommonEngineInit(); // recalculate time odds
826     if(gameInfo.variant != StringToVariant(appData.variant)) {
827         // we changed variant when loading the engine; this forces us to reset
828         Reset(TRUE, savCps != &first);
829         EditGameEvent(); // for consistency with other path, as Reset changes mode
830     }
831     InitChessProgram(savCps, FALSE);
832     SendToProgram("force\n", savCps);
833     DisplayMessage("", "");
834     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
835     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
836     ThawUI();
837     SetGNUMode();
838 }
839
840 void
841 ReplaceEngine(ChessProgramState *cps, int n)
842 {
843     EditGameEvent();
844     UnloadEngine(cps);
845     appData.noChessProgram = False;
846     appData.clockMode = True;
847     InitEngine(cps, n);
848     if(n) return; // only startup first engine immediately; second can wait
849     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
850     LoadEngine();
851 }
852
853 void
854 InitBackEnd1()
855 {
856     int matched, min, sec;
857
858     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
859     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
860
861     GetTimeMark(&programStartTime);
862     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
863
864     ClearProgramStats();
865     programStats.ok_to_send = 1;
866     programStats.seen_stat = 0;
867
868     /*
869      * Initialize game list
870      */
871     ListNew(&gameList);
872
873
874     /*
875      * Internet chess server status
876      */
877     if (appData.icsActive) {
878         appData.matchMode = FALSE;
879         appData.matchGames = 0;
880 #if ZIPPY
881         appData.noChessProgram = !appData.zippyPlay;
882 #else
883         appData.zippyPlay = FALSE;
884         appData.zippyTalk = FALSE;
885         appData.noChessProgram = TRUE;
886 #endif
887         if (*appData.icsHelper != NULLCHAR) {
888             appData.useTelnet = TRUE;
889             appData.telnetProgram = appData.icsHelper;
890         }
891     } else {
892         appData.zippyTalk = appData.zippyPlay = FALSE;
893     }
894
895     /* [AS] Initialize pv info list [HGM] and game state */
896     {
897         int i, j;
898
899         for( i=0; i<=framePtr; i++ ) {
900             pvInfoList[i].depth = -1;
901             boards[i][EP_STATUS] = EP_NONE;
902             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
903         }
904     }
905
906     /*
907      * Parse timeControl resource
908      */
909     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
910                           appData.movesPerSession)) {
911         char buf[MSG_SIZ];
912         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
913         DisplayFatalError(buf, 0, 2);
914     }
915
916     /*
917      * Parse searchTime resource
918      */
919     if (*appData.searchTime != NULLCHAR) {
920         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
921         if (matched == 1) {
922             searchTime = min * 60;
923         } else if (matched == 2) {
924             searchTime = min * 60 + sec;
925         } else {
926             char buf[MSG_SIZ];
927             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
928             DisplayFatalError(buf, 0, 2);
929         }
930     }
931
932     /* [AS] Adjudication threshold */
933     adjudicateLossThreshold = appData.adjudicateLossThreshold;
934
935     InitEngine(&first, 0);
936     InitEngine(&second, 1);
937     CommonEngineInit();
938
939     if (appData.icsActive) {
940         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
941     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
942         appData.clockMode = FALSE;
943         first.sendTime = second.sendTime = 0;
944     }
945
946 #if ZIPPY
947     /* Override some settings from environment variables, for backward
948        compatibility.  Unfortunately it's not feasible to have the env
949        vars just set defaults, at least in xboard.  Ugh.
950     */
951     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
952       ZippyInit();
953     }
954 #endif
955
956     if (!appData.icsActive) {
957       char buf[MSG_SIZ];
958       int len;
959
960       /* Check for variants that are supported only in ICS mode,
961          or not at all.  Some that are accepted here nevertheless
962          have bugs; see comments below.
963       */
964       VariantClass variant = StringToVariant(appData.variant);
965       switch (variant) {
966       case VariantBughouse:     /* need four players and two boards */
967       case VariantKriegspiel:   /* need to hide pieces and move details */
968         /* case VariantFischeRandom: (Fabien: moved below) */
969         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
970         if( (len > MSG_SIZ) && appData.debugMode )
971           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
972
973         DisplayFatalError(buf, 0, 2);
974         return;
975
976       case VariantUnknown:
977       case VariantLoadable:
978       case Variant29:
979       case Variant30:
980       case Variant31:
981       case Variant32:
982       case Variant33:
983       case Variant34:
984       case Variant35:
985       case Variant36:
986       default:
987         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
988         if( (len > MSG_SIZ) && appData.debugMode )
989           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
990
991         DisplayFatalError(buf, 0, 2);
992         return;
993
994       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
995       case VariantFairy:      /* [HGM] TestLegality definitely off! */
996       case VariantGothic:     /* [HGM] should work */
997       case VariantCapablanca: /* [HGM] should work */
998       case VariantCourier:    /* [HGM] initial forced moves not implemented */
999       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1000       case VariantKnightmate: /* [HGM] should work */
1001       case VariantCylinder:   /* [HGM] untested */
1002       case VariantFalcon:     /* [HGM] untested */
1003       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1004                                  offboard interposition not understood */
1005       case VariantNormal:     /* definitely works! */
1006       case VariantWildCastle: /* pieces not automatically shuffled */
1007       case VariantNoCastle:   /* pieces not automatically shuffled */
1008       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1009       case VariantLosers:     /* should work except for win condition,
1010                                  and doesn't know captures are mandatory */
1011       case VariantSuicide:    /* should work except for win condition,
1012                                  and doesn't know captures are mandatory */
1013       case VariantGiveaway:   /* should work except for win condition,
1014                                  and doesn't know captures are mandatory */
1015       case VariantTwoKings:   /* should work */
1016       case VariantAtomic:     /* should work except for win condition */
1017       case Variant3Check:     /* should work except for win condition */
1018       case VariantShatranj:   /* should work except for all win conditions */
1019       case VariantMakruk:     /* should work except for daw countdown */
1020       case VariantBerolina:   /* might work if TestLegality is off */
1021       case VariantCapaRandom: /* should work */
1022       case VariantJanus:      /* should work */
1023       case VariantSuper:      /* experimental */
1024       case VariantGreat:      /* experimental, requires legality testing to be off */
1025       case VariantSChess:     /* S-Chess, should work */
1026       case VariantSpartan:    /* should work */
1027         break;
1028       }
1029     }
1030
1031 }
1032
1033 int NextIntegerFromString( char ** str, long * value )
1034 {
1035     int result = -1;
1036     char * s = *str;
1037
1038     while( *s == ' ' || *s == '\t' ) {
1039         s++;
1040     }
1041
1042     *value = 0;
1043
1044     if( *s >= '0' && *s <= '9' ) {
1045         while( *s >= '0' && *s <= '9' ) {
1046             *value = *value * 10 + (*s - '0');
1047             s++;
1048         }
1049
1050         result = 0;
1051     }
1052
1053     *str = s;
1054
1055     return result;
1056 }
1057
1058 int NextTimeControlFromString( char ** str, long * value )
1059 {
1060     long temp;
1061     int result = NextIntegerFromString( str, &temp );
1062
1063     if( result == 0 ) {
1064         *value = temp * 60; /* Minutes */
1065         if( **str == ':' ) {
1066             (*str)++;
1067             result = NextIntegerFromString( str, &temp );
1068             *value += temp; /* Seconds */
1069         }
1070     }
1071
1072     return result;
1073 }
1074
1075 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1076 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1077     int result = -1, type = 0; long temp, temp2;
1078
1079     if(**str != ':') return -1; // old params remain in force!
1080     (*str)++;
1081     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1082     if( NextIntegerFromString( str, &temp ) ) return -1;
1083     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1084
1085     if(**str != '/') {
1086         /* time only: incremental or sudden-death time control */
1087         if(**str == '+') { /* increment follows; read it */
1088             (*str)++;
1089             if(**str == '!') type = *(*str)++; // Bronstein TC
1090             if(result = NextIntegerFromString( str, &temp2)) return -1;
1091             *inc = temp2 * 1000;
1092             if(**str == '.') { // read fraction of increment
1093                 char *start = ++(*str);
1094                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1095                 temp2 *= 1000;
1096                 while(start++ < *str) temp2 /= 10;
1097                 *inc += temp2;
1098             }
1099         } else *inc = 0;
1100         *moves = 0; *tc = temp * 1000; *incType = type;
1101         return 0;
1102     }
1103
1104     (*str)++; /* classical time control */
1105     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1106
1107     if(result == 0) {
1108         *moves = temp;
1109         *tc    = temp2 * 1000;
1110         *inc   = 0;
1111         *incType = type;
1112     }
1113     return result;
1114 }
1115
1116 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1117 {   /* [HGM] get time to add from the multi-session time-control string */
1118     int incType, moves=1; /* kludge to force reading of first session */
1119     long time, increment;
1120     char *s = tcString;
1121
1122     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1123     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1124     do {
1125         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1126         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1127         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1128         if(movenr == -1) return time;    /* last move before new session     */
1129         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1130         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1131         if(!moves) return increment;     /* current session is incremental   */
1132         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1133     } while(movenr >= -1);               /* try again for next session       */
1134
1135     return 0; // no new time quota on this move
1136 }
1137
1138 int
1139 ParseTimeControl(tc, ti, mps)
1140      char *tc;
1141      float ti;
1142      int mps;
1143 {
1144   long tc1;
1145   long tc2;
1146   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1147   int min, sec=0;
1148
1149   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1150   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1151       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1152   if(ti > 0) {
1153
1154     if(mps)
1155       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1156     else 
1157       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1158   } else {
1159     if(mps)
1160       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1161     else 
1162       snprintf(buf, MSG_SIZ, ":%s", mytc);
1163   }
1164   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1165   
1166   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1167     return FALSE;
1168   }
1169
1170   if( *tc == '/' ) {
1171     /* Parse second time control */
1172     tc++;
1173
1174     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1175       return FALSE;
1176     }
1177
1178     if( tc2 == 0 ) {
1179       return FALSE;
1180     }
1181
1182     timeControl_2 = tc2 * 1000;
1183   }
1184   else {
1185     timeControl_2 = 0;
1186   }
1187
1188   if( tc1 == 0 ) {
1189     return FALSE;
1190   }
1191
1192   timeControl = tc1 * 1000;
1193
1194   if (ti >= 0) {
1195     timeIncrement = ti * 1000;  /* convert to ms */
1196     movesPerSession = 0;
1197   } else {
1198     timeIncrement = 0;
1199     movesPerSession = mps;
1200   }
1201   return TRUE;
1202 }
1203
1204 void
1205 InitBackEnd2()
1206 {
1207     if (appData.debugMode) {
1208         fprintf(debugFP, "%s\n", programVersion);
1209     }
1210
1211     set_cont_sequence(appData.wrapContSeq);
1212     if (appData.matchGames > 0) {
1213         appData.matchMode = TRUE;
1214     } else if (appData.matchMode) {
1215         appData.matchGames = 1;
1216     }
1217     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1218         appData.matchGames = appData.sameColorGames;
1219     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1220         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1221         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1222     }
1223     Reset(TRUE, FALSE);
1224     if (appData.noChessProgram || first.protocolVersion == 1) {
1225       InitBackEnd3();
1226     } else {
1227       /* kludge: allow timeout for initial "feature" commands */
1228       FreezeUI();
1229       DisplayMessage("", _("Starting chess program"));
1230       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1231     }
1232 }
1233
1234 void
1235 MatchEvent(int mode)
1236 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1237         /* Set up machine vs. machine match */
1238         if (appData.noChessProgram) {
1239             DisplayFatalError(_("Can't have a match with no chess programs"),
1240                               0, 2);
1241             return;
1242         }
1243         matchMode = mode;
1244         matchGame = 1;
1245         if (*appData.loadGameFile != NULLCHAR) {
1246             int index = appData.loadGameIndex; // [HGM] autoinc
1247             if(index<0) lastIndex = index = 1;
1248             if (!LoadGameFromFile(appData.loadGameFile,
1249                                   index,
1250                                   appData.loadGameFile, FALSE)) {
1251                 DisplayFatalError(_("Bad game file"), 0, 1);
1252                 return;
1253             }
1254         } else if (*appData.loadPositionFile != NULLCHAR) {
1255             int index = appData.loadPositionIndex; // [HGM] autoinc
1256             if(index<0) lastIndex = index = 1;
1257             if (!LoadPositionFromFile(appData.loadPositionFile,
1258                                       index,
1259                                       appData.loadPositionFile)) {
1260                 DisplayFatalError(_("Bad position file"), 0, 1);
1261                 return;
1262             }
1263         }
1264         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1265         TwoMachinesEvent();
1266 }
1267
1268 void
1269 InitBackEnd3 P((void))
1270 {
1271     GameMode initialMode;
1272     char buf[MSG_SIZ];
1273     int err, len;
1274
1275     InitChessProgram(&first, startedFromSetupPosition);
1276
1277     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1278         free(programVersion);
1279         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1280         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1281     }
1282
1283     if (appData.icsActive) {
1284 #ifdef WIN32
1285         /* [DM] Make a console window if needed [HGM] merged ifs */
1286         ConsoleCreate();
1287 #endif
1288         err = establish();
1289         if (err != 0)
1290           {
1291             if (*appData.icsCommPort != NULLCHAR)
1292               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1293                              appData.icsCommPort);
1294             else
1295               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1296                         appData.icsHost, appData.icsPort);
1297
1298             if( (len > MSG_SIZ) && appData.debugMode )
1299               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1300
1301             DisplayFatalError(buf, err, 1);
1302             return;
1303         }
1304         SetICSMode();
1305         telnetISR =
1306           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1307         fromUserISR =
1308           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1309         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1310             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1311     } else if (appData.noChessProgram) {
1312         SetNCPMode();
1313     } else {
1314         SetGNUMode();
1315     }
1316
1317     if (*appData.cmailGameName != NULLCHAR) {
1318         SetCmailMode();
1319         OpenLoopback(&cmailPR);
1320         cmailISR =
1321           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1322     }
1323
1324     ThawUI();
1325     DisplayMessage("", "");
1326     if (StrCaseCmp(appData.initialMode, "") == 0) {
1327       initialMode = BeginningOfGame;
1328       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1329         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1330         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1331         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1332         ModeHighlight();
1333       }
1334     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1335       initialMode = TwoMachinesPlay;
1336     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1337       initialMode = AnalyzeFile;
1338     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1339       initialMode = AnalyzeMode;
1340     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1341       initialMode = MachinePlaysWhite;
1342     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1343       initialMode = MachinePlaysBlack;
1344     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1345       initialMode = EditGame;
1346     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1347       initialMode = EditPosition;
1348     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1349       initialMode = Training;
1350     } else {
1351       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1352       if( (len > MSG_SIZ) && appData.debugMode )
1353         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1354
1355       DisplayFatalError(buf, 0, 2);
1356       return;
1357     }
1358
1359     if (appData.matchMode) {
1360         MatchEvent(TRUE);
1361     } else if (*appData.cmailGameName != NULLCHAR) {
1362         /* Set up cmail mode */
1363         ReloadCmailMsgEvent(TRUE);
1364     } else {
1365         /* Set up other modes */
1366         if (initialMode == AnalyzeFile) {
1367           if (*appData.loadGameFile == NULLCHAR) {
1368             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1369             return;
1370           }
1371         }
1372         if (*appData.loadGameFile != NULLCHAR) {
1373             (void) LoadGameFromFile(appData.loadGameFile,
1374                                     appData.loadGameIndex,
1375                                     appData.loadGameFile, TRUE);
1376         } else if (*appData.loadPositionFile != NULLCHAR) {
1377             (void) LoadPositionFromFile(appData.loadPositionFile,
1378                                         appData.loadPositionIndex,
1379                                         appData.loadPositionFile);
1380             /* [HGM] try to make self-starting even after FEN load */
1381             /* to allow automatic setup of fairy variants with wtm */
1382             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1383                 gameMode = BeginningOfGame;
1384                 setboardSpoiledMachineBlack = 1;
1385             }
1386             /* [HGM] loadPos: make that every new game uses the setup */
1387             /* from file as long as we do not switch variant          */
1388             if(!blackPlaysFirst) {
1389                 startedFromPositionFile = TRUE;
1390                 CopyBoard(filePosition, boards[0]);
1391             }
1392         }
1393         if (initialMode == AnalyzeMode) {
1394           if (appData.noChessProgram) {
1395             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1396             return;
1397           }
1398           if (appData.icsActive) {
1399             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1400             return;
1401           }
1402           AnalyzeModeEvent();
1403         } else if (initialMode == AnalyzeFile) {
1404           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1405           ShowThinkingEvent();
1406           AnalyzeFileEvent();
1407           AnalysisPeriodicEvent(1);
1408         } else if (initialMode == MachinePlaysWhite) {
1409           if (appData.noChessProgram) {
1410             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1411                               0, 2);
1412             return;
1413           }
1414           if (appData.icsActive) {
1415             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1416                               0, 2);
1417             return;
1418           }
1419           MachineWhiteEvent();
1420         } else if (initialMode == MachinePlaysBlack) {
1421           if (appData.noChessProgram) {
1422             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1423                               0, 2);
1424             return;
1425           }
1426           if (appData.icsActive) {
1427             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1428                               0, 2);
1429             return;
1430           }
1431           MachineBlackEvent();
1432         } else if (initialMode == TwoMachinesPlay) {
1433           if (appData.noChessProgram) {
1434             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1435                               0, 2);
1436             return;
1437           }
1438           if (appData.icsActive) {
1439             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1440                               0, 2);
1441             return;
1442           }
1443           TwoMachinesEvent();
1444         } else if (initialMode == EditGame) {
1445           EditGameEvent();
1446         } else if (initialMode == EditPosition) {
1447           EditPositionEvent();
1448         } else if (initialMode == Training) {
1449           if (*appData.loadGameFile == NULLCHAR) {
1450             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1451             return;
1452           }
1453           TrainingEvent();
1454         }
1455     }
1456 }
1457
1458 /*
1459  * Establish will establish a contact to a remote host.port.
1460  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1461  *  used to talk to the host.
1462  * Returns 0 if okay, error code if not.
1463  */
1464 int
1465 establish()
1466 {
1467     char buf[MSG_SIZ];
1468
1469     if (*appData.icsCommPort != NULLCHAR) {
1470         /* Talk to the host through a serial comm port */
1471         return OpenCommPort(appData.icsCommPort, &icsPR);
1472
1473     } else if (*appData.gateway != NULLCHAR) {
1474         if (*appData.remoteShell == NULLCHAR) {
1475             /* Use the rcmd protocol to run telnet program on a gateway host */
1476             snprintf(buf, sizeof(buf), "%s %s %s",
1477                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1478             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1479
1480         } else {
1481             /* Use the rsh program to run telnet program on a gateway host */
1482             if (*appData.remoteUser == NULLCHAR) {
1483                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1484                         appData.gateway, appData.telnetProgram,
1485                         appData.icsHost, appData.icsPort);
1486             } else {
1487                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1488                         appData.remoteShell, appData.gateway,
1489                         appData.remoteUser, appData.telnetProgram,
1490                         appData.icsHost, appData.icsPort);
1491             }
1492             return StartChildProcess(buf, "", &icsPR);
1493
1494         }
1495     } else if (appData.useTelnet) {
1496         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1497
1498     } else {
1499         /* TCP socket interface differs somewhat between
1500            Unix and NT; handle details in the front end.
1501            */
1502         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1503     }
1504 }
1505
1506 void EscapeExpand(char *p, char *q)
1507 {       // [HGM] initstring: routine to shape up string arguments
1508         while(*p++ = *q++) if(p[-1] == '\\')
1509             switch(*q++) {
1510                 case 'n': p[-1] = '\n'; break;
1511                 case 'r': p[-1] = '\r'; break;
1512                 case 't': p[-1] = '\t'; break;
1513                 case '\\': p[-1] = '\\'; break;
1514                 case 0: *p = 0; return;
1515                 default: p[-1] = q[-1]; break;
1516             }
1517 }
1518
1519 void
1520 show_bytes(fp, buf, count)
1521      FILE *fp;
1522      char *buf;
1523      int count;
1524 {
1525     while (count--) {
1526         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1527             fprintf(fp, "\\%03o", *buf & 0xff);
1528         } else {
1529             putc(*buf, fp);
1530         }
1531         buf++;
1532     }
1533     fflush(fp);
1534 }
1535
1536 /* Returns an errno value */
1537 int
1538 OutputMaybeTelnet(pr, message, count, outError)
1539      ProcRef pr;
1540      char *message;
1541      int count;
1542      int *outError;
1543 {
1544     char buf[8192], *p, *q, *buflim;
1545     int left, newcount, outcount;
1546
1547     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1548         *appData.gateway != NULLCHAR) {
1549         if (appData.debugMode) {
1550             fprintf(debugFP, ">ICS: ");
1551             show_bytes(debugFP, message, count);
1552             fprintf(debugFP, "\n");
1553         }
1554         return OutputToProcess(pr, message, count, outError);
1555     }
1556
1557     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1558     p = message;
1559     q = buf;
1560     left = count;
1561     newcount = 0;
1562     while (left) {
1563         if (q >= buflim) {
1564             if (appData.debugMode) {
1565                 fprintf(debugFP, ">ICS: ");
1566                 show_bytes(debugFP, buf, newcount);
1567                 fprintf(debugFP, "\n");
1568             }
1569             outcount = OutputToProcess(pr, buf, newcount, outError);
1570             if (outcount < newcount) return -1; /* to be sure */
1571             q = buf;
1572             newcount = 0;
1573         }
1574         if (*p == '\n') {
1575             *q++ = '\r';
1576             newcount++;
1577         } else if (((unsigned char) *p) == TN_IAC) {
1578             *q++ = (char) TN_IAC;
1579             newcount ++;
1580         }
1581         *q++ = *p++;
1582         newcount++;
1583         left--;
1584     }
1585     if (appData.debugMode) {
1586         fprintf(debugFP, ">ICS: ");
1587         show_bytes(debugFP, buf, newcount);
1588         fprintf(debugFP, "\n");
1589     }
1590     outcount = OutputToProcess(pr, buf, newcount, outError);
1591     if (outcount < newcount) return -1; /* to be sure */
1592     return count;
1593 }
1594
1595 void
1596 read_from_player(isr, closure, message, count, error)
1597      InputSourceRef isr;
1598      VOIDSTAR closure;
1599      char *message;
1600      int count;
1601      int error;
1602 {
1603     int outError, outCount;
1604     static int gotEof = 0;
1605
1606     /* Pass data read from player on to ICS */
1607     if (count > 0) {
1608         gotEof = 0;
1609         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1610         if (outCount < count) {
1611             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1612         }
1613     } else if (count < 0) {
1614         RemoveInputSource(isr);
1615         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1616     } else if (gotEof++ > 0) {
1617         RemoveInputSource(isr);
1618         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1619     }
1620 }
1621
1622 void
1623 KeepAlive()
1624 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1625     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1626     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1627     SendToICS("date\n");
1628     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1629 }
1630
1631 /* added routine for printf style output to ics */
1632 void ics_printf(char *format, ...)
1633 {
1634     char buffer[MSG_SIZ];
1635     va_list args;
1636
1637     va_start(args, format);
1638     vsnprintf(buffer, sizeof(buffer), format, args);
1639     buffer[sizeof(buffer)-1] = '\0';
1640     SendToICS(buffer);
1641     va_end(args);
1642 }
1643
1644 void
1645 SendToICS(s)
1646      char *s;
1647 {
1648     int count, outCount, outError;
1649
1650     if (icsPR == NULL) return;
1651
1652     count = strlen(s);
1653     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1654     if (outCount < count) {
1655         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1656     }
1657 }
1658
1659 /* This is used for sending logon scripts to the ICS. Sending
1660    without a delay causes problems when using timestamp on ICC
1661    (at least on my machine). */
1662 void
1663 SendToICSDelayed(s,msdelay)
1664      char *s;
1665      long msdelay;
1666 {
1667     int count, outCount, outError;
1668
1669     if (icsPR == NULL) return;
1670
1671     count = strlen(s);
1672     if (appData.debugMode) {
1673         fprintf(debugFP, ">ICS: ");
1674         show_bytes(debugFP, s, count);
1675         fprintf(debugFP, "\n");
1676     }
1677     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1678                                       msdelay);
1679     if (outCount < count) {
1680         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1681     }
1682 }
1683
1684
1685 /* Remove all highlighting escape sequences in s
1686    Also deletes any suffix starting with '('
1687    */
1688 char *
1689 StripHighlightAndTitle(s)
1690      char *s;
1691 {
1692     static char retbuf[MSG_SIZ];
1693     char *p = retbuf;
1694
1695     while (*s != NULLCHAR) {
1696         while (*s == '\033') {
1697             while (*s != NULLCHAR && !isalpha(*s)) s++;
1698             if (*s != NULLCHAR) s++;
1699         }
1700         while (*s != NULLCHAR && *s != '\033') {
1701             if (*s == '(' || *s == '[') {
1702                 *p = NULLCHAR;
1703                 return retbuf;
1704             }
1705             *p++ = *s++;
1706         }
1707     }
1708     *p = NULLCHAR;
1709     return retbuf;
1710 }
1711
1712 /* Remove all highlighting escape sequences in s */
1713 char *
1714 StripHighlight(s)
1715      char *s;
1716 {
1717     static char retbuf[MSG_SIZ];
1718     char *p = retbuf;
1719
1720     while (*s != NULLCHAR) {
1721         while (*s == '\033') {
1722             while (*s != NULLCHAR && !isalpha(*s)) s++;
1723             if (*s != NULLCHAR) s++;
1724         }
1725         while (*s != NULLCHAR && *s != '\033') {
1726             *p++ = *s++;
1727         }
1728     }
1729     *p = NULLCHAR;
1730     return retbuf;
1731 }
1732
1733 char *variantNames[] = VARIANT_NAMES;
1734 char *
1735 VariantName(v)
1736      VariantClass v;
1737 {
1738     return variantNames[v];
1739 }
1740
1741
1742 /* Identify a variant from the strings the chess servers use or the
1743    PGN Variant tag names we use. */
1744 VariantClass
1745 StringToVariant(e)
1746      char *e;
1747 {
1748     char *p;
1749     int wnum = -1;
1750     VariantClass v = VariantNormal;
1751     int i, found = FALSE;
1752     char buf[MSG_SIZ];
1753     int len;
1754
1755     if (!e) return v;
1756
1757     /* [HGM] skip over optional board-size prefixes */
1758     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1759         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1760         while( *e++ != '_');
1761     }
1762
1763     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1764         v = VariantNormal;
1765         found = TRUE;
1766     } else
1767     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1768       if (StrCaseStr(e, variantNames[i])) {
1769         v = (VariantClass) i;
1770         found = TRUE;
1771         break;
1772       }
1773     }
1774
1775     if (!found) {
1776       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1777           || StrCaseStr(e, "wild/fr")
1778           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1779         v = VariantFischeRandom;
1780       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1781                  (i = 1, p = StrCaseStr(e, "w"))) {
1782         p += i;
1783         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1784         if (isdigit(*p)) {
1785           wnum = atoi(p);
1786         } else {
1787           wnum = -1;
1788         }
1789         switch (wnum) {
1790         case 0: /* FICS only, actually */
1791         case 1:
1792           /* Castling legal even if K starts on d-file */
1793           v = VariantWildCastle;
1794           break;
1795         case 2:
1796         case 3:
1797         case 4:
1798           /* Castling illegal even if K & R happen to start in
1799              normal positions. */
1800           v = VariantNoCastle;
1801           break;
1802         case 5:
1803         case 7:
1804         case 8:
1805         case 10:
1806         case 11:
1807         case 12:
1808         case 13:
1809         case 14:
1810         case 15:
1811         case 18:
1812         case 19:
1813           /* Castling legal iff K & R start in normal positions */
1814           v = VariantNormal;
1815           break;
1816         case 6:
1817         case 20:
1818         case 21:
1819           /* Special wilds for position setup; unclear what to do here */
1820           v = VariantLoadable;
1821           break;
1822         case 9:
1823           /* Bizarre ICC game */
1824           v = VariantTwoKings;
1825           break;
1826         case 16:
1827           v = VariantKriegspiel;
1828           break;
1829         case 17:
1830           v = VariantLosers;
1831           break;
1832         case 22:
1833           v = VariantFischeRandom;
1834           break;
1835         case 23:
1836           v = VariantCrazyhouse;
1837           break;
1838         case 24:
1839           v = VariantBughouse;
1840           break;
1841         case 25:
1842           v = Variant3Check;
1843           break;
1844         case 26:
1845           /* Not quite the same as FICS suicide! */
1846           v = VariantGiveaway;
1847           break;
1848         case 27:
1849           v = VariantAtomic;
1850           break;
1851         case 28:
1852           v = VariantShatranj;
1853           break;
1854
1855         /* Temporary names for future ICC types.  The name *will* change in
1856            the next xboard/WinBoard release after ICC defines it. */
1857         case 29:
1858           v = Variant29;
1859           break;
1860         case 30:
1861           v = Variant30;
1862           break;
1863         case 31:
1864           v = Variant31;
1865           break;
1866         case 32:
1867           v = Variant32;
1868           break;
1869         case 33:
1870           v = Variant33;
1871           break;
1872         case 34:
1873           v = Variant34;
1874           break;
1875         case 35:
1876           v = Variant35;
1877           break;
1878         case 36:
1879           v = Variant36;
1880           break;
1881         case 37:
1882           v = VariantShogi;
1883           break;
1884         case 38:
1885           v = VariantXiangqi;
1886           break;
1887         case 39:
1888           v = VariantCourier;
1889           break;
1890         case 40:
1891           v = VariantGothic;
1892           break;
1893         case 41:
1894           v = VariantCapablanca;
1895           break;
1896         case 42:
1897           v = VariantKnightmate;
1898           break;
1899         case 43:
1900           v = VariantFairy;
1901           break;
1902         case 44:
1903           v = VariantCylinder;
1904           break;
1905         case 45:
1906           v = VariantFalcon;
1907           break;
1908         case 46:
1909           v = VariantCapaRandom;
1910           break;
1911         case 47:
1912           v = VariantBerolina;
1913           break;
1914         case 48:
1915           v = VariantJanus;
1916           break;
1917         case 49:
1918           v = VariantSuper;
1919           break;
1920         case 50:
1921           v = VariantGreat;
1922           break;
1923         case -1:
1924           /* Found "wild" or "w" in the string but no number;
1925              must assume it's normal chess. */
1926           v = VariantNormal;
1927           break;
1928         default:
1929           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1930           if( (len > MSG_SIZ) && appData.debugMode )
1931             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1932
1933           DisplayError(buf, 0);
1934           v = VariantUnknown;
1935           break;
1936         }
1937       }
1938     }
1939     if (appData.debugMode) {
1940       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1941               e, wnum, VariantName(v));
1942     }
1943     return v;
1944 }
1945
1946 static int leftover_start = 0, leftover_len = 0;
1947 char star_match[STAR_MATCH_N][MSG_SIZ];
1948
1949 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1950    advance *index beyond it, and set leftover_start to the new value of
1951    *index; else return FALSE.  If pattern contains the character '*', it
1952    matches any sequence of characters not containing '\r', '\n', or the
1953    character following the '*' (if any), and the matched sequence(s) are
1954    copied into star_match.
1955    */
1956 int
1957 looking_at(buf, index, pattern)
1958      char *buf;
1959      int *index;
1960      char *pattern;
1961 {
1962     char *bufp = &buf[*index], *patternp = pattern;
1963     int star_count = 0;
1964     char *matchp = star_match[0];
1965
1966     for (;;) {
1967         if (*patternp == NULLCHAR) {
1968             *index = leftover_start = bufp - buf;
1969             *matchp = NULLCHAR;
1970             return TRUE;
1971         }
1972         if (*bufp == NULLCHAR) return FALSE;
1973         if (*patternp == '*') {
1974             if (*bufp == *(patternp + 1)) {
1975                 *matchp = NULLCHAR;
1976                 matchp = star_match[++star_count];
1977                 patternp += 2;
1978                 bufp++;
1979                 continue;
1980             } else if (*bufp == '\n' || *bufp == '\r') {
1981                 patternp++;
1982                 if (*patternp == NULLCHAR)
1983                   continue;
1984                 else
1985                   return FALSE;
1986             } else {
1987                 *matchp++ = *bufp++;
1988                 continue;
1989             }
1990         }
1991         if (*patternp != *bufp) return FALSE;
1992         patternp++;
1993         bufp++;
1994     }
1995 }
1996
1997 void
1998 SendToPlayer(data, length)
1999      char *data;
2000      int length;
2001 {
2002     int error, outCount;
2003     outCount = OutputToProcess(NoProc, data, length, &error);
2004     if (outCount < length) {
2005         DisplayFatalError(_("Error writing to display"), error, 1);
2006     }
2007 }
2008
2009 void
2010 PackHolding(packed, holding)
2011      char packed[];
2012      char *holding;
2013 {
2014     char *p = holding;
2015     char *q = packed;
2016     int runlength = 0;
2017     int curr = 9999;
2018     do {
2019         if (*p == curr) {
2020             runlength++;
2021         } else {
2022             switch (runlength) {
2023               case 0:
2024                 break;
2025               case 1:
2026                 *q++ = curr;
2027                 break;
2028               case 2:
2029                 *q++ = curr;
2030                 *q++ = curr;
2031                 break;
2032               default:
2033                 sprintf(q, "%d", runlength);
2034                 while (*q) q++;
2035                 *q++ = curr;
2036                 break;
2037             }
2038             runlength = 1;
2039             curr = *p;
2040         }
2041     } while (*p++);
2042     *q = NULLCHAR;
2043 }
2044
2045 /* Telnet protocol requests from the front end */
2046 void
2047 TelnetRequest(ddww, option)
2048      unsigned char ddww, option;
2049 {
2050     unsigned char msg[3];
2051     int outCount, outError;
2052
2053     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2054
2055     if (appData.debugMode) {
2056         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2057         switch (ddww) {
2058           case TN_DO:
2059             ddwwStr = "DO";
2060             break;
2061           case TN_DONT:
2062             ddwwStr = "DONT";
2063             break;
2064           case TN_WILL:
2065             ddwwStr = "WILL";
2066             break;
2067           case TN_WONT:
2068             ddwwStr = "WONT";
2069             break;
2070           default:
2071             ddwwStr = buf1;
2072             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2073             break;
2074         }
2075         switch (option) {
2076           case TN_ECHO:
2077             optionStr = "ECHO";
2078             break;
2079           default:
2080             optionStr = buf2;
2081             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2082             break;
2083         }
2084         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2085     }
2086     msg[0] = TN_IAC;
2087     msg[1] = ddww;
2088     msg[2] = option;
2089     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2090     if (outCount < 3) {
2091         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2092     }
2093 }
2094
2095 void
2096 DoEcho()
2097 {
2098     if (!appData.icsActive) return;
2099     TelnetRequest(TN_DO, TN_ECHO);
2100 }
2101
2102 void
2103 DontEcho()
2104 {
2105     if (!appData.icsActive) return;
2106     TelnetRequest(TN_DONT, TN_ECHO);
2107 }
2108
2109 void
2110 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2111 {
2112     /* put the holdings sent to us by the server on the board holdings area */
2113     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2114     char p;
2115     ChessSquare piece;
2116
2117     if(gameInfo.holdingsWidth < 2)  return;
2118     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2119         return; // prevent overwriting by pre-board holdings
2120
2121     if( (int)lowestPiece >= BlackPawn ) {
2122         holdingsColumn = 0;
2123         countsColumn = 1;
2124         holdingsStartRow = BOARD_HEIGHT-1;
2125         direction = -1;
2126     } else {
2127         holdingsColumn = BOARD_WIDTH-1;
2128         countsColumn = BOARD_WIDTH-2;
2129         holdingsStartRow = 0;
2130         direction = 1;
2131     }
2132
2133     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2134         board[i][holdingsColumn] = EmptySquare;
2135         board[i][countsColumn]   = (ChessSquare) 0;
2136     }
2137     while( (p=*holdings++) != NULLCHAR ) {
2138         piece = CharToPiece( ToUpper(p) );
2139         if(piece == EmptySquare) continue;
2140         /*j = (int) piece - (int) WhitePawn;*/
2141         j = PieceToNumber(piece);
2142         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2143         if(j < 0) continue;               /* should not happen */
2144         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2145         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2146         board[holdingsStartRow+j*direction][countsColumn]++;
2147     }
2148 }
2149
2150
2151 void
2152 VariantSwitch(Board board, VariantClass newVariant)
2153 {
2154    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2155    static Board oldBoard;
2156
2157    startedFromPositionFile = FALSE;
2158    if(gameInfo.variant == newVariant) return;
2159
2160    /* [HGM] This routine is called each time an assignment is made to
2161     * gameInfo.variant during a game, to make sure the board sizes
2162     * are set to match the new variant. If that means adding or deleting
2163     * holdings, we shift the playing board accordingly
2164     * This kludge is needed because in ICS observe mode, we get boards
2165     * of an ongoing game without knowing the variant, and learn about the
2166     * latter only later. This can be because of the move list we requested,
2167     * in which case the game history is refilled from the beginning anyway,
2168     * but also when receiving holdings of a crazyhouse game. In the latter
2169     * case we want to add those holdings to the already received position.
2170     */
2171
2172
2173    if (appData.debugMode) {
2174      fprintf(debugFP, "Switch board from %s to %s\n",
2175              VariantName(gameInfo.variant), VariantName(newVariant));
2176      setbuf(debugFP, NULL);
2177    }
2178    shuffleOpenings = 0;       /* [HGM] shuffle */
2179    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2180    switch(newVariant)
2181      {
2182      case VariantShogi:
2183        newWidth = 9;  newHeight = 9;
2184        gameInfo.holdingsSize = 7;
2185      case VariantBughouse:
2186      case VariantCrazyhouse:
2187        newHoldingsWidth = 2; break;
2188      case VariantGreat:
2189        newWidth = 10;
2190      case VariantSuper:
2191        newHoldingsWidth = 2;
2192        gameInfo.holdingsSize = 8;
2193        break;
2194      case VariantGothic:
2195      case VariantCapablanca:
2196      case VariantCapaRandom:
2197        newWidth = 10;
2198      default:
2199        newHoldingsWidth = gameInfo.holdingsSize = 0;
2200      };
2201
2202    if(newWidth  != gameInfo.boardWidth  ||
2203       newHeight != gameInfo.boardHeight ||
2204       newHoldingsWidth != gameInfo.holdingsWidth ) {
2205
2206      /* shift position to new playing area, if needed */
2207      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2208        for(i=0; i<BOARD_HEIGHT; i++)
2209          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2210            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2211              board[i][j];
2212        for(i=0; i<newHeight; i++) {
2213          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2214          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2215        }
2216      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2217        for(i=0; i<BOARD_HEIGHT; i++)
2218          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2219            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2220              board[i][j];
2221      }
2222      gameInfo.boardWidth  = newWidth;
2223      gameInfo.boardHeight = newHeight;
2224      gameInfo.holdingsWidth = newHoldingsWidth;
2225      gameInfo.variant = newVariant;
2226      InitDrawingSizes(-2, 0);
2227    } else gameInfo.variant = newVariant;
2228    CopyBoard(oldBoard, board);   // remember correctly formatted board
2229      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2230    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2231 }
2232
2233 static int loggedOn = FALSE;
2234
2235 /*-- Game start info cache: --*/
2236 int gs_gamenum;
2237 char gs_kind[MSG_SIZ];
2238 static char player1Name[128] = "";
2239 static char player2Name[128] = "";
2240 static char cont_seq[] = "\n\\   ";
2241 static int player1Rating = -1;
2242 static int player2Rating = -1;
2243 /*----------------------------*/
2244
2245 ColorClass curColor = ColorNormal;
2246 int suppressKibitz = 0;
2247
2248 // [HGM] seekgraph
2249 Boolean soughtPending = FALSE;
2250 Boolean seekGraphUp;
2251 #define MAX_SEEK_ADS 200
2252 #define SQUARE 0x80
2253 char *seekAdList[MAX_SEEK_ADS];
2254 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2255 float tcList[MAX_SEEK_ADS];
2256 char colorList[MAX_SEEK_ADS];
2257 int nrOfSeekAds = 0;
2258 int minRating = 1010, maxRating = 2800;
2259 int hMargin = 10, vMargin = 20, h, w;
2260 extern int squareSize, lineGap;
2261
2262 void
2263 PlotSeekAd(int i)
2264 {
2265         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2266         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2267         if(r < minRating+100 && r >=0 ) r = minRating+100;
2268         if(r > maxRating) r = maxRating;
2269         if(tc < 1.) tc = 1.;
2270         if(tc > 95.) tc = 95.;
2271         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2272         y = ((double)r - minRating)/(maxRating - minRating)
2273             * (h-vMargin-squareSize/8-1) + vMargin;
2274         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2275         if(strstr(seekAdList[i], " u ")) color = 1;
2276         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2277            !strstr(seekAdList[i], "bullet") &&
2278            !strstr(seekAdList[i], "blitz") &&
2279            !strstr(seekAdList[i], "standard") ) color = 2;
2280         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2281         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2282 }
2283
2284 void
2285 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2286 {
2287         char buf[MSG_SIZ], *ext = "";
2288         VariantClass v = StringToVariant(type);
2289         if(strstr(type, "wild")) {
2290             ext = type + 4; // append wild number
2291             if(v == VariantFischeRandom) type = "chess960"; else
2292             if(v == VariantLoadable) type = "setup"; else
2293             type = VariantName(v);
2294         }
2295         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2296         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2297             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2298             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2299             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2300             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2301             seekNrList[nrOfSeekAds] = nr;
2302             zList[nrOfSeekAds] = 0;
2303             seekAdList[nrOfSeekAds++] = StrSave(buf);
2304             if(plot) PlotSeekAd(nrOfSeekAds-1);
2305         }
2306 }
2307
2308 void
2309 EraseSeekDot(int i)
2310 {
2311     int x = xList[i], y = yList[i], d=squareSize/4, k;
2312     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2313     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2314     // now replot every dot that overlapped
2315     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2316         int xx = xList[k], yy = yList[k];
2317         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2318             DrawSeekDot(xx, yy, colorList[k]);
2319     }
2320 }
2321
2322 void
2323 RemoveSeekAd(int nr)
2324 {
2325         int i;
2326         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2327             EraseSeekDot(i);
2328             if(seekAdList[i]) free(seekAdList[i]);
2329             seekAdList[i] = seekAdList[--nrOfSeekAds];
2330             seekNrList[i] = seekNrList[nrOfSeekAds];
2331             ratingList[i] = ratingList[nrOfSeekAds];
2332             colorList[i]  = colorList[nrOfSeekAds];
2333             tcList[i] = tcList[nrOfSeekAds];
2334             xList[i]  = xList[nrOfSeekAds];
2335             yList[i]  = yList[nrOfSeekAds];
2336             zList[i]  = zList[nrOfSeekAds];
2337             seekAdList[nrOfSeekAds] = NULL;
2338             break;
2339         }
2340 }
2341
2342 Boolean
2343 MatchSoughtLine(char *line)
2344 {
2345     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2346     int nr, base, inc, u=0; char dummy;
2347
2348     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2349        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2350        (u=1) &&
2351        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2352         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2353         // match: compact and save the line
2354         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2355         return TRUE;
2356     }
2357     return FALSE;
2358 }
2359
2360 int
2361 DrawSeekGraph()
2362 {
2363     int i;
2364     if(!seekGraphUp) return FALSE;
2365     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2366     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2367
2368     DrawSeekBackground(0, 0, w, h);
2369     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2370     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2371     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2372         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2373         yy = h-1-yy;
2374         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2375         if(i%500 == 0) {
2376             char buf[MSG_SIZ];
2377             snprintf(buf, MSG_SIZ, "%d", i);
2378             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2379         }
2380     }
2381     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2382     for(i=1; i<100; i+=(i<10?1:5)) {
2383         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2384         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2385         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2386             char buf[MSG_SIZ];
2387             snprintf(buf, MSG_SIZ, "%d", i);
2388             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2389         }
2390     }
2391     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2392     return TRUE;
2393 }
2394
2395 int SeekGraphClick(ClickType click, int x, int y, int moving)
2396 {
2397     static int lastDown = 0, displayed = 0, lastSecond;
2398     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2399         if(click == Release || moving) return FALSE;
2400         nrOfSeekAds = 0;
2401         soughtPending = TRUE;
2402         SendToICS(ics_prefix);
2403         SendToICS("sought\n"); // should this be "sought all"?
2404     } else { // issue challenge based on clicked ad
2405         int dist = 10000; int i, closest = 0, second = 0;
2406         for(i=0; i<nrOfSeekAds; i++) {
2407             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2408             if(d < dist) { dist = d; closest = i; }
2409             second += (d - zList[i] < 120); // count in-range ads
2410             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2411         }
2412         if(dist < 120) {
2413             char buf[MSG_SIZ];
2414             second = (second > 1);
2415             if(displayed != closest || second != lastSecond) {
2416                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2417                 lastSecond = second; displayed = closest;
2418             }
2419             if(click == Press) {
2420                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2421                 lastDown = closest;
2422                 return TRUE;
2423             } // on press 'hit', only show info
2424             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2425             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2426             SendToICS(ics_prefix);
2427             SendToICS(buf);
2428             return TRUE; // let incoming board of started game pop down the graph
2429         } else if(click == Release) { // release 'miss' is ignored
2430             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2431             if(moving == 2) { // right up-click
2432                 nrOfSeekAds = 0; // refresh graph
2433                 soughtPending = TRUE;
2434                 SendToICS(ics_prefix);
2435                 SendToICS("sought\n"); // should this be "sought all"?
2436             }
2437             return TRUE;
2438         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2439         // press miss or release hit 'pop down' seek graph
2440         seekGraphUp = FALSE;
2441         DrawPosition(TRUE, NULL);
2442     }
2443     return TRUE;
2444 }
2445
2446 void
2447 read_from_ics(isr, closure, data, count, error)
2448      InputSourceRef isr;
2449      VOIDSTAR closure;
2450      char *data;
2451      int count;
2452      int error;
2453 {
2454 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2455 #define STARTED_NONE 0
2456 #define STARTED_MOVES 1
2457 #define STARTED_BOARD 2
2458 #define STARTED_OBSERVE 3
2459 #define STARTED_HOLDINGS 4
2460 #define STARTED_CHATTER 5
2461 #define STARTED_COMMENT 6
2462 #define STARTED_MOVES_NOHIDE 7
2463
2464     static int started = STARTED_NONE;
2465     static char parse[20000];
2466     static int parse_pos = 0;
2467     static char buf[BUF_SIZE + 1];
2468     static int firstTime = TRUE, intfSet = FALSE;
2469     static ColorClass prevColor = ColorNormal;
2470     static int savingComment = FALSE;
2471     static int cmatch = 0; // continuation sequence match
2472     char *bp;
2473     char str[MSG_SIZ];
2474     int i, oldi;
2475     int buf_len;
2476     int next_out;
2477     int tkind;
2478     int backup;    /* [DM] For zippy color lines */
2479     char *p;
2480     char talker[MSG_SIZ]; // [HGM] chat
2481     int channel;
2482
2483     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2484
2485     if (appData.debugMode) {
2486       if (!error) {
2487         fprintf(debugFP, "<ICS: ");
2488         show_bytes(debugFP, data, count);
2489         fprintf(debugFP, "\n");
2490       }
2491     }
2492
2493     if (appData.debugMode) { int f = forwardMostMove;
2494         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2495                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2496                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2497     }
2498     if (count > 0) {
2499         /* If last read ended with a partial line that we couldn't parse,
2500            prepend it to the new read and try again. */
2501         if (leftover_len > 0) {
2502             for (i=0; i<leftover_len; i++)
2503               buf[i] = buf[leftover_start + i];
2504         }
2505
2506     /* copy new characters into the buffer */
2507     bp = buf + leftover_len;
2508     buf_len=leftover_len;
2509     for (i=0; i<count; i++)
2510     {
2511         // ignore these
2512         if (data[i] == '\r')
2513             continue;
2514
2515         // join lines split by ICS?
2516         if (!appData.noJoin)
2517         {
2518             /*
2519                 Joining just consists of finding matches against the
2520                 continuation sequence, and discarding that sequence
2521                 if found instead of copying it.  So, until a match
2522                 fails, there's nothing to do since it might be the
2523                 complete sequence, and thus, something we don't want
2524                 copied.
2525             */
2526             if (data[i] == cont_seq[cmatch])
2527             {
2528                 cmatch++;
2529                 if (cmatch == strlen(cont_seq))
2530                 {
2531                     cmatch = 0; // complete match.  just reset the counter
2532
2533                     /*
2534                         it's possible for the ICS to not include the space
2535                         at the end of the last word, making our [correct]
2536                         join operation fuse two separate words.  the server
2537                         does this when the space occurs at the width setting.
2538                     */
2539                     if (!buf_len || buf[buf_len-1] != ' ')
2540                     {
2541                         *bp++ = ' ';
2542                         buf_len++;
2543                     }
2544                 }
2545                 continue;
2546             }
2547             else if (cmatch)
2548             {
2549                 /*
2550                     match failed, so we have to copy what matched before
2551                     falling through and copying this character.  In reality,
2552                     this will only ever be just the newline character, but
2553                     it doesn't hurt to be precise.
2554                 */
2555                 strncpy(bp, cont_seq, cmatch);
2556                 bp += cmatch;
2557                 buf_len += cmatch;
2558                 cmatch = 0;
2559             }
2560         }
2561
2562         // copy this char
2563         *bp++ = data[i];
2564         buf_len++;
2565     }
2566
2567         buf[buf_len] = NULLCHAR;
2568 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2569         next_out = 0;
2570         leftover_start = 0;
2571
2572         i = 0;
2573         while (i < buf_len) {
2574             /* Deal with part of the TELNET option negotiation
2575                protocol.  We refuse to do anything beyond the
2576                defaults, except that we allow the WILL ECHO option,
2577                which ICS uses to turn off password echoing when we are
2578                directly connected to it.  We reject this option
2579                if localLineEditing mode is on (always on in xboard)
2580                and we are talking to port 23, which might be a real
2581                telnet server that will try to keep WILL ECHO on permanently.
2582              */
2583             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2584                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2585                 unsigned char option;
2586                 oldi = i;
2587                 switch ((unsigned char) buf[++i]) {
2588                   case TN_WILL:
2589                     if (appData.debugMode)
2590                       fprintf(debugFP, "\n<WILL ");
2591                     switch (option = (unsigned char) buf[++i]) {
2592                       case TN_ECHO:
2593                         if (appData.debugMode)
2594                           fprintf(debugFP, "ECHO ");
2595                         /* Reply only if this is a change, according
2596                            to the protocol rules. */
2597                         if (remoteEchoOption) break;
2598                         if (appData.localLineEditing &&
2599                             atoi(appData.icsPort) == TN_PORT) {
2600                             TelnetRequest(TN_DONT, TN_ECHO);
2601                         } else {
2602                             EchoOff();
2603                             TelnetRequest(TN_DO, TN_ECHO);
2604                             remoteEchoOption = TRUE;
2605                         }
2606                         break;
2607                       default:
2608                         if (appData.debugMode)
2609                           fprintf(debugFP, "%d ", option);
2610                         /* Whatever this is, we don't want it. */
2611                         TelnetRequest(TN_DONT, option);
2612                         break;
2613                     }
2614                     break;
2615                   case TN_WONT:
2616                     if (appData.debugMode)
2617                       fprintf(debugFP, "\n<WONT ");
2618                     switch (option = (unsigned char) buf[++i]) {
2619                       case TN_ECHO:
2620                         if (appData.debugMode)
2621                           fprintf(debugFP, "ECHO ");
2622                         /* Reply only if this is a change, according
2623                            to the protocol rules. */
2624                         if (!remoteEchoOption) break;
2625                         EchoOn();
2626                         TelnetRequest(TN_DONT, TN_ECHO);
2627                         remoteEchoOption = FALSE;
2628                         break;
2629                       default:
2630                         if (appData.debugMode)
2631                           fprintf(debugFP, "%d ", (unsigned char) option);
2632                         /* Whatever this is, it must already be turned
2633                            off, because we never agree to turn on
2634                            anything non-default, so according to the
2635                            protocol rules, we don't reply. */
2636                         break;
2637                     }
2638                     break;
2639                   case TN_DO:
2640                     if (appData.debugMode)
2641                       fprintf(debugFP, "\n<DO ");
2642                     switch (option = (unsigned char) buf[++i]) {
2643                       default:
2644                         /* Whatever this is, we refuse to do it. */
2645                         if (appData.debugMode)
2646                           fprintf(debugFP, "%d ", option);
2647                         TelnetRequest(TN_WONT, option);
2648                         break;
2649                     }
2650                     break;
2651                   case TN_DONT:
2652                     if (appData.debugMode)
2653                       fprintf(debugFP, "\n<DONT ");
2654                     switch (option = (unsigned char) buf[++i]) {
2655                       default:
2656                         if (appData.debugMode)
2657                           fprintf(debugFP, "%d ", option);
2658                         /* Whatever this is, we are already not doing
2659                            it, because we never agree to do anything
2660                            non-default, so according to the protocol
2661                            rules, we don't reply. */
2662                         break;
2663                     }
2664                     break;
2665                   case TN_IAC:
2666                     if (appData.debugMode)
2667                       fprintf(debugFP, "\n<IAC ");
2668                     /* Doubled IAC; pass it through */
2669                     i--;
2670                     break;
2671                   default:
2672                     if (appData.debugMode)
2673                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2674                     /* Drop all other telnet commands on the floor */
2675                     break;
2676                 }
2677                 if (oldi > next_out)
2678                   SendToPlayer(&buf[next_out], oldi - next_out);
2679                 if (++i > next_out)
2680                   next_out = i;
2681                 continue;
2682             }
2683
2684             /* OK, this at least will *usually* work */
2685             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2686                 loggedOn = TRUE;
2687             }
2688
2689             if (loggedOn && !intfSet) {
2690                 if (ics_type == ICS_ICC) {
2691                   snprintf(str, MSG_SIZ,
2692                           "/set-quietly interface %s\n/set-quietly style 12\n",
2693                           programVersion);
2694                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2695                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2696                 } else if (ics_type == ICS_CHESSNET) {
2697                   snprintf(str, MSG_SIZ, "/style 12\n");
2698                 } else {
2699                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2700                   strcat(str, programVersion);
2701                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2702                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2703                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2704 #ifdef WIN32
2705                   strcat(str, "$iset nohighlight 1\n");
2706 #endif
2707                   strcat(str, "$iset lock 1\n$style 12\n");
2708                 }
2709                 SendToICS(str);
2710                 NotifyFrontendLogin();
2711                 intfSet = TRUE;
2712             }
2713
2714             if (started == STARTED_COMMENT) {
2715                 /* Accumulate characters in comment */
2716                 parse[parse_pos++] = buf[i];
2717                 if (buf[i] == '\n') {
2718                     parse[parse_pos] = NULLCHAR;
2719                     if(chattingPartner>=0) {
2720                         char mess[MSG_SIZ];
2721                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2722                         OutputChatMessage(chattingPartner, mess);
2723                         chattingPartner = -1;
2724                         next_out = i+1; // [HGM] suppress printing in ICS window
2725                     } else
2726                     if(!suppressKibitz) // [HGM] kibitz
2727                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2728                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2729                         int nrDigit = 0, nrAlph = 0, j;
2730                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2731                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2732                         parse[parse_pos] = NULLCHAR;
2733                         // try to be smart: if it does not look like search info, it should go to
2734                         // ICS interaction window after all, not to engine-output window.
2735                         for(j=0; j<parse_pos; j++) { // count letters and digits
2736                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2737                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2738                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2739                         }
2740                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2741                             int depth=0; float score;
2742                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2743                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2744                                 pvInfoList[forwardMostMove-1].depth = depth;
2745                                 pvInfoList[forwardMostMove-1].score = 100*score;
2746                             }
2747                             OutputKibitz(suppressKibitz, parse);
2748                         } else {
2749                             char tmp[MSG_SIZ];
2750                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2751                             SendToPlayer(tmp, strlen(tmp));
2752                         }
2753                         next_out = i+1; // [HGM] suppress printing in ICS window
2754                     }
2755                     started = STARTED_NONE;
2756                 } else {
2757                     /* Don't match patterns against characters in comment */
2758                     i++;
2759                     continue;
2760                 }
2761             }
2762             if (started == STARTED_CHATTER) {
2763                 if (buf[i] != '\n') {
2764                     /* Don't match patterns against characters in chatter */
2765                     i++;
2766                     continue;
2767                 }
2768                 started = STARTED_NONE;
2769                 if(suppressKibitz) next_out = i+1;
2770             }
2771
2772             /* Kludge to deal with rcmd protocol */
2773             if (firstTime && looking_at(buf, &i, "\001*")) {
2774                 DisplayFatalError(&buf[1], 0, 1);
2775                 continue;
2776             } else {
2777                 firstTime = FALSE;
2778             }
2779
2780             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2781                 ics_type = ICS_ICC;
2782                 ics_prefix = "/";
2783                 if (appData.debugMode)
2784                   fprintf(debugFP, "ics_type %d\n", ics_type);
2785                 continue;
2786             }
2787             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2788                 ics_type = ICS_FICS;
2789                 ics_prefix = "$";
2790                 if (appData.debugMode)
2791                   fprintf(debugFP, "ics_type %d\n", ics_type);
2792                 continue;
2793             }
2794             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2795                 ics_type = ICS_CHESSNET;
2796                 ics_prefix = "/";
2797                 if (appData.debugMode)
2798                   fprintf(debugFP, "ics_type %d\n", ics_type);
2799                 continue;
2800             }
2801
2802             if (!loggedOn &&
2803                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2804                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2805                  looking_at(buf, &i, "will be \"*\""))) {
2806               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2807               continue;
2808             }
2809
2810             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2811               char buf[MSG_SIZ];
2812               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2813               DisplayIcsInteractionTitle(buf);
2814               have_set_title = TRUE;
2815             }
2816
2817             /* skip finger notes */
2818             if (started == STARTED_NONE &&
2819                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2820                  (buf[i] == '1' && buf[i+1] == '0')) &&
2821                 buf[i+2] == ':' && buf[i+3] == ' ') {
2822               started = STARTED_CHATTER;
2823               i += 3;
2824               continue;
2825             }
2826
2827             oldi = i;
2828             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2829             if(appData.seekGraph) {
2830                 if(soughtPending && MatchSoughtLine(buf+i)) {
2831                     i = strstr(buf+i, "rated") - buf;
2832                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2833                     next_out = leftover_start = i;
2834                     started = STARTED_CHATTER;
2835                     suppressKibitz = TRUE;
2836                     continue;
2837                 }
2838                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2839                         && looking_at(buf, &i, "* ads displayed")) {
2840                     soughtPending = FALSE;
2841                     seekGraphUp = TRUE;
2842                     DrawSeekGraph();
2843                     continue;
2844                 }
2845                 if(appData.autoRefresh) {
2846                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2847                         int s = (ics_type == ICS_ICC); // ICC format differs
2848                         if(seekGraphUp)
2849                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2850                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2851                         looking_at(buf, &i, "*% "); // eat prompt
2852                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2853                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2854                         next_out = i; // suppress
2855                         continue;
2856                     }
2857                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2858                         char *p = star_match[0];
2859                         while(*p) {
2860                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2861                             while(*p && *p++ != ' '); // next
2862                         }
2863                         looking_at(buf, &i, "*% "); // eat prompt
2864                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2865                         next_out = i;
2866                         continue;
2867                     }
2868                 }
2869             }
2870
2871             /* skip formula vars */
2872             if (started == STARTED_NONE &&
2873                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2874               started = STARTED_CHATTER;
2875               i += 3;
2876               continue;
2877             }
2878
2879             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2880             if (appData.autoKibitz && started == STARTED_NONE &&
2881                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2882                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2883                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2884                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2885                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2886                         suppressKibitz = TRUE;
2887                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2888                         next_out = i;
2889                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2890                                 && (gameMode == IcsPlayingWhite)) ||
2891                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2892                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2893                             started = STARTED_CHATTER; // own kibitz we simply discard
2894                         else {
2895                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2896                             parse_pos = 0; parse[0] = NULLCHAR;
2897                             savingComment = TRUE;
2898                             suppressKibitz = gameMode != IcsObserving ? 2 :
2899                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2900                         }
2901                         continue;
2902                 } else
2903                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2904                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2905                          && atoi(star_match[0])) {
2906                     // suppress the acknowledgements of our own autoKibitz
2907                     char *p;
2908                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2909                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2910                     SendToPlayer(star_match[0], strlen(star_match[0]));
2911                     if(looking_at(buf, &i, "*% ")) // eat prompt
2912                         suppressKibitz = FALSE;
2913                     next_out = i;
2914                     continue;
2915                 }
2916             } // [HGM] kibitz: end of patch
2917
2918             // [HGM] chat: intercept tells by users for which we have an open chat window
2919             channel = -1;
2920             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2921                                            looking_at(buf, &i, "* whispers:") ||
2922                                            looking_at(buf, &i, "* kibitzes:") ||
2923                                            looking_at(buf, &i, "* shouts:") ||
2924                                            looking_at(buf, &i, "* c-shouts:") ||
2925                                            looking_at(buf, &i, "--> * ") ||
2926                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2927                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2928                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2929                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2930                 int p;
2931                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2932                 chattingPartner = -1;
2933
2934                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2935                 for(p=0; p<MAX_CHAT; p++) {
2936                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2937                     talker[0] = '['; strcat(talker, "] ");
2938                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2939                     chattingPartner = p; break;
2940                     }
2941                 } else
2942                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2943                 for(p=0; p<MAX_CHAT; p++) {
2944                     if(!strcmp("kibitzes", chatPartner[p])) {
2945                         talker[0] = '['; strcat(talker, "] ");
2946                         chattingPartner = p; break;
2947                     }
2948                 } else
2949                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2950                 for(p=0; p<MAX_CHAT; p++) {
2951                     if(!strcmp("whispers", chatPartner[p])) {
2952                         talker[0] = '['; strcat(talker, "] ");
2953                         chattingPartner = p; break;
2954                     }
2955                 } else
2956                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2957                   if(buf[i-8] == '-' && buf[i-3] == 't')
2958                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2959                     if(!strcmp("c-shouts", chatPartner[p])) {
2960                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2961                         chattingPartner = p; break;
2962                     }
2963                   }
2964                   if(chattingPartner < 0)
2965                   for(p=0; p<MAX_CHAT; p++) {
2966                     if(!strcmp("shouts", chatPartner[p])) {
2967                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2968                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2969                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2970                         chattingPartner = p; break;
2971                     }
2972                   }
2973                 }
2974                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2975                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2976                     talker[0] = 0; Colorize(ColorTell, FALSE);
2977                     chattingPartner = p; break;
2978                 }
2979                 if(chattingPartner<0) i = oldi; else {
2980                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2981                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2982                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2983                     started = STARTED_COMMENT;
2984                     parse_pos = 0; parse[0] = NULLCHAR;
2985                     savingComment = 3 + chattingPartner; // counts as TRUE
2986                     suppressKibitz = TRUE;
2987                     continue;
2988                 }
2989             } // [HGM] chat: end of patch
2990
2991           backup = i;
2992             if (appData.zippyTalk || appData.zippyPlay) {
2993                 /* [DM] Backup address for color zippy lines */
2994 #if ZIPPY
2995                if (loggedOn == TRUE)
2996                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2997                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2998 #endif
2999             } // [DM] 'else { ' deleted
3000                 if (
3001                     /* Regular tells and says */
3002                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3003                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3004                     looking_at(buf, &i, "* says: ") ||
3005                     /* Don't color "message" or "messages" output */
3006                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3007                     looking_at(buf, &i, "*. * at *:*: ") ||
3008                     looking_at(buf, &i, "--* (*:*): ") ||
3009                     /* Message notifications (same color as tells) */
3010                     looking_at(buf, &i, "* has left a message ") ||
3011                     looking_at(buf, &i, "* just sent you a message:\n") ||
3012                     /* Whispers and kibitzes */
3013                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3014                     looking_at(buf, &i, "* kibitzes: ") ||
3015                     /* Channel tells */
3016                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3017
3018                   if (tkind == 1 && strchr(star_match[0], ':')) {
3019                       /* Avoid "tells you:" spoofs in channels */
3020                      tkind = 3;
3021                   }
3022                   if (star_match[0][0] == NULLCHAR ||
3023                       strchr(star_match[0], ' ') ||
3024                       (tkind == 3 && strchr(star_match[1], ' '))) {
3025                     /* Reject bogus matches */
3026                     i = oldi;
3027                   } else {
3028                     if (appData.colorize) {
3029                       if (oldi > next_out) {
3030                         SendToPlayer(&buf[next_out], oldi - next_out);
3031                         next_out = oldi;
3032                       }
3033                       switch (tkind) {
3034                       case 1:
3035                         Colorize(ColorTell, FALSE);
3036                         curColor = ColorTell;
3037                         break;
3038                       case 2:
3039                         Colorize(ColorKibitz, FALSE);
3040                         curColor = ColorKibitz;
3041                         break;
3042                       case 3:
3043                         p = strrchr(star_match[1], '(');
3044                         if (p == NULL) {
3045                           p = star_match[1];
3046                         } else {
3047                           p++;
3048                         }
3049                         if (atoi(p) == 1) {
3050                           Colorize(ColorChannel1, FALSE);
3051                           curColor = ColorChannel1;
3052                         } else {
3053                           Colorize(ColorChannel, FALSE);
3054                           curColor = ColorChannel;
3055                         }
3056                         break;
3057                       case 5:
3058                         curColor = ColorNormal;
3059                         break;
3060                       }
3061                     }
3062                     if (started == STARTED_NONE && appData.autoComment &&
3063                         (gameMode == IcsObserving ||
3064                          gameMode == IcsPlayingWhite ||
3065                          gameMode == IcsPlayingBlack)) {
3066                       parse_pos = i - oldi;
3067                       memcpy(parse, &buf[oldi], parse_pos);
3068                       parse[parse_pos] = NULLCHAR;
3069                       started = STARTED_COMMENT;
3070                       savingComment = TRUE;
3071                     } else {
3072                       started = STARTED_CHATTER;
3073                       savingComment = FALSE;
3074                     }
3075                     loggedOn = TRUE;
3076                     continue;
3077                   }
3078                 }
3079
3080                 if (looking_at(buf, &i, "* s-shouts: ") ||
3081                     looking_at(buf, &i, "* c-shouts: ")) {
3082                     if (appData.colorize) {
3083                         if (oldi > next_out) {
3084                             SendToPlayer(&buf[next_out], oldi - next_out);
3085                             next_out = oldi;
3086                         }
3087                         Colorize(ColorSShout, FALSE);
3088                         curColor = ColorSShout;
3089                     }
3090                     loggedOn = TRUE;
3091                     started = STARTED_CHATTER;
3092                     continue;
3093                 }
3094
3095                 if (looking_at(buf, &i, "--->")) {
3096                     loggedOn = TRUE;
3097                     continue;
3098                 }
3099
3100                 if (looking_at(buf, &i, "* shouts: ") ||
3101                     looking_at(buf, &i, "--> ")) {
3102                     if (appData.colorize) {
3103                         if (oldi > next_out) {
3104                             SendToPlayer(&buf[next_out], oldi - next_out);
3105                             next_out = oldi;
3106                         }
3107                         Colorize(ColorShout, FALSE);
3108                         curColor = ColorShout;
3109                     }
3110                     loggedOn = TRUE;
3111                     started = STARTED_CHATTER;
3112                     continue;
3113                 }
3114
3115                 if (looking_at( buf, &i, "Challenge:")) {
3116                     if (appData.colorize) {
3117                         if (oldi > next_out) {
3118                             SendToPlayer(&buf[next_out], oldi - next_out);
3119                             next_out = oldi;
3120                         }
3121                         Colorize(ColorChallenge, FALSE);
3122                         curColor = ColorChallenge;
3123                     }
3124                     loggedOn = TRUE;
3125                     continue;
3126                 }
3127
3128                 if (looking_at(buf, &i, "* offers you") ||
3129                     looking_at(buf, &i, "* offers to be") ||
3130                     looking_at(buf, &i, "* would like to") ||
3131                     looking_at(buf, &i, "* requests to") ||
3132                     looking_at(buf, &i, "Your opponent offers") ||
3133                     looking_at(buf, &i, "Your opponent requests")) {
3134
3135                     if (appData.colorize) {
3136                         if (oldi > next_out) {
3137                             SendToPlayer(&buf[next_out], oldi - next_out);
3138                             next_out = oldi;
3139                         }
3140                         Colorize(ColorRequest, FALSE);
3141                         curColor = ColorRequest;
3142                     }
3143                     continue;
3144                 }
3145
3146                 if (looking_at(buf, &i, "* (*) seeking")) {
3147                     if (appData.colorize) {
3148                         if (oldi > next_out) {
3149                             SendToPlayer(&buf[next_out], oldi - next_out);
3150                             next_out = oldi;
3151                         }
3152                         Colorize(ColorSeek, FALSE);
3153                         curColor = ColorSeek;
3154                     }
3155                     continue;
3156             }
3157
3158           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3159
3160             if (looking_at(buf, &i, "\\   ")) {
3161                 if (prevColor != ColorNormal) {
3162                     if (oldi > next_out) {
3163                         SendToPlayer(&buf[next_out], oldi - next_out);
3164                         next_out = oldi;
3165                     }
3166                     Colorize(prevColor, TRUE);
3167                     curColor = prevColor;
3168                 }
3169                 if (savingComment) {
3170                     parse_pos = i - oldi;
3171                     memcpy(parse, &buf[oldi], parse_pos);
3172                     parse[parse_pos] = NULLCHAR;
3173                     started = STARTED_COMMENT;
3174                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3175                         chattingPartner = savingComment - 3; // kludge to remember the box
3176                 } else {
3177                     started = STARTED_CHATTER;
3178                 }
3179                 continue;
3180             }
3181
3182             if (looking_at(buf, &i, "Black Strength :") ||
3183                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3184                 looking_at(buf, &i, "<10>") ||
3185                 looking_at(buf, &i, "#@#")) {
3186                 /* Wrong board style */
3187                 loggedOn = TRUE;
3188                 SendToICS(ics_prefix);
3189                 SendToICS("set style 12\n");
3190                 SendToICS(ics_prefix);
3191                 SendToICS("refresh\n");
3192                 continue;
3193             }
3194
3195             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3196                 ICSInitScript();
3197                 have_sent_ICS_logon = 1;
3198                 continue;
3199             }
3200
3201             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3202                 (looking_at(buf, &i, "\n<12> ") ||
3203                  looking_at(buf, &i, "<12> "))) {
3204                 loggedOn = TRUE;
3205                 if (oldi > next_out) {
3206                     SendToPlayer(&buf[next_out], oldi - next_out);
3207                 }
3208                 next_out = i;
3209                 started = STARTED_BOARD;
3210                 parse_pos = 0;
3211                 continue;
3212             }
3213
3214             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3215                 looking_at(buf, &i, "<b1> ")) {
3216                 if (oldi > next_out) {
3217                     SendToPlayer(&buf[next_out], oldi - next_out);
3218                 }
3219                 next_out = i;
3220                 started = STARTED_HOLDINGS;
3221                 parse_pos = 0;
3222                 continue;
3223             }
3224
3225             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3226                 loggedOn = TRUE;
3227                 /* Header for a move list -- first line */
3228
3229                 switch (ics_getting_history) {
3230                   case H_FALSE:
3231                     switch (gameMode) {
3232                       case IcsIdle:
3233                       case BeginningOfGame:
3234                         /* User typed "moves" or "oldmoves" while we
3235                            were idle.  Pretend we asked for these
3236                            moves and soak them up so user can step
3237                            through them and/or save them.
3238                            */
3239                         Reset(FALSE, TRUE);
3240                         gameMode = IcsObserving;
3241                         ModeHighlight();
3242                         ics_gamenum = -1;
3243                         ics_getting_history = H_GOT_UNREQ_HEADER;
3244                         break;
3245                       case EditGame: /*?*/
3246                       case EditPosition: /*?*/
3247                         /* Should above feature work in these modes too? */
3248                         /* For now it doesn't */
3249                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3250                         break;
3251                       default:
3252                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3253                         break;
3254                     }
3255                     break;
3256                   case H_REQUESTED:
3257                     /* Is this the right one? */
3258                     if (gameInfo.white && gameInfo.black &&
3259                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3260                         strcmp(gameInfo.black, star_match[2]) == 0) {
3261                         /* All is well */
3262                         ics_getting_history = H_GOT_REQ_HEADER;
3263                     }
3264                     break;
3265                   case H_GOT_REQ_HEADER:
3266                   case H_GOT_UNREQ_HEADER:
3267                   case H_GOT_UNWANTED_HEADER:
3268                   case H_GETTING_MOVES:
3269                     /* Should not happen */
3270                     DisplayError(_("Error gathering move list: two headers"), 0);
3271                     ics_getting_history = H_FALSE;
3272                     break;
3273                 }
3274
3275                 /* Save player ratings into gameInfo if needed */
3276                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3277                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3278                     (gameInfo.whiteRating == -1 ||
3279                      gameInfo.blackRating == -1)) {
3280
3281                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3282                     gameInfo.blackRating = string_to_rating(star_match[3]);
3283                     if (appData.debugMode)
3284                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3285                               gameInfo.whiteRating, gameInfo.blackRating);
3286                 }
3287                 continue;
3288             }
3289
3290             if (looking_at(buf, &i,
3291               "* * match, initial time: * minute*, increment: * second")) {
3292                 /* Header for a move list -- second line */
3293                 /* Initial board will follow if this is a wild game */
3294                 if (gameInfo.event != NULL) free(gameInfo.event);
3295                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3296                 gameInfo.event = StrSave(str);
3297                 /* [HGM] we switched variant. Translate boards if needed. */
3298                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3299                 continue;
3300             }
3301
3302             if (looking_at(buf, &i, "Move  ")) {
3303                 /* Beginning of a move list */
3304                 switch (ics_getting_history) {
3305                   case H_FALSE:
3306                     /* Normally should not happen */
3307                     /* Maybe user hit reset while we were parsing */
3308                     break;
3309                   case H_REQUESTED:
3310                     /* Happens if we are ignoring a move list that is not
3311                      * the one we just requested.  Common if the user
3312                      * tries to observe two games without turning off
3313                      * getMoveList */
3314                     break;
3315                   case H_GETTING_MOVES:
3316                     /* Should not happen */
3317                     DisplayError(_("Error gathering move list: nested"), 0);
3318                     ics_getting_history = H_FALSE;
3319                     break;
3320                   case H_GOT_REQ_HEADER:
3321                     ics_getting_history = H_GETTING_MOVES;
3322                     started = STARTED_MOVES;
3323                     parse_pos = 0;
3324                     if (oldi > next_out) {
3325                         SendToPlayer(&buf[next_out], oldi - next_out);
3326                     }
3327                     break;
3328                   case H_GOT_UNREQ_HEADER:
3329                     ics_getting_history = H_GETTING_MOVES;
3330                     started = STARTED_MOVES_NOHIDE;
3331                     parse_pos = 0;
3332                     break;
3333                   case H_GOT_UNWANTED_HEADER:
3334                     ics_getting_history = H_FALSE;
3335                     break;
3336                 }
3337                 continue;
3338             }
3339
3340             if (looking_at(buf, &i, "% ") ||
3341                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3342                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3343                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3344                     soughtPending = FALSE;
3345                     seekGraphUp = TRUE;
3346                     DrawSeekGraph();
3347                 }
3348                 if(suppressKibitz) next_out = i;
3349                 savingComment = FALSE;
3350                 suppressKibitz = 0;
3351                 switch (started) {
3352                   case STARTED_MOVES:
3353                   case STARTED_MOVES_NOHIDE:
3354                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3355                     parse[parse_pos + i - oldi] = NULLCHAR;
3356                     ParseGameHistory(parse);
3357 #if ZIPPY
3358                     if (appData.zippyPlay && first.initDone) {
3359                         FeedMovesToProgram(&first, forwardMostMove);
3360                         if (gameMode == IcsPlayingWhite) {
3361                             if (WhiteOnMove(forwardMostMove)) {
3362                                 if (first.sendTime) {
3363                                   if (first.useColors) {
3364                                     SendToProgram("black\n", &first);
3365                                   }
3366                                   SendTimeRemaining(&first, TRUE);
3367                                 }
3368                                 if (first.useColors) {
3369                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3370                                 }
3371                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3372                                 first.maybeThinking = TRUE;
3373                             } else {
3374                                 if (first.usePlayother) {
3375                                   if (first.sendTime) {
3376                                     SendTimeRemaining(&first, TRUE);
3377                                   }
3378                                   SendToProgram("playother\n", &first);
3379                                   firstMove = FALSE;
3380                                 } else {
3381                                   firstMove = TRUE;
3382                                 }
3383                             }
3384                         } else if (gameMode == IcsPlayingBlack) {
3385                             if (!WhiteOnMove(forwardMostMove)) {
3386                                 if (first.sendTime) {
3387                                   if (first.useColors) {
3388                                     SendToProgram("white\n", &first);
3389                                   }
3390                                   SendTimeRemaining(&first, FALSE);
3391                                 }
3392                                 if (first.useColors) {
3393                                   SendToProgram("black\n", &first);
3394                                 }
3395                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3396                                 first.maybeThinking = TRUE;
3397                             } else {
3398                                 if (first.usePlayother) {
3399                                   if (first.sendTime) {
3400                                     SendTimeRemaining(&first, FALSE);
3401                                   }
3402                                   SendToProgram("playother\n", &first);
3403                                   firstMove = FALSE;
3404                                 } else {
3405                                   firstMove = TRUE;
3406                                 }
3407                             }
3408                         }
3409                     }
3410 #endif
3411                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3412                         /* Moves came from oldmoves or moves command
3413                            while we weren't doing anything else.
3414                            */
3415                         currentMove = forwardMostMove;
3416                         ClearHighlights();/*!!could figure this out*/
3417                         flipView = appData.flipView;
3418                         DrawPosition(TRUE, boards[currentMove]);
3419                         DisplayBothClocks();
3420                         snprintf(str, MSG_SIZ, "%s vs. %s",
3421                                 gameInfo.white, gameInfo.black);
3422                         DisplayTitle(str);
3423                         gameMode = IcsIdle;
3424                     } else {
3425                         /* Moves were history of an active game */
3426                         if (gameInfo.resultDetails != NULL) {
3427                             free(gameInfo.resultDetails);
3428                             gameInfo.resultDetails = NULL;
3429                         }
3430                     }
3431                     HistorySet(parseList, backwardMostMove,
3432                                forwardMostMove, currentMove-1);
3433                     DisplayMove(currentMove - 1);
3434                     if (started == STARTED_MOVES) next_out = i;
3435                     started = STARTED_NONE;
3436                     ics_getting_history = H_FALSE;
3437                     break;
3438
3439                   case STARTED_OBSERVE:
3440                     started = STARTED_NONE;
3441                     SendToICS(ics_prefix);
3442                     SendToICS("refresh\n");
3443                     break;
3444
3445                   default:
3446                     break;
3447                 }
3448                 if(bookHit) { // [HGM] book: simulate book reply
3449                     static char bookMove[MSG_SIZ]; // a bit generous?
3450
3451                     programStats.nodes = programStats.depth = programStats.time =
3452                     programStats.score = programStats.got_only_move = 0;
3453                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3454
3455                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3456                     strcat(bookMove, bookHit);
3457                     HandleMachineMove(bookMove, &first);
3458                 }
3459                 continue;
3460             }
3461
3462             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3463                  started == STARTED_HOLDINGS ||
3464                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3465                 /* Accumulate characters in move list or board */
3466                 parse[parse_pos++] = buf[i];
3467             }
3468
3469             /* Start of game messages.  Mostly we detect start of game
3470                when the first board image arrives.  On some versions
3471                of the ICS, though, we need to do a "refresh" after starting
3472                to observe in order to get the current board right away. */
3473             if (looking_at(buf, &i, "Adding game * to observation list")) {
3474                 started = STARTED_OBSERVE;
3475                 continue;
3476             }
3477
3478             /* Handle auto-observe */
3479             if (appData.autoObserve &&
3480                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3481                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3482                 char *player;
3483                 /* Choose the player that was highlighted, if any. */
3484                 if (star_match[0][0] == '\033' ||
3485                     star_match[1][0] != '\033') {
3486                     player = star_match[0];
3487                 } else {
3488                     player = star_match[2];
3489                 }
3490                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3491                         ics_prefix, StripHighlightAndTitle(player));
3492                 SendToICS(str);
3493
3494                 /* Save ratings from notify string */
3495                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3496                 player1Rating = string_to_rating(star_match[1]);
3497                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3498                 player2Rating = string_to_rating(star_match[3]);
3499
3500                 if (appData.debugMode)
3501                   fprintf(debugFP,
3502                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3503                           player1Name, player1Rating,
3504                           player2Name, player2Rating);
3505
3506                 continue;
3507             }
3508
3509             /* Deal with automatic examine mode after a game,
3510                and with IcsObserving -> IcsExamining transition */
3511             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3512                 looking_at(buf, &i, "has made you an examiner of game *")) {
3513
3514                 int gamenum = atoi(star_match[0]);
3515                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3516                     gamenum == ics_gamenum) {
3517                     /* We were already playing or observing this game;
3518                        no need to refetch history */
3519                     gameMode = IcsExamining;
3520                     if (pausing) {
3521                         pauseExamForwardMostMove = forwardMostMove;
3522                     } else if (currentMove < forwardMostMove) {
3523                         ForwardInner(forwardMostMove);
3524                     }
3525                 } else {
3526                     /* I don't think this case really can happen */
3527                     SendToICS(ics_prefix);
3528                     SendToICS("refresh\n");
3529                 }
3530                 continue;
3531             }
3532
3533             /* Error messages */
3534 //          if (ics_user_moved) {
3535             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3536                 if (looking_at(buf, &i, "Illegal move") ||
3537                     looking_at(buf, &i, "Not a legal move") ||
3538                     looking_at(buf, &i, "Your king is in check") ||
3539                     looking_at(buf, &i, "It isn't your turn") ||
3540                     looking_at(buf, &i, "It is not your move")) {
3541                     /* Illegal move */
3542                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3543                         currentMove = forwardMostMove-1;
3544                         DisplayMove(currentMove - 1); /* before DMError */
3545                         DrawPosition(FALSE, boards[currentMove]);
3546                         SwitchClocks(forwardMostMove-1); // [HGM] race
3547                         DisplayBothClocks();
3548                     }
3549                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3550                     ics_user_moved = 0;
3551                     continue;
3552                 }
3553             }
3554
3555             if (looking_at(buf, &i, "still have time") ||
3556                 looking_at(buf, &i, "not out of time") ||
3557                 looking_at(buf, &i, "either player is out of time") ||
3558                 looking_at(buf, &i, "has timeseal; checking")) {
3559                 /* We must have called his flag a little too soon */
3560                 whiteFlag = blackFlag = FALSE;
3561                 continue;
3562             }
3563
3564             if (looking_at(buf, &i, "added * seconds to") ||
3565                 looking_at(buf, &i, "seconds were added to")) {
3566                 /* Update the clocks */
3567                 SendToICS(ics_prefix);
3568                 SendToICS("refresh\n");
3569                 continue;
3570             }
3571
3572             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3573                 ics_clock_paused = TRUE;
3574                 StopClocks();
3575                 continue;
3576             }
3577
3578             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3579                 ics_clock_paused = FALSE;
3580                 StartClocks();
3581                 continue;
3582             }
3583
3584             /* Grab player ratings from the Creating: message.
3585                Note we have to check for the special case when
3586                the ICS inserts things like [white] or [black]. */
3587             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3588                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3589                 /* star_matches:
3590                    0    player 1 name (not necessarily white)
3591                    1    player 1 rating
3592                    2    empty, white, or black (IGNORED)
3593                    3    player 2 name (not necessarily black)
3594                    4    player 2 rating
3595
3596                    The names/ratings are sorted out when the game
3597                    actually starts (below).
3598                 */
3599                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3600                 player1Rating = string_to_rating(star_match[1]);
3601                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3602                 player2Rating = string_to_rating(star_match[4]);
3603
3604                 if (appData.debugMode)
3605                   fprintf(debugFP,
3606                           "Ratings from 'Creating:' %s %d, %s %d\n",
3607                           player1Name, player1Rating,
3608                           player2Name, player2Rating);
3609
3610                 continue;
3611             }
3612
3613             /* Improved generic start/end-of-game messages */
3614             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3615                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3616                 /* If tkind == 0: */
3617                 /* star_match[0] is the game number */
3618                 /*           [1] is the white player's name */
3619                 /*           [2] is the black player's name */
3620                 /* For end-of-game: */
3621                 /*           [3] is the reason for the game end */
3622                 /*           [4] is a PGN end game-token, preceded by " " */
3623                 /* For start-of-game: */
3624                 /*           [3] begins with "Creating" or "Continuing" */
3625                 /*           [4] is " *" or empty (don't care). */
3626                 int gamenum = atoi(star_match[0]);
3627                 char *whitename, *blackname, *why, *endtoken;
3628                 ChessMove endtype = EndOfFile;
3629
3630                 if (tkind == 0) {
3631                   whitename = star_match[1];
3632                   blackname = star_match[2];
3633                   why = star_match[3];
3634                   endtoken = star_match[4];
3635                 } else {
3636                   whitename = star_match[1];
3637                   blackname = star_match[3];
3638                   why = star_match[5];
3639                   endtoken = star_match[6];
3640                 }
3641
3642                 /* Game start messages */
3643                 if (strncmp(why, "Creating ", 9) == 0 ||
3644                     strncmp(why, "Continuing ", 11) == 0) {
3645                     gs_gamenum = gamenum;
3646                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3647                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3648 #if ZIPPY
3649                     if (appData.zippyPlay) {
3650                         ZippyGameStart(whitename, blackname);
3651                     }
3652 #endif /*ZIPPY*/
3653                     partnerBoardValid = FALSE; // [HGM] bughouse
3654                     continue;
3655                 }
3656
3657                 /* Game end messages */
3658                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3659                     ics_gamenum != gamenum) {
3660                     continue;
3661                 }
3662                 while (endtoken[0] == ' ') endtoken++;
3663                 switch (endtoken[0]) {
3664                   case '*':
3665                   default:
3666                     endtype = GameUnfinished;
3667                     break;
3668                   case '0':
3669                     endtype = BlackWins;
3670                     break;
3671                   case '1':
3672                     if (endtoken[1] == '/')
3673                       endtype = GameIsDrawn;
3674                     else
3675                       endtype = WhiteWins;
3676                     break;
3677                 }
3678                 GameEnds(endtype, why, GE_ICS);
3679 #if ZIPPY
3680                 if (appData.zippyPlay && first.initDone) {
3681                     ZippyGameEnd(endtype, why);
3682                     if (first.pr == NULL) {
3683                       /* Start the next process early so that we'll
3684                          be ready for the next challenge */
3685                       StartChessProgram(&first);
3686                     }
3687                     /* Send "new" early, in case this command takes
3688                        a long time to finish, so that we'll be ready
3689                        for the next challenge. */
3690                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3691                     Reset(TRUE, TRUE);
3692                 }
3693 #endif /*ZIPPY*/
3694                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3695                 continue;
3696             }
3697
3698             if (looking_at(buf, &i, "Removing game * from observation") ||
3699                 looking_at(buf, &i, "no longer observing game *") ||
3700                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3701                 if (gameMode == IcsObserving &&
3702                     atoi(star_match[0]) == ics_gamenum)
3703                   {
3704                       /* icsEngineAnalyze */
3705                       if (appData.icsEngineAnalyze) {
3706                             ExitAnalyzeMode();
3707                             ModeHighlight();
3708                       }
3709                       StopClocks();
3710                       gameMode = IcsIdle;
3711                       ics_gamenum = -1;
3712                       ics_user_moved = FALSE;
3713                   }
3714                 continue;
3715             }
3716
3717             if (looking_at(buf, &i, "no longer examining game *")) {
3718                 if (gameMode == IcsExamining &&
3719                     atoi(star_match[0]) == ics_gamenum)
3720                   {
3721                       gameMode = IcsIdle;
3722                       ics_gamenum = -1;
3723                       ics_user_moved = FALSE;
3724                   }
3725                 continue;
3726             }
3727
3728             /* Advance leftover_start past any newlines we find,
3729                so only partial lines can get reparsed */
3730             if (looking_at(buf, &i, "\n")) {
3731                 prevColor = curColor;
3732                 if (curColor != ColorNormal) {
3733                     if (oldi > next_out) {
3734                         SendToPlayer(&buf[next_out], oldi - next_out);
3735                         next_out = oldi;
3736                     }
3737                     Colorize(ColorNormal, FALSE);
3738                     curColor = ColorNormal;
3739                 }
3740                 if (started == STARTED_BOARD) {
3741                     started = STARTED_NONE;
3742                     parse[parse_pos] = NULLCHAR;
3743                     ParseBoard12(parse);
3744                     ics_user_moved = 0;
3745
3746                     /* Send premove here */
3747                     if (appData.premove) {
3748                       char str[MSG_SIZ];
3749                       if (currentMove == 0 &&
3750                           gameMode == IcsPlayingWhite &&
3751                           appData.premoveWhite) {
3752                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3753                         if (appData.debugMode)
3754                           fprintf(debugFP, "Sending premove:\n");
3755                         SendToICS(str);
3756                       } else if (currentMove == 1 &&
3757                                  gameMode == IcsPlayingBlack &&
3758                                  appData.premoveBlack) {
3759                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3760                         if (appData.debugMode)
3761                           fprintf(debugFP, "Sending premove:\n");
3762                         SendToICS(str);
3763                       } else if (gotPremove) {
3764                         gotPremove = 0;
3765                         ClearPremoveHighlights();
3766                         if (appData.debugMode)
3767                           fprintf(debugFP, "Sending premove:\n");
3768                           UserMoveEvent(premoveFromX, premoveFromY,
3769                                         premoveToX, premoveToY,
3770                                         premovePromoChar);
3771                       }
3772                     }
3773
3774                     /* Usually suppress following prompt */
3775                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3776                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3777                         if (looking_at(buf, &i, "*% ")) {
3778                             savingComment = FALSE;
3779                             suppressKibitz = 0;
3780                         }
3781                     }
3782                     next_out = i;
3783                 } else if (started == STARTED_HOLDINGS) {
3784                     int gamenum;
3785                     char new_piece[MSG_SIZ];
3786                     started = STARTED_NONE;
3787                     parse[parse_pos] = NULLCHAR;
3788                     if (appData.debugMode)
3789                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3790                                                         parse, currentMove);
3791                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3792                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3793                         if (gameInfo.variant == VariantNormal) {
3794                           /* [HGM] We seem to switch variant during a game!
3795                            * Presumably no holdings were displayed, so we have
3796                            * to move the position two files to the right to
3797                            * create room for them!
3798                            */
3799                           VariantClass newVariant;
3800                           switch(gameInfo.boardWidth) { // base guess on board width
3801                                 case 9:  newVariant = VariantShogi; break;
3802                                 case 10: newVariant = VariantGreat; break;
3803                                 default: newVariant = VariantCrazyhouse; break;
3804                           }
3805                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3806                           /* Get a move list just to see the header, which
3807                              will tell us whether this is really bug or zh */
3808                           if (ics_getting_history == H_FALSE) {
3809                             ics_getting_history = H_REQUESTED;
3810                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3811                             SendToICS(str);
3812                           }
3813                         }
3814                         new_piece[0] = NULLCHAR;
3815                         sscanf(parse, "game %d white [%s black [%s <- %s",
3816                                &gamenum, white_holding, black_holding,
3817                                new_piece);
3818                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3819                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3820                         /* [HGM] copy holdings to board holdings area */
3821                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3822                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3823                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3824 #if ZIPPY
3825                         if (appData.zippyPlay && first.initDone) {
3826                             ZippyHoldings(white_holding, black_holding,
3827                                           new_piece);
3828                         }
3829 #endif /*ZIPPY*/
3830                         if (tinyLayout || smallLayout) {
3831                             char wh[16], bh[16];
3832                             PackHolding(wh, white_holding);
3833                             PackHolding(bh, black_holding);
3834                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3835                                     gameInfo.white, gameInfo.black);
3836                         } else {
3837                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3838                                     gameInfo.white, white_holding,
3839                                     gameInfo.black, black_holding);
3840                         }
3841                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3842                         DrawPosition(FALSE, boards[currentMove]);
3843                         DisplayTitle(str);
3844                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3845                         sscanf(parse, "game %d white [%s black [%s <- %s",
3846                                &gamenum, white_holding, black_holding,
3847                                new_piece);
3848                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3849                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3850                         /* [HGM] copy holdings to partner-board holdings area */
3851                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3852                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3853                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3854                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3855                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3856                       }
3857                     }
3858                     /* Suppress following prompt */
3859                     if (looking_at(buf, &i, "*% ")) {
3860                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3861                         savingComment = FALSE;
3862                         suppressKibitz = 0;
3863                     }
3864                     next_out = i;
3865                 }
3866                 continue;
3867             }
3868
3869             i++;                /* skip unparsed character and loop back */
3870         }
3871
3872         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3873 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3874 //          SendToPlayer(&buf[next_out], i - next_out);
3875             started != STARTED_HOLDINGS && leftover_start > next_out) {
3876             SendToPlayer(&buf[next_out], leftover_start - next_out);
3877             next_out = i;
3878         }
3879
3880         leftover_len = buf_len - leftover_start;
3881         /* if buffer ends with something we couldn't parse,
3882            reparse it after appending the next read */
3883
3884     } else if (count == 0) {
3885         RemoveInputSource(isr);
3886         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3887     } else {
3888         DisplayFatalError(_("Error reading from ICS"), error, 1);
3889     }
3890 }
3891
3892
3893 /* Board style 12 looks like this:
3894
3895    <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
3896
3897  * The "<12> " is stripped before it gets to this routine.  The two
3898  * trailing 0's (flip state and clock ticking) are later addition, and
3899  * some chess servers may not have them, or may have only the first.
3900  * Additional trailing fields may be added in the future.
3901  */
3902
3903 #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"
3904
3905 #define RELATION_OBSERVING_PLAYED    0
3906 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3907 #define RELATION_PLAYING_MYMOVE      1
3908 #define RELATION_PLAYING_NOTMYMOVE  -1
3909 #define RELATION_EXAMINING           2
3910 #define RELATION_ISOLATED_BOARD     -3
3911 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3912
3913 void
3914 ParseBoard12(string)
3915      char *string;
3916 {
3917     GameMode newGameMode;
3918     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3919     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3920     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3921     char to_play, board_chars[200];
3922     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3923     char black[32], white[32];
3924     Board board;
3925     int prevMove = currentMove;
3926     int ticking = 2;
3927     ChessMove moveType;
3928     int fromX, fromY, toX, toY;
3929     char promoChar;
3930     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3931     char *bookHit = NULL; // [HGM] book
3932     Boolean weird = FALSE, reqFlag = FALSE;
3933
3934     fromX = fromY = toX = toY = -1;
3935
3936     newGame = FALSE;
3937
3938     if (appData.debugMode)
3939       fprintf(debugFP, _("Parsing board: %s\n"), string);
3940
3941     move_str[0] = NULLCHAR;
3942     elapsed_time[0] = NULLCHAR;
3943     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3944         int  i = 0, j;
3945         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3946             if(string[i] == ' ') { ranks++; files = 0; }
3947             else files++;
3948             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3949             i++;
3950         }
3951         for(j = 0; j <i; j++) board_chars[j] = string[j];
3952         board_chars[i] = '\0';
3953         string += i + 1;
3954     }
3955     n = sscanf(string, PATTERN, &to_play, &double_push,
3956                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3957                &gamenum, white, black, &relation, &basetime, &increment,
3958                &white_stren, &black_stren, &white_time, &black_time,
3959                &moveNum, str, elapsed_time, move_str, &ics_flip,
3960                &ticking);
3961
3962     if (n < 21) {
3963         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3964         DisplayError(str, 0);
3965         return;
3966     }
3967
3968     /* Convert the move number to internal form */
3969     moveNum = (moveNum - 1) * 2;
3970     if (to_play == 'B') moveNum++;
3971     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3972       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3973                         0, 1);
3974       return;
3975     }
3976
3977     switch (relation) {
3978       case RELATION_OBSERVING_PLAYED:
3979       case RELATION_OBSERVING_STATIC:
3980         if (gamenum == -1) {
3981             /* Old ICC buglet */
3982             relation = RELATION_OBSERVING_STATIC;
3983         }
3984         newGameMode = IcsObserving;
3985         break;
3986       case RELATION_PLAYING_MYMOVE:
3987       case RELATION_PLAYING_NOTMYMOVE:
3988         newGameMode =
3989           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3990             IcsPlayingWhite : IcsPlayingBlack;
3991         break;
3992       case RELATION_EXAMINING:
3993         newGameMode = IcsExamining;
3994         break;
3995       case RELATION_ISOLATED_BOARD:
3996       default:
3997         /* Just display this board.  If user was doing something else,
3998            we will forget about it until the next board comes. */
3999         newGameMode = IcsIdle;
4000         break;
4001       case RELATION_STARTING_POSITION:
4002         newGameMode = gameMode;
4003         break;
4004     }
4005
4006     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4007          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4008       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4009       char *toSqr;
4010       for (k = 0; k < ranks; k++) {
4011         for (j = 0; j < files; j++)
4012           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4013         if(gameInfo.holdingsWidth > 1) {
4014              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4015              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4016         }
4017       }
4018       CopyBoard(partnerBoard, board);
4019       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4020         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4021         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4022       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4023       if(toSqr = strchr(str, '-')) {
4024         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4025         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4026       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4027       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4028       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4029       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4030       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4031       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4032                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4033       DisplayMessage(partnerStatus, "");
4034         partnerBoardValid = TRUE;
4035       return;
4036     }
4037
4038     /* Modify behavior for initial board display on move listing
4039        of wild games.
4040        */
4041     switch (ics_getting_history) {
4042       case H_FALSE:
4043       case H_REQUESTED:
4044         break;
4045       case H_GOT_REQ_HEADER:
4046       case H_GOT_UNREQ_HEADER:
4047         /* This is the initial position of the current game */
4048         gamenum = ics_gamenum;
4049         moveNum = 0;            /* old ICS bug workaround */
4050         if (to_play == 'B') {
4051           startedFromSetupPosition = TRUE;
4052           blackPlaysFirst = TRUE;
4053           moveNum = 1;
4054           if (forwardMostMove == 0) forwardMostMove = 1;
4055           if (backwardMostMove == 0) backwardMostMove = 1;
4056           if (currentMove == 0) currentMove = 1;
4057         }
4058         newGameMode = gameMode;
4059         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4060         break;
4061       case H_GOT_UNWANTED_HEADER:
4062         /* This is an initial board that we don't want */
4063         return;
4064       case H_GETTING_MOVES:
4065         /* Should not happen */
4066         DisplayError(_("Error gathering move list: extra board"), 0);
4067         ics_getting_history = H_FALSE;
4068         return;
4069     }
4070
4071    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4072                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4073      /* [HGM] We seem to have switched variant unexpectedly
4074       * Try to guess new variant from board size
4075       */
4076           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4077           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4078           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4079           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4080           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4081           if(!weird) newVariant = VariantNormal;
4082           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4083           /* Get a move list just to see the header, which
4084              will tell us whether this is really bug or zh */
4085           if (ics_getting_history == H_FALSE) {
4086             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4087             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4088             SendToICS(str);
4089           }
4090     }
4091
4092     /* Take action if this is the first board of a new game, or of a
4093        different game than is currently being displayed.  */
4094     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4095         relation == RELATION_ISOLATED_BOARD) {
4096
4097         /* Forget the old game and get the history (if any) of the new one */
4098         if (gameMode != BeginningOfGame) {
4099           Reset(TRUE, TRUE);
4100         }
4101         newGame = TRUE;
4102         if (appData.autoRaiseBoard) BoardToTop();
4103         prevMove = -3;
4104         if (gamenum == -1) {
4105             newGameMode = IcsIdle;
4106         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4107                    appData.getMoveList && !reqFlag) {
4108             /* Need to get game history */
4109             ics_getting_history = H_REQUESTED;
4110             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4111             SendToICS(str);
4112         }
4113
4114         /* Initially flip the board to have black on the bottom if playing
4115            black or if the ICS flip flag is set, but let the user change
4116            it with the Flip View button. */
4117         flipView = appData.autoFlipView ?
4118           (newGameMode == IcsPlayingBlack) || ics_flip :
4119           appData.flipView;
4120
4121         /* Done with values from previous mode; copy in new ones */
4122         gameMode = newGameMode;
4123         ModeHighlight();
4124         ics_gamenum = gamenum;
4125         if (gamenum == gs_gamenum) {
4126             int klen = strlen(gs_kind);
4127             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4128             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4129             gameInfo.event = StrSave(str);
4130         } else {
4131             gameInfo.event = StrSave("ICS game");
4132         }
4133         gameInfo.site = StrSave(appData.icsHost);
4134         gameInfo.date = PGNDate();
4135         gameInfo.round = StrSave("-");
4136         gameInfo.white = StrSave(white);
4137         gameInfo.black = StrSave(black);
4138         timeControl = basetime * 60 * 1000;
4139         timeControl_2 = 0;
4140         timeIncrement = increment * 1000;
4141         movesPerSession = 0;
4142         gameInfo.timeControl = TimeControlTagValue();
4143         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4144   if (appData.debugMode) {
4145     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4146     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4147     setbuf(debugFP, NULL);
4148   }
4149
4150         gameInfo.outOfBook = NULL;
4151
4152         /* Do we have the ratings? */
4153         if (strcmp(player1Name, white) == 0 &&
4154             strcmp(player2Name, black) == 0) {
4155             if (appData.debugMode)
4156               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4157                       player1Rating, player2Rating);
4158             gameInfo.whiteRating = player1Rating;
4159             gameInfo.blackRating = player2Rating;
4160         } else if (strcmp(player2Name, white) == 0 &&
4161                    strcmp(player1Name, black) == 0) {
4162             if (appData.debugMode)
4163               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4164                       player2Rating, player1Rating);
4165             gameInfo.whiteRating = player2Rating;
4166             gameInfo.blackRating = player1Rating;
4167         }
4168         player1Name[0] = player2Name[0] = NULLCHAR;
4169
4170         /* Silence shouts if requested */
4171         if (appData.quietPlay &&
4172             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4173             SendToICS(ics_prefix);
4174             SendToICS("set shout 0\n");
4175         }
4176     }
4177
4178     /* Deal with midgame name changes */
4179     if (!newGame) {
4180         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4181             if (gameInfo.white) free(gameInfo.white);
4182             gameInfo.white = StrSave(white);
4183         }
4184         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4185             if (gameInfo.black) free(gameInfo.black);
4186             gameInfo.black = StrSave(black);
4187         }
4188     }
4189
4190     /* Throw away game result if anything actually changes in examine mode */
4191     if (gameMode == IcsExamining && !newGame) {
4192         gameInfo.result = GameUnfinished;
4193         if (gameInfo.resultDetails != NULL) {
4194             free(gameInfo.resultDetails);
4195             gameInfo.resultDetails = NULL;
4196         }
4197     }
4198
4199     /* In pausing && IcsExamining mode, we ignore boards coming
4200        in if they are in a different variation than we are. */
4201     if (pauseExamInvalid) return;
4202     if (pausing && gameMode == IcsExamining) {
4203         if (moveNum <= pauseExamForwardMostMove) {
4204             pauseExamInvalid = TRUE;
4205             forwardMostMove = pauseExamForwardMostMove;
4206             return;
4207         }
4208     }
4209
4210   if (appData.debugMode) {
4211     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4212   }
4213     /* Parse the board */
4214     for (k = 0; k < ranks; k++) {
4215       for (j = 0; j < files; j++)
4216         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4217       if(gameInfo.holdingsWidth > 1) {
4218            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4219            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4220       }
4221     }
4222     CopyBoard(boards[moveNum], board);
4223     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4224     if (moveNum == 0) {
4225         startedFromSetupPosition =
4226           !CompareBoards(board, initialPosition);
4227         if(startedFromSetupPosition)
4228             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4229     }
4230
4231     /* [HGM] Set castling rights. Take the outermost Rooks,
4232        to make it also work for FRC opening positions. Note that board12
4233        is really defective for later FRC positions, as it has no way to
4234        indicate which Rook can castle if they are on the same side of King.
4235        For the initial position we grant rights to the outermost Rooks,
4236        and remember thos rights, and we then copy them on positions
4237        later in an FRC game. This means WB might not recognize castlings with
4238        Rooks that have moved back to their original position as illegal,
4239        but in ICS mode that is not its job anyway.
4240     */
4241     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4242     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4243
4244         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4245             if(board[0][i] == WhiteRook) j = i;
4246         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4247         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4248             if(board[0][i] == WhiteRook) j = i;
4249         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4250         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4251             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4252         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4253         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4254             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4255         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4256
4257         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4258         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4259             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4260         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4261             if(board[BOARD_HEIGHT-1][k] == bKing)
4262                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4263         if(gameInfo.variant == VariantTwoKings) {
4264             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4265             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4266             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4267         }
4268     } else { int r;
4269         r = boards[moveNum][CASTLING][0] = initialRights[0];
4270         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4271         r = boards[moveNum][CASTLING][1] = initialRights[1];
4272         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4273         r = boards[moveNum][CASTLING][3] = initialRights[3];
4274         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4275         r = boards[moveNum][CASTLING][4] = initialRights[4];
4276         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4277         /* wildcastle kludge: always assume King has rights */
4278         r = boards[moveNum][CASTLING][2] = initialRights[2];
4279         r = boards[moveNum][CASTLING][5] = initialRights[5];
4280     }
4281     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4282     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4283
4284
4285     if (ics_getting_history == H_GOT_REQ_HEADER ||
4286         ics_getting_history == H_GOT_UNREQ_HEADER) {
4287         /* This was an initial position from a move list, not
4288            the current position */
4289         return;
4290     }
4291
4292     /* Update currentMove and known move number limits */
4293     newMove = newGame || moveNum > forwardMostMove;
4294
4295     if (newGame) {
4296         forwardMostMove = backwardMostMove = currentMove = moveNum;
4297         if (gameMode == IcsExamining && moveNum == 0) {
4298           /* Workaround for ICS limitation: we are not told the wild
4299              type when starting to examine a game.  But if we ask for
4300              the move list, the move list header will tell us */
4301             ics_getting_history = H_REQUESTED;
4302             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4303             SendToICS(str);
4304         }
4305     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4306                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4307 #if ZIPPY
4308         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4309         /* [HGM] applied this also to an engine that is silently watching        */
4310         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4311             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4312             gameInfo.variant == currentlyInitializedVariant) {
4313           takeback = forwardMostMove - moveNum;
4314           for (i = 0; i < takeback; i++) {
4315             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4316             SendToProgram("undo\n", &first);
4317           }
4318         }
4319 #endif
4320
4321         forwardMostMove = moveNum;
4322         if (!pausing || currentMove > forwardMostMove)
4323           currentMove = forwardMostMove;
4324     } else {
4325         /* New part of history that is not contiguous with old part */
4326         if (pausing && gameMode == IcsExamining) {
4327             pauseExamInvalid = TRUE;
4328             forwardMostMove = pauseExamForwardMostMove;
4329             return;
4330         }
4331         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4332 #if ZIPPY
4333             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4334                 // [HGM] when we will receive the move list we now request, it will be
4335                 // fed to the engine from the first move on. So if the engine is not
4336                 // in the initial position now, bring it there.
4337                 InitChessProgram(&first, 0);
4338             }
4339 #endif
4340             ics_getting_history = H_REQUESTED;
4341             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4342             SendToICS(str);
4343         }
4344         forwardMostMove = backwardMostMove = currentMove = moveNum;
4345     }
4346
4347     /* Update the clocks */
4348     if (strchr(elapsed_time, '.')) {
4349       /* Time is in ms */
4350       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4351       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4352     } else {
4353       /* Time is in seconds */
4354       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4355       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4356     }
4357
4358
4359 #if ZIPPY
4360     if (appData.zippyPlay && newGame &&
4361         gameMode != IcsObserving && gameMode != IcsIdle &&
4362         gameMode != IcsExamining)
4363       ZippyFirstBoard(moveNum, basetime, increment);
4364 #endif
4365
4366     /* Put the move on the move list, first converting
4367        to canonical algebraic form. */
4368     if (moveNum > 0) {
4369   if (appData.debugMode) {
4370     if (appData.debugMode) { int f = forwardMostMove;
4371         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4372                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4373                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4374     }
4375     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4376     fprintf(debugFP, "moveNum = %d\n", moveNum);
4377     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4378     setbuf(debugFP, NULL);
4379   }
4380         if (moveNum <= backwardMostMove) {
4381             /* We don't know what the board looked like before
4382                this move.  Punt. */
4383           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4384             strcat(parseList[moveNum - 1], " ");
4385             strcat(parseList[moveNum - 1], elapsed_time);
4386             moveList[moveNum - 1][0] = NULLCHAR;
4387         } else if (strcmp(move_str, "none") == 0) {
4388             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4389             /* Again, we don't know what the board looked like;
4390                this is really the start of the game. */
4391             parseList[moveNum - 1][0] = NULLCHAR;
4392             moveList[moveNum - 1][0] = NULLCHAR;
4393             backwardMostMove = moveNum;
4394             startedFromSetupPosition = TRUE;
4395             fromX = fromY = toX = toY = -1;
4396         } else {
4397           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4398           //                 So we parse the long-algebraic move string in stead of the SAN move
4399           int valid; char buf[MSG_SIZ], *prom;
4400
4401           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4402                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4403           // str looks something like "Q/a1-a2"; kill the slash
4404           if(str[1] == '/')
4405             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4406           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4407           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4408                 strcat(buf, prom); // long move lacks promo specification!
4409           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4410                 if(appData.debugMode)
4411                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4412                 safeStrCpy(move_str, buf, MSG_SIZ);
4413           }
4414           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4415                                 &fromX, &fromY, &toX, &toY, &promoChar)
4416                || ParseOneMove(buf, moveNum - 1, &moveType,
4417                                 &fromX, &fromY, &toX, &toY, &promoChar);
4418           // end of long SAN patch
4419           if (valid) {
4420             (void) CoordsToAlgebraic(boards[moveNum - 1],
4421                                      PosFlags(moveNum - 1),
4422                                      fromY, fromX, toY, toX, promoChar,
4423                                      parseList[moveNum-1]);
4424             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4425               case MT_NONE:
4426               case MT_STALEMATE:
4427               default:
4428                 break;
4429               case MT_CHECK:
4430                 if(gameInfo.variant != VariantShogi)
4431                     strcat(parseList[moveNum - 1], "+");
4432                 break;
4433               case MT_CHECKMATE:
4434               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4435                 strcat(parseList[moveNum - 1], "#");
4436                 break;
4437             }
4438             strcat(parseList[moveNum - 1], " ");
4439             strcat(parseList[moveNum - 1], elapsed_time);
4440             /* currentMoveString is set as a side-effect of ParseOneMove */
4441             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4442             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4443             strcat(moveList[moveNum - 1], "\n");
4444
4445             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4446                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4447               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4448                 ChessSquare old, new = boards[moveNum][k][j];
4449                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4450                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4451                   if(old == new) continue;
4452                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4453                   else if(new == WhiteWazir || new == BlackWazir) {
4454                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4455                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4456                       else boards[moveNum][k][j] = old; // preserve type of Gold
4457                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4458                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4459               }
4460           } else {
4461             /* Move from ICS was illegal!?  Punt. */
4462             if (appData.debugMode) {
4463               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4464               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4465             }
4466             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4467             strcat(parseList[moveNum - 1], " ");
4468             strcat(parseList[moveNum - 1], elapsed_time);
4469             moveList[moveNum - 1][0] = NULLCHAR;
4470             fromX = fromY = toX = toY = -1;
4471           }
4472         }
4473   if (appData.debugMode) {
4474     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4475     setbuf(debugFP, NULL);
4476   }
4477
4478 #if ZIPPY
4479         /* Send move to chess program (BEFORE animating it). */
4480         if (appData.zippyPlay && !newGame && newMove &&
4481            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4482
4483             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4484                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4485                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4486                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4487                             move_str);
4488                     DisplayError(str, 0);
4489                 } else {
4490                     if (first.sendTime) {
4491                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4492                     }
4493                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4494                     if (firstMove && !bookHit) {
4495                         firstMove = FALSE;
4496                         if (first.useColors) {
4497                           SendToProgram(gameMode == IcsPlayingWhite ?
4498                                         "white\ngo\n" :
4499                                         "black\ngo\n", &first);
4500                         } else {
4501                           SendToProgram("go\n", &first);
4502                         }
4503                         first.maybeThinking = TRUE;
4504                     }
4505                 }
4506             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4507               if (moveList[moveNum - 1][0] == NULLCHAR) {
4508                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4509                 DisplayError(str, 0);
4510               } else {
4511                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4512                 SendMoveToProgram(moveNum - 1, &first);
4513               }
4514             }
4515         }
4516 #endif
4517     }
4518
4519     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4520         /* If move comes from a remote source, animate it.  If it
4521            isn't remote, it will have already been animated. */
4522         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4523             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4524         }
4525         if (!pausing && appData.highlightLastMove) {
4526             SetHighlights(fromX, fromY, toX, toY);
4527         }
4528     }
4529
4530     /* Start the clocks */
4531     whiteFlag = blackFlag = FALSE;
4532     appData.clockMode = !(basetime == 0 && increment == 0);
4533     if (ticking == 0) {
4534       ics_clock_paused = TRUE;
4535       StopClocks();
4536     } else if (ticking == 1) {
4537       ics_clock_paused = FALSE;
4538     }
4539     if (gameMode == IcsIdle ||
4540         relation == RELATION_OBSERVING_STATIC ||
4541         relation == RELATION_EXAMINING ||
4542         ics_clock_paused)
4543       DisplayBothClocks();
4544     else
4545       StartClocks();
4546
4547     /* Display opponents and material strengths */
4548     if (gameInfo.variant != VariantBughouse &&
4549         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4550         if (tinyLayout || smallLayout) {
4551             if(gameInfo.variant == VariantNormal)
4552               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4553                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4554                     basetime, increment);
4555             else
4556               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4557                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4558                     basetime, increment, (int) gameInfo.variant);
4559         } else {
4560             if(gameInfo.variant == VariantNormal)
4561               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4562                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4563                     basetime, increment);
4564             else
4565               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4566                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4567                     basetime, increment, VariantName(gameInfo.variant));
4568         }
4569         DisplayTitle(str);
4570   if (appData.debugMode) {
4571     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4572   }
4573     }
4574
4575
4576     /* Display the board */
4577     if (!pausing && !appData.noGUI) {
4578
4579       if (appData.premove)
4580           if (!gotPremove ||
4581              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4582              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4583               ClearPremoveHighlights();
4584
4585       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4586         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4587       DrawPosition(j, boards[currentMove]);
4588
4589       DisplayMove(moveNum - 1);
4590       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4591             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4592               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4593         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4594       }
4595     }
4596
4597     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4598 #if ZIPPY
4599     if(bookHit) { // [HGM] book: simulate book reply
4600         static char bookMove[MSG_SIZ]; // a bit generous?
4601
4602         programStats.nodes = programStats.depth = programStats.time =
4603         programStats.score = programStats.got_only_move = 0;
4604         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4605
4606         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4607         strcat(bookMove, bookHit);
4608         HandleMachineMove(bookMove, &first);
4609     }
4610 #endif
4611 }
4612
4613 void
4614 GetMoveListEvent()
4615 {
4616     char buf[MSG_SIZ];
4617     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4618         ics_getting_history = H_REQUESTED;
4619         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4620         SendToICS(buf);
4621     }
4622 }
4623
4624 void
4625 AnalysisPeriodicEvent(force)
4626      int force;
4627 {
4628     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4629          && !force) || !appData.periodicUpdates)
4630       return;
4631
4632     /* Send . command to Crafty to collect stats */
4633     SendToProgram(".\n", &first);
4634
4635     /* Don't send another until we get a response (this makes
4636        us stop sending to old Crafty's which don't understand
4637        the "." command (sending illegal cmds resets node count & time,
4638        which looks bad)) */
4639     programStats.ok_to_send = 0;
4640 }
4641
4642 void ics_update_width(new_width)
4643         int new_width;
4644 {
4645         ics_printf("set width %d\n", new_width);
4646 }
4647
4648 void
4649 SendMoveToProgram(moveNum, cps)
4650      int moveNum;
4651      ChessProgramState *cps;
4652 {
4653     char buf[MSG_SIZ];
4654
4655     if (cps->useUsermove) {
4656       SendToProgram("usermove ", cps);
4657     }
4658     if (cps->useSAN) {
4659       char *space;
4660       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4661         int len = space - parseList[moveNum];
4662         memcpy(buf, parseList[moveNum], len);
4663         buf[len++] = '\n';
4664         buf[len] = NULLCHAR;
4665       } else {
4666         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4667       }
4668       SendToProgram(buf, cps);
4669     } else {
4670       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4671         AlphaRank(moveList[moveNum], 4);
4672         SendToProgram(moveList[moveNum], cps);
4673         AlphaRank(moveList[moveNum], 4); // and back
4674       } else
4675       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4676        * the engine. It would be nice to have a better way to identify castle
4677        * moves here. */
4678       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4679                                                                          && cps->useOOCastle) {
4680         int fromX = moveList[moveNum][0] - AAA;
4681         int fromY = moveList[moveNum][1] - ONE;
4682         int toX = moveList[moveNum][2] - AAA;
4683         int toY = moveList[moveNum][3] - ONE;
4684         if((boards[moveNum][fromY][fromX] == WhiteKing
4685             && boards[moveNum][toY][toX] == WhiteRook)
4686            || (boards[moveNum][fromY][fromX] == BlackKing
4687                && boards[moveNum][toY][toX] == BlackRook)) {
4688           if(toX > fromX) SendToProgram("O-O\n", cps);
4689           else SendToProgram("O-O-O\n", cps);
4690         }
4691         else SendToProgram(moveList[moveNum], cps);
4692       }
4693       else SendToProgram(moveList[moveNum], cps);
4694       /* End of additions by Tord */
4695     }
4696
4697     /* [HGM] setting up the opening has brought engine in force mode! */
4698     /*       Send 'go' if we are in a mode where machine should play. */
4699     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4700         (gameMode == TwoMachinesPlay   ||
4701 #if ZIPPY
4702          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4703 #endif
4704          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4705         SendToProgram("go\n", cps);
4706   if (appData.debugMode) {
4707     fprintf(debugFP, "(extra)\n");
4708   }
4709     }
4710     setboardSpoiledMachineBlack = 0;
4711 }
4712
4713 void
4714 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4715      ChessMove moveType;
4716      int fromX, fromY, toX, toY;
4717      char promoChar;
4718 {
4719     char user_move[MSG_SIZ];
4720
4721     switch (moveType) {
4722       default:
4723         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4724                 (int)moveType, fromX, fromY, toX, toY);
4725         DisplayError(user_move + strlen("say "), 0);
4726         break;
4727       case WhiteKingSideCastle:
4728       case BlackKingSideCastle:
4729       case WhiteQueenSideCastleWild:
4730       case BlackQueenSideCastleWild:
4731       /* PUSH Fabien */
4732       case WhiteHSideCastleFR:
4733       case BlackHSideCastleFR:
4734       /* POP Fabien */
4735         snprintf(user_move, MSG_SIZ, "o-o\n");
4736         break;
4737       case WhiteQueenSideCastle:
4738       case BlackQueenSideCastle:
4739       case WhiteKingSideCastleWild:
4740       case BlackKingSideCastleWild:
4741       /* PUSH Fabien */
4742       case WhiteASideCastleFR:
4743       case BlackASideCastleFR:
4744       /* POP Fabien */
4745         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4746         break;
4747       case WhiteNonPromotion:
4748       case BlackNonPromotion:
4749         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4750         break;
4751       case WhitePromotion:
4752       case BlackPromotion:
4753         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4754           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4755                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4756                 PieceToChar(WhiteFerz));
4757         else if(gameInfo.variant == VariantGreat)
4758           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4759                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4760                 PieceToChar(WhiteMan));
4761         else
4762           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4763                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4764                 promoChar);
4765         break;
4766       case WhiteDrop:
4767       case BlackDrop:
4768       drop:
4769         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4770                  ToUpper(PieceToChar((ChessSquare) fromX)),
4771                  AAA + toX, ONE + toY);
4772         break;
4773       case IllegalMove:  /* could be a variant we don't quite understand */
4774         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4775       case NormalMove:
4776       case WhiteCapturesEnPassant:
4777       case BlackCapturesEnPassant:
4778         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4779                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4780         break;
4781     }
4782     SendToICS(user_move);
4783     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4784         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4785 }
4786
4787 void
4788 UploadGameEvent()
4789 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4790     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4791     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4792     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4793         DisplayError("You cannot do this while you are playing or observing", 0);
4794         return;
4795     }
4796     if(gameMode != IcsExamining) { // is this ever not the case?
4797         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4798
4799         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4800           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4801         } else { // on FICS we must first go to general examine mode
4802           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4803         }
4804         if(gameInfo.variant != VariantNormal) {
4805             // try figure out wild number, as xboard names are not always valid on ICS
4806             for(i=1; i<=36; i++) {
4807               snprintf(buf, MSG_SIZ, "wild/%d", i);
4808                 if(StringToVariant(buf) == gameInfo.variant) break;
4809             }
4810             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4811             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4812             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4813         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4814         SendToICS(ics_prefix);
4815         SendToICS(buf);
4816         if(startedFromSetupPosition || backwardMostMove != 0) {
4817           fen = PositionToFEN(backwardMostMove, NULL);
4818           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4819             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4820             SendToICS(buf);
4821           } else { // FICS: everything has to set by separate bsetup commands
4822             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4823             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4824             SendToICS(buf);
4825             if(!WhiteOnMove(backwardMostMove)) {
4826                 SendToICS("bsetup tomove black\n");
4827             }
4828             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4829             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4830             SendToICS(buf);
4831             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4832             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4833             SendToICS(buf);
4834             i = boards[backwardMostMove][EP_STATUS];
4835             if(i >= 0) { // set e.p.
4836               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4837                 SendToICS(buf);
4838             }
4839             bsetup++;
4840           }
4841         }
4842       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4843             SendToICS("bsetup done\n"); // switch to normal examining.
4844     }
4845     for(i = backwardMostMove; i<last; i++) {
4846         char buf[20];
4847         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4848         SendToICS(buf);
4849     }
4850     SendToICS(ics_prefix);
4851     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4852 }
4853
4854 void
4855 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4856      int rf, ff, rt, ft;
4857      char promoChar;
4858      char move[7];
4859 {
4860     if (rf == DROP_RANK) {
4861       sprintf(move, "%c@%c%c\n",
4862                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4863     } else {
4864         if (promoChar == 'x' || promoChar == NULLCHAR) {
4865           sprintf(move, "%c%c%c%c\n",
4866                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4867         } else {
4868             sprintf(move, "%c%c%c%c%c\n",
4869                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4870         }
4871     }
4872 }
4873
4874 void
4875 ProcessICSInitScript(f)
4876      FILE *f;
4877 {
4878     char buf[MSG_SIZ];
4879
4880     while (fgets(buf, MSG_SIZ, f)) {
4881         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4882     }
4883
4884     fclose(f);
4885 }
4886
4887
4888 static int lastX, lastY, selectFlag, dragging;
4889
4890 void
4891 Sweep(int step)
4892 {
4893     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4894     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4895     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4896     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4897     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4898     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4899     do {
4900         promoSweep -= step;
4901         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4902         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4903         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4904         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4905         if(!step) step = 1;
4906     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4907             appData.testLegality && (promoSweep == king ||
4908             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4909     ChangeDragPiece(promoSweep);
4910 }
4911
4912 int PromoScroll(int x, int y)
4913 {
4914   int step = 0;
4915
4916   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4917   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4918   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4919   if(!step) return FALSE;
4920   lastX = x; lastY = y;
4921   if((promoSweep < BlackPawn) == flipView) step = -step;
4922   if(step > 0) selectFlag = 1;
4923   if(!selectFlag) Sweep(step);
4924   return FALSE;
4925 }
4926
4927 void
4928 NextPiece(int step)
4929 {
4930     ChessSquare piece = boards[currentMove][toY][toX];
4931     do {
4932         pieceSweep -= step;
4933         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4934         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4935         if(!step) step = -1;
4936     } while(PieceToChar(pieceSweep) == '.');
4937     boards[currentMove][toY][toX] = pieceSweep;
4938     DrawPosition(FALSE, boards[currentMove]);
4939     boards[currentMove][toY][toX] = piece;
4940 }
4941 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4942 void
4943 AlphaRank(char *move, int n)
4944 {
4945 //    char *p = move, c; int x, y;
4946
4947     if (appData.debugMode) {
4948         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4949     }
4950
4951     if(move[1]=='*' &&
4952        move[2]>='0' && move[2]<='9' &&
4953        move[3]>='a' && move[3]<='x'    ) {
4954         move[1] = '@';
4955         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4956         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4957     } else
4958     if(move[0]>='0' && move[0]<='9' &&
4959        move[1]>='a' && move[1]<='x' &&
4960        move[2]>='0' && move[2]<='9' &&
4961        move[3]>='a' && move[3]<='x'    ) {
4962         /* input move, Shogi -> normal */
4963         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4964         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4965         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4966         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4967     } else
4968     if(move[1]=='@' &&
4969        move[3]>='0' && move[3]<='9' &&
4970        move[2]>='a' && move[2]<='x'    ) {
4971         move[1] = '*';
4972         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4973         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4974     } else
4975     if(
4976        move[0]>='a' && move[0]<='x' &&
4977        move[3]>='0' && move[3]<='9' &&
4978        move[2]>='a' && move[2]<='x'    ) {
4979          /* output move, normal -> Shogi */
4980         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4981         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4982         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4983         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4984         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4985     }
4986     if (appData.debugMode) {
4987         fprintf(debugFP, "   out = '%s'\n", move);
4988     }
4989 }
4990
4991 char yy_textstr[8000];
4992
4993 /* Parser for moves from gnuchess, ICS, or user typein box */
4994 Boolean
4995 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4996      char *move;
4997      int moveNum;
4998      ChessMove *moveType;
4999      int *fromX, *fromY, *toX, *toY;
5000      char *promoChar;
5001 {
5002     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5003
5004     switch (*moveType) {
5005       case WhitePromotion:
5006       case BlackPromotion:
5007       case WhiteNonPromotion:
5008       case BlackNonPromotion:
5009       case NormalMove:
5010       case WhiteCapturesEnPassant:
5011       case BlackCapturesEnPassant:
5012       case WhiteKingSideCastle:
5013       case WhiteQueenSideCastle:
5014       case BlackKingSideCastle:
5015       case BlackQueenSideCastle:
5016       case WhiteKingSideCastleWild:
5017       case WhiteQueenSideCastleWild:
5018       case BlackKingSideCastleWild:
5019       case BlackQueenSideCastleWild:
5020       /* Code added by Tord: */
5021       case WhiteHSideCastleFR:
5022       case WhiteASideCastleFR:
5023       case BlackHSideCastleFR:
5024       case BlackASideCastleFR:
5025       /* End of code added by Tord */
5026       case IllegalMove:         /* bug or odd chess variant */
5027         *fromX = currentMoveString[0] - AAA;
5028         *fromY = currentMoveString[1] - ONE;
5029         *toX = currentMoveString[2] - AAA;
5030         *toY = currentMoveString[3] - ONE;
5031         *promoChar = currentMoveString[4];
5032         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5033             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5034     if (appData.debugMode) {
5035         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5036     }
5037             *fromX = *fromY = *toX = *toY = 0;
5038             return FALSE;
5039         }
5040         if (appData.testLegality) {
5041           return (*moveType != IllegalMove);
5042         } else {
5043           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5044                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5045         }
5046
5047       case WhiteDrop:
5048       case BlackDrop:
5049         *fromX = *moveType == WhiteDrop ?
5050           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5051           (int) CharToPiece(ToLower(currentMoveString[0]));
5052         *fromY = DROP_RANK;
5053         *toX = currentMoveString[2] - AAA;
5054         *toY = currentMoveString[3] - ONE;
5055         *promoChar = NULLCHAR;
5056         return TRUE;
5057
5058       case AmbiguousMove:
5059       case ImpossibleMove:
5060       case EndOfFile:
5061       case ElapsedTime:
5062       case Comment:
5063       case PGNTag:
5064       case NAG:
5065       case WhiteWins:
5066       case BlackWins:
5067       case GameIsDrawn:
5068       default:
5069     if (appData.debugMode) {
5070         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5071     }
5072         /* bug? */
5073         *fromX = *fromY = *toX = *toY = 0;
5074         *promoChar = NULLCHAR;
5075         return FALSE;
5076     }
5077 }
5078
5079
5080 void
5081 ParsePV(char *pv, Boolean storeComments)
5082 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5083   int fromX, fromY, toX, toY; char promoChar;
5084   ChessMove moveType;
5085   Boolean valid;
5086   int nr = 0;
5087
5088   endPV = forwardMostMove;
5089   do {
5090     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5091     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5092     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5093 if(appData.debugMode){
5094 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);
5095 }
5096     if(!valid && nr == 0 &&
5097        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5098         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5099         // Hande case where played move is different from leading PV move
5100         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5101         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5102         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5103         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5104           endPV += 2; // if position different, keep this
5105           moveList[endPV-1][0] = fromX + AAA;
5106           moveList[endPV-1][1] = fromY + ONE;
5107           moveList[endPV-1][2] = toX + AAA;
5108           moveList[endPV-1][3] = toY + ONE;
5109           parseList[endPV-1][0] = NULLCHAR;
5110           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5111         }
5112       }
5113     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5114     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5115     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5116     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5117         valid++; // allow comments in PV
5118         continue;
5119     }
5120     nr++;
5121     if(endPV+1 > framePtr) break; // no space, truncate
5122     if(!valid) break;
5123     endPV++;
5124     CopyBoard(boards[endPV], boards[endPV-1]);
5125     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5126     moveList[endPV-1][0] = fromX + AAA;
5127     moveList[endPV-1][1] = fromY + ONE;
5128     moveList[endPV-1][2] = toX + AAA;
5129     moveList[endPV-1][3] = toY + ONE;
5130     moveList[endPV-1][4] = promoChar;
5131     moveList[endPV-1][5] = NULLCHAR;
5132     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5133     if(storeComments)
5134         CoordsToAlgebraic(boards[endPV - 1],
5135                              PosFlags(endPV - 1),
5136                              fromY, fromX, toY, toX, promoChar,
5137                              parseList[endPV - 1]);
5138     else
5139         parseList[endPV-1][0] = NULLCHAR;
5140   } while(valid);
5141   currentMove = endPV;
5142   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5143   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5144                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5145   DrawPosition(TRUE, boards[currentMove]);
5146 }
5147
5148 Boolean
5149 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5150 {
5151         int startPV;
5152         char *p;
5153
5154         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5155         lastX = x; lastY = y;
5156         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5157         startPV = index;
5158         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5159         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5160         index = startPV;
5161         do{ while(buf[index] && buf[index] != '\n') index++;
5162         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5163         buf[index] = 0;
5164         ParsePV(buf+startPV, FALSE);
5165         *start = startPV; *end = index-1;
5166         return TRUE;
5167 }
5168
5169 Boolean
5170 LoadPV(int x, int y)
5171 { // called on right mouse click to load PV
5172   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5173   lastX = x; lastY = y;
5174   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5175   return TRUE;
5176 }
5177
5178 void
5179 UnLoadPV()
5180 {
5181   if(endPV < 0) return;
5182   endPV = -1;
5183   currentMove = forwardMostMove;
5184   ClearPremoveHighlights();
5185   DrawPosition(TRUE, boards[currentMove]);
5186 }
5187
5188 void
5189 MovePV(int x, int y, int h)
5190 { // step through PV based on mouse coordinates (called on mouse move)
5191   int margin = h>>3, step = 0;
5192
5193   // we must somehow check if right button is still down (might be released off board!)
5194   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5195   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5196   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5197   if(!step) return;
5198   lastX = x; lastY = y;
5199
5200   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5201   if(endPV < 0) return;
5202   if(y < margin) step = 1; else
5203   if(y > h - margin) step = -1;
5204   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5205   currentMove += step;
5206   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5207   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5208                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5209   DrawPosition(FALSE, boards[currentMove]);
5210 }
5211
5212
5213 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5214 // All positions will have equal probability, but the current method will not provide a unique
5215 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5216 #define DARK 1
5217 #define LITE 2
5218 #define ANY 3
5219
5220 int squaresLeft[4];
5221 int piecesLeft[(int)BlackPawn];
5222 int seed, nrOfShuffles;
5223
5224 void GetPositionNumber()
5225 {       // sets global variable seed
5226         int i;
5227
5228         seed = appData.defaultFrcPosition;
5229         if(seed < 0) { // randomize based on time for negative FRC position numbers
5230                 for(i=0; i<50; i++) seed += random();
5231                 seed = random() ^ random() >> 8 ^ random() << 8;
5232                 if(seed<0) seed = -seed;
5233         }
5234 }
5235
5236 int put(Board board, int pieceType, int rank, int n, int shade)
5237 // put the piece on the (n-1)-th empty squares of the given shade
5238 {
5239         int i;
5240
5241         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5242                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5243                         board[rank][i] = (ChessSquare) pieceType;
5244                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5245                         squaresLeft[ANY]--;
5246                         piecesLeft[pieceType]--;
5247                         return i;
5248                 }
5249         }
5250         return -1;
5251 }
5252
5253
5254 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5255 // calculate where the next piece goes, (any empty square), and put it there
5256 {
5257         int i;
5258
5259         i = seed % squaresLeft[shade];
5260         nrOfShuffles *= squaresLeft[shade];
5261         seed /= squaresLeft[shade];
5262         put(board, pieceType, rank, i, shade);
5263 }
5264
5265 void AddTwoPieces(Board board, int pieceType, int rank)
5266 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5267 {
5268         int i, n=squaresLeft[ANY], j=n-1, k;
5269
5270         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5271         i = seed % k;  // pick one
5272         nrOfShuffles *= k;
5273         seed /= k;
5274         while(i >= j) i -= j--;
5275         j = n - 1 - j; i += j;
5276         put(board, pieceType, rank, j, ANY);
5277         put(board, pieceType, rank, i, ANY);
5278 }
5279
5280 void SetUpShuffle(Board board, int number)
5281 {
5282         int i, p, first=1;
5283
5284         GetPositionNumber(); nrOfShuffles = 1;
5285
5286         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5287         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5288         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5289
5290         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5291
5292         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5293             p = (int) board[0][i];
5294             if(p < (int) BlackPawn) piecesLeft[p] ++;
5295             board[0][i] = EmptySquare;
5296         }
5297
5298         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5299             // shuffles restricted to allow normal castling put KRR first
5300             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5301                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5302             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5303                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5304             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5305                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5306             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5307                 put(board, WhiteRook, 0, 0, ANY);
5308             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5309         }
5310
5311         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5312             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5313             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5314                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5315                 while(piecesLeft[p] >= 2) {
5316                     AddOnePiece(board, p, 0, LITE);
5317                     AddOnePiece(board, p, 0, DARK);
5318                 }
5319                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5320             }
5321
5322         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5323             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5324             // but we leave King and Rooks for last, to possibly obey FRC restriction
5325             if(p == (int)WhiteRook) continue;
5326             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5327             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5328         }
5329
5330         // now everything is placed, except perhaps King (Unicorn) and Rooks
5331
5332         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5333             // Last King gets castling rights
5334             while(piecesLeft[(int)WhiteUnicorn]) {
5335                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5336                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5337             }
5338
5339             while(piecesLeft[(int)WhiteKing]) {
5340                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5341                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5342             }
5343
5344
5345         } else {
5346             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5347             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5348         }
5349
5350         // Only Rooks can be left; simply place them all
5351         while(piecesLeft[(int)WhiteRook]) {
5352                 i = put(board, WhiteRook, 0, 0, ANY);
5353                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5354                         if(first) {
5355                                 first=0;
5356                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5357                         }
5358                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5359                 }
5360         }
5361         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5362             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5363         }
5364
5365         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5366 }
5367
5368 int SetCharTable( char *table, const char * map )
5369 /* [HGM] moved here from winboard.c because of its general usefulness */
5370 /*       Basically a safe strcpy that uses the last character as King */
5371 {
5372     int result = FALSE; int NrPieces;
5373
5374     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5375                     && NrPieces >= 12 && !(NrPieces&1)) {
5376         int i; /* [HGM] Accept even length from 12 to 34 */
5377
5378         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5379         for( i=0; i<NrPieces/2-1; i++ ) {
5380             table[i] = map[i];
5381             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5382         }
5383         table[(int) WhiteKing]  = map[NrPieces/2-1];
5384         table[(int) BlackKing]  = map[NrPieces-1];
5385
5386         result = TRUE;
5387     }
5388
5389     return result;
5390 }
5391
5392 void Prelude(Board board)
5393 {       // [HGM] superchess: random selection of exo-pieces
5394         int i, j, k; ChessSquare p;
5395         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5396
5397         GetPositionNumber(); // use FRC position number
5398
5399         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5400             SetCharTable(pieceToChar, appData.pieceToCharTable);
5401             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5402                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5403         }
5404
5405         j = seed%4;                 seed /= 4;
5406         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5407         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5408         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5409         j = seed%3 + (seed%3 >= j); seed /= 3;
5410         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5411         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5412         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5413         j = seed%3;                 seed /= 3;
5414         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5415         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5416         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5417         j = seed%2 + (seed%2 >= j); seed /= 2;
5418         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5419         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5420         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5421         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5422         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5423         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5424         put(board, exoPieces[0],    0, 0, ANY);
5425         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5426 }
5427
5428 void
5429 InitPosition(redraw)
5430      int redraw;
5431 {
5432     ChessSquare (* pieces)[BOARD_FILES];
5433     int i, j, pawnRow, overrule,
5434     oldx = gameInfo.boardWidth,
5435     oldy = gameInfo.boardHeight,
5436     oldh = gameInfo.holdingsWidth;
5437     static int oldv;
5438
5439     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5440
5441     /* [AS] Initialize pv info list [HGM] and game status */
5442     {
5443         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5444             pvInfoList[i].depth = 0;
5445             boards[i][EP_STATUS] = EP_NONE;
5446             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5447         }
5448
5449         initialRulePlies = 0; /* 50-move counter start */
5450
5451         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5452         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5453     }
5454
5455
5456     /* [HGM] logic here is completely changed. In stead of full positions */
5457     /* the initialized data only consist of the two backranks. The switch */
5458     /* selects which one we will use, which is than copied to the Board   */
5459     /* initialPosition, which for the rest is initialized by Pawns and    */
5460     /* empty squares. This initial position is then copied to boards[0],  */
5461     /* possibly after shuffling, so that it remains available.            */
5462
5463     gameInfo.holdingsWidth = 0; /* default board sizes */
5464     gameInfo.boardWidth    = 8;
5465     gameInfo.boardHeight   = 8;
5466     gameInfo.holdingsSize  = 0;
5467     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5468     for(i=0; i<BOARD_FILES-2; i++)
5469       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5470     initialPosition[EP_STATUS] = EP_NONE;
5471     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5472     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5473          SetCharTable(pieceNickName, appData.pieceNickNames);
5474     else SetCharTable(pieceNickName, "............");
5475     pieces = FIDEArray;
5476
5477     switch (gameInfo.variant) {
5478     case VariantFischeRandom:
5479       shuffleOpenings = TRUE;
5480     default:
5481       break;
5482     case VariantShatranj:
5483       pieces = ShatranjArray;
5484       nrCastlingRights = 0;
5485       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5486       break;
5487     case VariantMakruk:
5488       pieces = makrukArray;
5489       nrCastlingRights = 0;
5490       startedFromSetupPosition = TRUE;
5491       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5492       break;
5493     case VariantTwoKings:
5494       pieces = twoKingsArray;
5495       break;
5496     case VariantCapaRandom:
5497       shuffleOpenings = TRUE;
5498     case VariantCapablanca:
5499       pieces = CapablancaArray;
5500       gameInfo.boardWidth = 10;
5501       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5502       break;
5503     case VariantGothic:
5504       pieces = GothicArray;
5505       gameInfo.boardWidth = 10;
5506       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5507       break;
5508     case VariantSChess:
5509       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5510       gameInfo.holdingsSize = 7;
5511       break;
5512     case VariantJanus:
5513       pieces = JanusArray;
5514       gameInfo.boardWidth = 10;
5515       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5516       nrCastlingRights = 6;
5517         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5518         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5519         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5520         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5521         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5522         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5523       break;
5524     case VariantFalcon:
5525       pieces = FalconArray;
5526       gameInfo.boardWidth = 10;
5527       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5528       break;
5529     case VariantXiangqi:
5530       pieces = XiangqiArray;
5531       gameInfo.boardWidth  = 9;
5532       gameInfo.boardHeight = 10;
5533       nrCastlingRights = 0;
5534       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5535       break;
5536     case VariantShogi:
5537       pieces = ShogiArray;
5538       gameInfo.boardWidth  = 9;
5539       gameInfo.boardHeight = 9;
5540       gameInfo.holdingsSize = 7;
5541       nrCastlingRights = 0;
5542       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5543       break;
5544     case VariantCourier:
5545       pieces = CourierArray;
5546       gameInfo.boardWidth  = 12;
5547       nrCastlingRights = 0;
5548       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5549       break;
5550     case VariantKnightmate:
5551       pieces = KnightmateArray;
5552       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5553       break;
5554     case VariantSpartan:
5555       pieces = SpartanArray;
5556       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5557       break;
5558     case VariantFairy:
5559       pieces = fairyArray;
5560       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5561       break;
5562     case VariantGreat:
5563       pieces = GreatArray;
5564       gameInfo.boardWidth = 10;
5565       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5566       gameInfo.holdingsSize = 8;
5567       break;
5568     case VariantSuper:
5569       pieces = FIDEArray;
5570       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5571       gameInfo.holdingsSize = 8;
5572       startedFromSetupPosition = TRUE;
5573       break;
5574     case VariantCrazyhouse:
5575     case VariantBughouse:
5576       pieces = FIDEArray;
5577       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5578       gameInfo.holdingsSize = 5;
5579       break;
5580     case VariantWildCastle:
5581       pieces = FIDEArray;
5582       /* !!?shuffle with kings guaranteed to be on d or e file */
5583       shuffleOpenings = 1;
5584       break;
5585     case VariantNoCastle:
5586       pieces = FIDEArray;
5587       nrCastlingRights = 0;
5588       /* !!?unconstrained back-rank shuffle */
5589       shuffleOpenings = 1;
5590       break;
5591     }
5592
5593     overrule = 0;
5594     if(appData.NrFiles >= 0) {
5595         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5596         gameInfo.boardWidth = appData.NrFiles;
5597     }
5598     if(appData.NrRanks >= 0) {
5599         gameInfo.boardHeight = appData.NrRanks;
5600     }
5601     if(appData.holdingsSize >= 0) {
5602         i = appData.holdingsSize;
5603         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5604         gameInfo.holdingsSize = i;
5605     }
5606     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5607     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5608         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5609
5610     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5611     if(pawnRow < 1) pawnRow = 1;
5612     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5613
5614     /* User pieceToChar list overrules defaults */
5615     if(appData.pieceToCharTable != NULL)
5616         SetCharTable(pieceToChar, appData.pieceToCharTable);
5617
5618     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5619
5620         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5621             s = (ChessSquare) 0; /* account holding counts in guard band */
5622         for( i=0; i<BOARD_HEIGHT; i++ )
5623             initialPosition[i][j] = s;
5624
5625         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5626         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5627         initialPosition[pawnRow][j] = WhitePawn;
5628         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5629         if(gameInfo.variant == VariantXiangqi) {
5630             if(j&1) {
5631                 initialPosition[pawnRow][j] =
5632                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5633                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5634                    initialPosition[2][j] = WhiteCannon;
5635                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5636                 }
5637             }
5638         }
5639         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5640     }
5641     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5642
5643             j=BOARD_LEFT+1;
5644             initialPosition[1][j] = WhiteBishop;
5645             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5646             j=BOARD_RGHT-2;
5647             initialPosition[1][j] = WhiteRook;
5648             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5649     }
5650
5651     if( nrCastlingRights == -1) {
5652         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5653         /*       This sets default castling rights from none to normal corners   */
5654         /* Variants with other castling rights must set them themselves above    */
5655         nrCastlingRights = 6;
5656
5657         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5658         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5659         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5660         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5661         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5662         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5663      }
5664
5665      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5666      if(gameInfo.variant == VariantGreat) { // promotion commoners
5667         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5668         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5669         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5670         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5671      }
5672      if( gameInfo.variant == VariantSChess ) {
5673       initialPosition[1][0] = BlackMarshall;
5674       initialPosition[2][0] = BlackAngel;
5675       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5676       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5677       initialPosition[1][1] = initialPosition[2][1] = 
5678       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5679      }
5680   if (appData.debugMode) {
5681     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5682   }
5683     if(shuffleOpenings) {
5684         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5685         startedFromSetupPosition = TRUE;
5686     }
5687     if(startedFromPositionFile) {
5688       /* [HGM] loadPos: use PositionFile for every new game */
5689       CopyBoard(initialPosition, filePosition);
5690       for(i=0; i<nrCastlingRights; i++)
5691           initialRights[i] = filePosition[CASTLING][i];
5692       startedFromSetupPosition = TRUE;
5693     }
5694
5695     CopyBoard(boards[0], initialPosition);
5696
5697     if(oldx != gameInfo.boardWidth ||
5698        oldy != gameInfo.boardHeight ||
5699        oldv != gameInfo.variant ||
5700        oldh != gameInfo.holdingsWidth
5701                                          )
5702             InitDrawingSizes(-2 ,0);
5703
5704     oldv = gameInfo.variant;
5705     if (redraw)
5706       DrawPosition(TRUE, boards[currentMove]);
5707 }
5708
5709 void
5710 SendBoard(cps, moveNum)
5711      ChessProgramState *cps;
5712      int moveNum;
5713 {
5714     char message[MSG_SIZ];
5715
5716     if (cps->useSetboard) {
5717       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5718       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5719       SendToProgram(message, cps);
5720       free(fen);
5721
5722     } else {
5723       ChessSquare *bp;
5724       int i, j;
5725       /* Kludge to set black to move, avoiding the troublesome and now
5726        * deprecated "black" command.
5727        */
5728       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5729         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5730
5731       SendToProgram("edit\n", cps);
5732       SendToProgram("#\n", cps);
5733       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5734         bp = &boards[moveNum][i][BOARD_LEFT];
5735         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5736           if ((int) *bp < (int) BlackPawn) {
5737             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5738                     AAA + j, ONE + i);
5739             if(message[0] == '+' || message[0] == '~') {
5740               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5741                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5742                         AAA + j, ONE + i);
5743             }
5744             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5745                 message[1] = BOARD_RGHT   - 1 - j + '1';
5746                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5747             }
5748             SendToProgram(message, cps);
5749           }
5750         }
5751       }
5752
5753       SendToProgram("c\n", cps);
5754       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5755         bp = &boards[moveNum][i][BOARD_LEFT];
5756         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5757           if (((int) *bp != (int) EmptySquare)
5758               && ((int) *bp >= (int) BlackPawn)) {
5759             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5760                     AAA + j, ONE + i);
5761             if(message[0] == '+' || message[0] == '~') {
5762               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5763                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5764                         AAA + j, ONE + i);
5765             }
5766             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5767                 message[1] = BOARD_RGHT   - 1 - j + '1';
5768                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5769             }
5770             SendToProgram(message, cps);
5771           }
5772         }
5773       }
5774
5775       SendToProgram(".\n", cps);
5776     }
5777     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5778 }
5779
5780 ChessSquare
5781 DefaultPromoChoice(int white)
5782 {
5783     ChessSquare result;
5784     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5785         result = WhiteFerz; // no choice
5786     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5787         result= WhiteKing; // in Suicide Q is the last thing we want
5788     else if(gameInfo.variant == VariantSpartan)
5789         result = white ? WhiteQueen : WhiteAngel;
5790     else result = WhiteQueen;
5791     if(!white) result = WHITE_TO_BLACK result;
5792     return result;
5793 }
5794
5795 static int autoQueen; // [HGM] oneclick
5796
5797 int
5798 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5799 {
5800     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5801     /* [HGM] add Shogi promotions */
5802     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5803     ChessSquare piece;
5804     ChessMove moveType;
5805     Boolean premove;
5806
5807     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5808     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5809
5810     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5811       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5812         return FALSE;
5813
5814     piece = boards[currentMove][fromY][fromX];
5815     if(gameInfo.variant == VariantShogi) {
5816         promotionZoneSize = BOARD_HEIGHT/3;
5817         highestPromotingPiece = (int)WhiteFerz;
5818     } else if(gameInfo.variant == VariantMakruk) {
5819         promotionZoneSize = 3;
5820     }
5821
5822     // Treat Lance as Pawn when it is not representing Amazon
5823     if(gameInfo.variant != VariantSuper) {
5824         if(piece == WhiteLance) piece = WhitePawn; else
5825         if(piece == BlackLance) piece = BlackPawn;
5826     }
5827
5828     // next weed out all moves that do not touch the promotion zone at all
5829     if((int)piece >= BlackPawn) {
5830         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5831              return FALSE;
5832         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5833     } else {
5834         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5835            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5836     }
5837
5838     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5839
5840     // weed out mandatory Shogi promotions
5841     if(gameInfo.variant == VariantShogi) {
5842         if(piece >= BlackPawn) {
5843             if(toY == 0 && piece == BlackPawn ||
5844                toY == 0 && piece == BlackQueen ||
5845                toY <= 1 && piece == BlackKnight) {
5846                 *promoChoice = '+';
5847                 return FALSE;
5848             }
5849         } else {
5850             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5851                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5852                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5853                 *promoChoice = '+';
5854                 return FALSE;
5855             }
5856         }
5857     }
5858
5859     // weed out obviously illegal Pawn moves
5860     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5861         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5862         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5863         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5864         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5865         // note we are not allowed to test for valid (non-)capture, due to premove
5866     }
5867
5868     // we either have a choice what to promote to, or (in Shogi) whether to promote
5869     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5870         *promoChoice = PieceToChar(BlackFerz);  // no choice
5871         return FALSE;
5872     }
5873     // no sense asking what we must promote to if it is going to explode...
5874     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5875         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5876         return FALSE;
5877     }
5878     // give caller the default choice even if we will not make it
5879     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5880     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5881     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5882                            && gameInfo.variant != VariantShogi
5883                            && gameInfo.variant != VariantSuper) return FALSE;
5884     if(autoQueen) return FALSE; // predetermined
5885
5886     // suppress promotion popup on illegal moves that are not premoves
5887     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5888               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5889     if(appData.testLegality && !premove) {
5890         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5891                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5892         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5893             return FALSE;
5894     }
5895
5896     return TRUE;
5897 }
5898
5899 int
5900 InPalace(row, column)
5901      int row, column;
5902 {   /* [HGM] for Xiangqi */
5903     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5904          column < (BOARD_WIDTH + 4)/2 &&
5905          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5906     return FALSE;
5907 }
5908
5909 int
5910 PieceForSquare (x, y)
5911      int x;
5912      int y;
5913 {
5914   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5915      return -1;
5916   else
5917      return boards[currentMove][y][x];
5918 }
5919
5920 int
5921 OKToStartUserMove(x, y)
5922      int x, y;
5923 {
5924     ChessSquare from_piece;
5925     int white_piece;
5926
5927     if (matchMode) return FALSE;
5928     if (gameMode == EditPosition) return TRUE;
5929
5930     if (x >= 0 && y >= 0)
5931       from_piece = boards[currentMove][y][x];
5932     else
5933       from_piece = EmptySquare;
5934
5935     if (from_piece == EmptySquare) return FALSE;
5936
5937     white_piece = (int)from_piece >= (int)WhitePawn &&
5938       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5939
5940     switch (gameMode) {
5941       case PlayFromGameFile:
5942       case AnalyzeFile:
5943       case TwoMachinesPlay:
5944       case EndOfGame:
5945         return FALSE;
5946
5947       case IcsObserving:
5948       case IcsIdle:
5949         return FALSE;
5950
5951       case MachinePlaysWhite:
5952       case IcsPlayingBlack:
5953         if (appData.zippyPlay) return FALSE;
5954         if (white_piece) {
5955             DisplayMoveError(_("You are playing Black"));
5956             return FALSE;
5957         }
5958         break;
5959
5960       case MachinePlaysBlack:
5961       case IcsPlayingWhite:
5962         if (appData.zippyPlay) return FALSE;
5963         if (!white_piece) {
5964             DisplayMoveError(_("You are playing White"));
5965             return FALSE;
5966         }
5967         break;
5968
5969       case EditGame:
5970         if (!white_piece && WhiteOnMove(currentMove)) {
5971             DisplayMoveError(_("It is White's turn"));
5972             return FALSE;
5973         }
5974         if (white_piece && !WhiteOnMove(currentMove)) {
5975             DisplayMoveError(_("It is Black's turn"));
5976             return FALSE;
5977         }
5978         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5979             /* Editing correspondence game history */
5980             /* Could disallow this or prompt for confirmation */
5981             cmailOldMove = -1;
5982         }
5983         break;
5984
5985       case BeginningOfGame:
5986         if (appData.icsActive) return FALSE;
5987         if (!appData.noChessProgram) {
5988             if (!white_piece) {
5989                 DisplayMoveError(_("You are playing White"));
5990                 return FALSE;
5991             }
5992         }
5993         break;
5994
5995       case Training:
5996         if (!white_piece && WhiteOnMove(currentMove)) {
5997             DisplayMoveError(_("It is White's turn"));
5998             return FALSE;
5999         }
6000         if (white_piece && !WhiteOnMove(currentMove)) {
6001             DisplayMoveError(_("It is Black's turn"));
6002             return FALSE;
6003         }
6004         break;
6005
6006       default:
6007       case IcsExamining:
6008         break;
6009     }
6010     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6011         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6012         && gameMode != AnalyzeFile && gameMode != Training) {
6013         DisplayMoveError(_("Displayed position is not current"));
6014         return FALSE;
6015     }
6016     return TRUE;
6017 }
6018
6019 Boolean
6020 OnlyMove(int *x, int *y, Boolean captures) {
6021     DisambiguateClosure cl;
6022     if (appData.zippyPlay) return FALSE;
6023     switch(gameMode) {
6024       case MachinePlaysBlack:
6025       case IcsPlayingWhite:
6026       case BeginningOfGame:
6027         if(!WhiteOnMove(currentMove)) return FALSE;
6028         break;
6029       case MachinePlaysWhite:
6030       case IcsPlayingBlack:
6031         if(WhiteOnMove(currentMove)) return FALSE;
6032         break;
6033       case EditGame:
6034         break;
6035       default:
6036         return FALSE;
6037     }
6038     cl.pieceIn = EmptySquare;
6039     cl.rfIn = *y;
6040     cl.ffIn = *x;
6041     cl.rtIn = -1;
6042     cl.ftIn = -1;
6043     cl.promoCharIn = NULLCHAR;
6044     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6045     if( cl.kind == NormalMove ||
6046         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6047         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6048         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6049       fromX = cl.ff;
6050       fromY = cl.rf;
6051       *x = cl.ft;
6052       *y = cl.rt;
6053       return TRUE;
6054     }
6055     if(cl.kind != ImpossibleMove) return FALSE;
6056     cl.pieceIn = EmptySquare;
6057     cl.rfIn = -1;
6058     cl.ffIn = -1;
6059     cl.rtIn = *y;
6060     cl.ftIn = *x;
6061     cl.promoCharIn = NULLCHAR;
6062     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6063     if( cl.kind == NormalMove ||
6064         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6065         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6066         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6067       fromX = cl.ff;
6068       fromY = cl.rf;
6069       *x = cl.ft;
6070       *y = cl.rt;
6071       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6072       return TRUE;
6073     }
6074     return FALSE;
6075 }
6076
6077 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6078 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6079 int lastLoadGameUseList = FALSE;
6080 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6081 ChessMove lastLoadGameStart = EndOfFile;
6082
6083 void
6084 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6085      int fromX, fromY, toX, toY;
6086      int promoChar;
6087 {
6088     ChessMove moveType;
6089     ChessSquare pdown, pup;
6090
6091     /* Check if the user is playing in turn.  This is complicated because we
6092        let the user "pick up" a piece before it is his turn.  So the piece he
6093        tried to pick up may have been captured by the time he puts it down!
6094        Therefore we use the color the user is supposed to be playing in this
6095        test, not the color of the piece that is currently on the starting
6096        square---except in EditGame mode, where the user is playing both
6097        sides; fortunately there the capture race can't happen.  (It can
6098        now happen in IcsExamining mode, but that's just too bad.  The user
6099        will get a somewhat confusing message in that case.)
6100        */
6101
6102     switch (gameMode) {
6103       case PlayFromGameFile:
6104       case AnalyzeFile:
6105       case TwoMachinesPlay:
6106       case EndOfGame:
6107       case IcsObserving:
6108       case IcsIdle:
6109         /* We switched into a game mode where moves are not accepted,
6110            perhaps while the mouse button was down. */
6111         return;
6112
6113       case MachinePlaysWhite:
6114         /* User is moving for Black */
6115         if (WhiteOnMove(currentMove)) {
6116             DisplayMoveError(_("It is White's turn"));
6117             return;
6118         }
6119         break;
6120
6121       case MachinePlaysBlack:
6122         /* User is moving for White */
6123         if (!WhiteOnMove(currentMove)) {
6124             DisplayMoveError(_("It is Black's turn"));
6125             return;
6126         }
6127         break;
6128
6129       case EditGame:
6130       case IcsExamining:
6131       case BeginningOfGame:
6132       case AnalyzeMode:
6133       case Training:
6134         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6135         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6136             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6137             /* User is moving for Black */
6138             if (WhiteOnMove(currentMove)) {
6139                 DisplayMoveError(_("It is White's turn"));
6140                 return;
6141             }
6142         } else {
6143             /* User is moving for White */
6144             if (!WhiteOnMove(currentMove)) {
6145                 DisplayMoveError(_("It is Black's turn"));
6146                 return;
6147             }
6148         }
6149         break;
6150
6151       case IcsPlayingBlack:
6152         /* User is moving for Black */
6153         if (WhiteOnMove(currentMove)) {
6154             if (!appData.premove) {
6155                 DisplayMoveError(_("It is White's turn"));
6156             } else if (toX >= 0 && toY >= 0) {
6157                 premoveToX = toX;
6158                 premoveToY = toY;
6159                 premoveFromX = fromX;
6160                 premoveFromY = fromY;
6161                 premovePromoChar = promoChar;
6162                 gotPremove = 1;
6163                 if (appData.debugMode)
6164                     fprintf(debugFP, "Got premove: fromX %d,"
6165                             "fromY %d, toX %d, toY %d\n",
6166                             fromX, fromY, toX, toY);
6167             }
6168             return;
6169         }
6170         break;
6171
6172       case IcsPlayingWhite:
6173         /* User is moving for White */
6174         if (!WhiteOnMove(currentMove)) {
6175             if (!appData.premove) {
6176                 DisplayMoveError(_("It is Black's turn"));
6177             } else if (toX >= 0 && toY >= 0) {
6178                 premoveToX = toX;
6179                 premoveToY = toY;
6180                 premoveFromX = fromX;
6181                 premoveFromY = fromY;
6182                 premovePromoChar = promoChar;
6183                 gotPremove = 1;
6184                 if (appData.debugMode)
6185                     fprintf(debugFP, "Got premove: fromX %d,"
6186                             "fromY %d, toX %d, toY %d\n",
6187                             fromX, fromY, toX, toY);
6188             }
6189             return;
6190         }
6191         break;
6192
6193       default:
6194         break;
6195
6196       case EditPosition:
6197         /* EditPosition, empty square, or different color piece;
6198            click-click move is possible */
6199         if (toX == -2 || toY == -2) {
6200             boards[0][fromY][fromX] = EmptySquare;
6201             DrawPosition(FALSE, boards[currentMove]);
6202             return;
6203         } else if (toX >= 0 && toY >= 0) {
6204             boards[0][toY][toX] = boards[0][fromY][fromX];
6205             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6206                 if(boards[0][fromY][0] != EmptySquare) {
6207                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6208                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6209                 }
6210             } else
6211             if(fromX == BOARD_RGHT+1) {
6212                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6213                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6214                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6215                 }
6216             } else
6217             boards[0][fromY][fromX] = EmptySquare;
6218             DrawPosition(FALSE, boards[currentMove]);
6219             return;
6220         }
6221         return;
6222     }
6223
6224     if(toX < 0 || toY < 0) return;
6225     pdown = boards[currentMove][fromY][fromX];
6226     pup = boards[currentMove][toY][toX];
6227
6228     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6229     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6230          if( pup != EmptySquare ) return;
6231          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6232            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6233                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6234            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6235            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6236            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6237            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6238          fromY = DROP_RANK;
6239     }
6240
6241     /* [HGM] always test for legality, to get promotion info */
6242     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6243                                          fromY, fromX, toY, toX, promoChar);
6244     /* [HGM] but possibly ignore an IllegalMove result */
6245     if (appData.testLegality) {
6246         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6247             DisplayMoveError(_("Illegal move"));
6248             return;
6249         }
6250     }
6251
6252     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6253 }
6254
6255 /* Common tail of UserMoveEvent and DropMenuEvent */
6256 int
6257 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6258      ChessMove moveType;
6259      int fromX, fromY, toX, toY;
6260      /*char*/int promoChar;
6261 {
6262     char *bookHit = 0;
6263
6264     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6265         // [HGM] superchess: suppress promotions to non-available piece
6266         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6267         if(WhiteOnMove(currentMove)) {
6268             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6269         } else {
6270             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6271         }
6272     }
6273
6274     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6275        move type in caller when we know the move is a legal promotion */
6276     if(moveType == NormalMove && promoChar)
6277         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6278
6279     /* [HGM] <popupFix> The following if has been moved here from
6280        UserMoveEvent(). Because it seemed to belong here (why not allow
6281        piece drops in training games?), and because it can only be
6282        performed after it is known to what we promote. */
6283     if (gameMode == Training) {
6284       /* compare the move played on the board to the next move in the
6285        * game. If they match, display the move and the opponent's response.
6286        * If they don't match, display an error message.
6287        */
6288       int saveAnimate;
6289       Board testBoard;
6290       CopyBoard(testBoard, boards[currentMove]);
6291       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6292
6293       if (CompareBoards(testBoard, boards[currentMove+1])) {
6294         ForwardInner(currentMove+1);
6295
6296         /* Autoplay the opponent's response.
6297          * if appData.animate was TRUE when Training mode was entered,
6298          * the response will be animated.
6299          */
6300         saveAnimate = appData.animate;
6301         appData.animate = animateTraining;
6302         ForwardInner(currentMove+1);
6303         appData.animate = saveAnimate;
6304
6305         /* check for the end of the game */
6306         if (currentMove >= forwardMostMove) {
6307           gameMode = PlayFromGameFile;
6308           ModeHighlight();
6309           SetTrainingModeOff();
6310           DisplayInformation(_("End of game"));
6311         }
6312       } else {
6313         DisplayError(_("Incorrect move"), 0);
6314       }
6315       return 1;
6316     }
6317
6318   /* Ok, now we know that the move is good, so we can kill
6319      the previous line in Analysis Mode */
6320   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6321                                 && currentMove < forwardMostMove) {
6322     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6323     else forwardMostMove = currentMove;
6324   }
6325
6326   /* If we need the chess program but it's dead, restart it */
6327   ResurrectChessProgram();
6328
6329   /* A user move restarts a paused game*/
6330   if (pausing)
6331     PauseEvent();
6332
6333   thinkOutput[0] = NULLCHAR;
6334
6335   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6336
6337   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6338     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6339     return 1;
6340   }
6341
6342   if (gameMode == BeginningOfGame) {
6343     if (appData.noChessProgram) {
6344       gameMode = EditGame;
6345       SetGameInfo();
6346     } else {
6347       char buf[MSG_SIZ];
6348       gameMode = MachinePlaysBlack;
6349       StartClocks();
6350       SetGameInfo();
6351       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6352       DisplayTitle(buf);
6353       if (first.sendName) {
6354         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6355         SendToProgram(buf, &first);
6356       }
6357       StartClocks();
6358     }
6359     ModeHighlight();
6360   }
6361
6362   /* Relay move to ICS or chess engine */
6363   if (appData.icsActive) {
6364     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6365         gameMode == IcsExamining) {
6366       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6367         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6368         SendToICS("draw ");
6369         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6370       }
6371       // also send plain move, in case ICS does not understand atomic claims
6372       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6373       ics_user_moved = 1;
6374     }
6375   } else {
6376     if (first.sendTime && (gameMode == BeginningOfGame ||
6377                            gameMode == MachinePlaysWhite ||
6378                            gameMode == MachinePlaysBlack)) {
6379       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6380     }
6381     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6382          // [HGM] book: if program might be playing, let it use book
6383         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6384         first.maybeThinking = TRUE;
6385     } else SendMoveToProgram(forwardMostMove-1, &first);
6386     if (currentMove == cmailOldMove + 1) {
6387       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6388     }
6389   }
6390
6391   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6392
6393   switch (gameMode) {
6394   case EditGame:
6395     if(appData.testLegality)
6396     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6397     case MT_NONE:
6398     case MT_CHECK:
6399       break;
6400     case MT_CHECKMATE:
6401     case MT_STAINMATE:
6402       if (WhiteOnMove(currentMove)) {
6403         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6404       } else {
6405         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6406       }
6407       break;
6408     case MT_STALEMATE:
6409       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6410       break;
6411     }
6412     break;
6413
6414   case MachinePlaysBlack:
6415   case MachinePlaysWhite:
6416     /* disable certain menu options while machine is thinking */
6417     SetMachineThinkingEnables();
6418     break;
6419
6420   default:
6421     break;
6422   }
6423
6424   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6425   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6426
6427   if(bookHit) { // [HGM] book: simulate book reply
6428         static char bookMove[MSG_SIZ]; // a bit generous?
6429
6430         programStats.nodes = programStats.depth = programStats.time =
6431         programStats.score = programStats.got_only_move = 0;
6432         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6433
6434         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6435         strcat(bookMove, bookHit);
6436         HandleMachineMove(bookMove, &first);
6437   }
6438   return 1;
6439 }
6440
6441 void
6442 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6443      Board board;
6444      int flags;
6445      ChessMove kind;
6446      int rf, ff, rt, ft;
6447      VOIDSTAR closure;
6448 {
6449     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6450     Markers *m = (Markers *) closure;
6451     if(rf == fromY && ff == fromX)
6452         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6453                          || kind == WhiteCapturesEnPassant
6454                          || kind == BlackCapturesEnPassant);
6455     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6456 }
6457
6458 void
6459 MarkTargetSquares(int clear)
6460 {
6461   int x, y;
6462   if(!appData.markers || !appData.highlightDragging ||
6463      !appData.testLegality || gameMode == EditPosition) return;
6464   if(clear) {
6465     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6466   } else {
6467     int capt = 0;
6468     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6469     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6470       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6471       if(capt)
6472       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6473     }
6474   }
6475   DrawPosition(TRUE, NULL);
6476 }
6477
6478 int
6479 Explode(Board board, int fromX, int fromY, int toX, int toY)
6480 {
6481     if(gameInfo.variant == VariantAtomic &&
6482        (board[toY][toX] != EmptySquare ||                     // capture?
6483         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6484                          board[fromY][fromX] == BlackPawn   )
6485       )) {
6486         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6487         return TRUE;
6488     }
6489     return FALSE;
6490 }
6491
6492 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6493
6494 int CanPromote(ChessSquare piece, int y)
6495 {
6496         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6497         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6498         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6499            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6500            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6501                                                   gameInfo.variant == VariantMakruk) return FALSE;
6502         return (piece == BlackPawn && y == 1 ||
6503                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6504                 piece == BlackLance && y == 1 ||
6505                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6506 }
6507
6508 void LeftClick(ClickType clickType, int xPix, int yPix)
6509 {
6510     int x, y;
6511     Boolean saveAnimate;
6512     static int second = 0, promotionChoice = 0, clearFlag = 0;
6513     char promoChoice = NULLCHAR;
6514     ChessSquare piece;
6515
6516     if(appData.seekGraph && appData.icsActive && loggedOn &&
6517         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6518         SeekGraphClick(clickType, xPix, yPix, 0);
6519         return;
6520     }
6521
6522     if (clickType == Press) ErrorPopDown();
6523     MarkTargetSquares(1);
6524
6525     x = EventToSquare(xPix, BOARD_WIDTH);
6526     y = EventToSquare(yPix, BOARD_HEIGHT);
6527     if (!flipView && y >= 0) {
6528         y = BOARD_HEIGHT - 1 - y;
6529     }
6530     if (flipView && x >= 0) {
6531         x = BOARD_WIDTH - 1 - x;
6532     }
6533
6534     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6535         defaultPromoChoice = promoSweep;
6536         promoSweep = EmptySquare;   // terminate sweep
6537         promoDefaultAltered = TRUE;
6538         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6539     }
6540
6541     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6542         if(clickType == Release) return; // ignore upclick of click-click destination
6543         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6544         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6545         if(gameInfo.holdingsWidth &&
6546                 (WhiteOnMove(currentMove)
6547                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6548                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6549             // click in right holdings, for determining promotion piece
6550             ChessSquare p = boards[currentMove][y][x];
6551             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6552             if(p != EmptySquare) {
6553                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6554                 fromX = fromY = -1;
6555                 return;
6556             }
6557         }
6558         DrawPosition(FALSE, boards[currentMove]);
6559         return;
6560     }
6561
6562     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6563     if(clickType == Press
6564             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6565               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6566               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6567         return;
6568
6569     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6570         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6571
6572     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6573         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6574                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6575         defaultPromoChoice = DefaultPromoChoice(side);
6576     }
6577
6578     autoQueen = appData.alwaysPromoteToQueen;
6579
6580     if (fromX == -1) {
6581       int originalY = y;
6582       gatingPiece = EmptySquare;
6583       if (clickType != Press) {
6584         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6585             DragPieceEnd(xPix, yPix); dragging = 0;
6586             DrawPosition(FALSE, NULL);
6587         }
6588         return;
6589       }
6590       fromX = x; fromY = y;
6591       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6592          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6593          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6594             /* First square */
6595             if (OKToStartUserMove(fromX, fromY)) {
6596                 second = 0;
6597                 MarkTargetSquares(0);
6598                 DragPieceBegin(xPix, yPix); dragging = 1;
6599                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6600                     promoSweep = defaultPromoChoice;
6601                     selectFlag = 0; lastX = xPix; lastY = yPix;
6602                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6603                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6604                 }
6605                 if (appData.highlightDragging) {
6606                     SetHighlights(fromX, fromY, -1, -1);
6607                 }
6608             } else fromX = fromY = -1;
6609             return;
6610         }
6611     }
6612
6613     /* fromX != -1 */
6614     if (clickType == Press && gameMode != EditPosition) {
6615         ChessSquare fromP;
6616         ChessSquare toP;
6617         int frc;
6618
6619         // ignore off-board to clicks
6620         if(y < 0 || x < 0) return;
6621
6622         /* Check if clicking again on the same color piece */
6623         fromP = boards[currentMove][fromY][fromX];
6624         toP = boards[currentMove][y][x];
6625         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6626         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6627              WhitePawn <= toP && toP <= WhiteKing &&
6628              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6629              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6630             (BlackPawn <= fromP && fromP <= BlackKing &&
6631              BlackPawn <= toP && toP <= BlackKing &&
6632              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6633              !(fromP == BlackKing && toP == BlackRook && frc))) {
6634             /* Clicked again on same color piece -- changed his mind */
6635             second = (x == fromX && y == fromY);
6636             promoDefaultAltered = FALSE;
6637            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6638             if (appData.highlightDragging) {
6639                 SetHighlights(x, y, -1, -1);
6640             } else {
6641                 ClearHighlights();
6642             }
6643             if (OKToStartUserMove(x, y)) {
6644                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6645                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6646                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6647                  gatingPiece = boards[currentMove][fromY][fromX];
6648                 else gatingPiece = EmptySquare;
6649                 fromX = x;
6650                 fromY = y; dragging = 1;
6651                 MarkTargetSquares(0);
6652                 DragPieceBegin(xPix, yPix);
6653                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6654                     promoSweep = defaultPromoChoice;
6655                     selectFlag = 0; lastX = xPix; lastY = yPix;
6656                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6657                 }
6658             }
6659            }
6660            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6661            second = FALSE; 
6662         }
6663         // ignore clicks on holdings
6664         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6665     }
6666
6667     if (clickType == Release && x == fromX && y == fromY) {
6668         DragPieceEnd(xPix, yPix); dragging = 0;
6669         if(clearFlag) {
6670             // a deferred attempt to click-click move an empty square on top of a piece
6671             boards[currentMove][y][x] = EmptySquare;
6672             ClearHighlights();
6673             DrawPosition(FALSE, boards[currentMove]);
6674             fromX = fromY = -1; clearFlag = 0;
6675             return;
6676         }
6677         if (appData.animateDragging) {
6678             /* Undo animation damage if any */
6679             DrawPosition(FALSE, NULL);
6680         }
6681         if (second) {
6682             /* Second up/down in same square; just abort move */
6683             second = 0;
6684             fromX = fromY = -1;
6685             gatingPiece = EmptySquare;
6686             ClearHighlights();
6687             gotPremove = 0;
6688             ClearPremoveHighlights();
6689         } else {
6690             /* First upclick in same square; start click-click mode */
6691             SetHighlights(x, y, -1, -1);
6692         }
6693         return;
6694     }
6695
6696     clearFlag = 0;
6697
6698     /* we now have a different from- and (possibly off-board) to-square */
6699     /* Completed move */
6700     toX = x;
6701     toY = y;
6702     saveAnimate = appData.animate;
6703     if (clickType == Press) {
6704         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6705             // must be Edit Position mode with empty-square selected
6706             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6707             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6708             return;
6709         }
6710         /* Finish clickclick move */
6711         if (appData.animate || appData.highlightLastMove) {
6712             SetHighlights(fromX, fromY, toX, toY);
6713         } else {
6714             ClearHighlights();
6715         }
6716     } else {
6717         /* Finish drag move */
6718         if (appData.highlightLastMove) {
6719             SetHighlights(fromX, fromY, toX, toY);
6720         } else {
6721             ClearHighlights();
6722         }
6723         DragPieceEnd(xPix, yPix); dragging = 0;
6724         /* Don't animate move and drag both */
6725         appData.animate = FALSE;
6726     }
6727
6728     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6729     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6730         ChessSquare piece = boards[currentMove][fromY][fromX];
6731         if(gameMode == EditPosition && piece != EmptySquare &&
6732            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6733             int n;
6734
6735             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6736                 n = PieceToNumber(piece - (int)BlackPawn);
6737                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6738                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6739                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6740             } else
6741             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6742                 n = PieceToNumber(piece);
6743                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6744                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6745                 boards[currentMove][n][BOARD_WIDTH-2]++;
6746             }
6747             boards[currentMove][fromY][fromX] = EmptySquare;
6748         }
6749         ClearHighlights();
6750         fromX = fromY = -1;
6751         DrawPosition(TRUE, boards[currentMove]);
6752         return;
6753     }
6754
6755     // off-board moves should not be highlighted
6756     if(x < 0 || y < 0) ClearHighlights();
6757
6758     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6759
6760     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6761         SetHighlights(fromX, fromY, toX, toY);
6762         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6763             // [HGM] super: promotion to captured piece selected from holdings
6764             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6765             promotionChoice = TRUE;
6766             // kludge follows to temporarily execute move on display, without promoting yet
6767             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6768             boards[currentMove][toY][toX] = p;
6769             DrawPosition(FALSE, boards[currentMove]);
6770             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6771             boards[currentMove][toY][toX] = q;
6772             DisplayMessage("Click in holdings to choose piece", "");
6773             return;
6774         }
6775         PromotionPopUp();
6776     } else {
6777         int oldMove = currentMove;
6778         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6779         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6780         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6781         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6782            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6783             DrawPosition(TRUE, boards[currentMove]);
6784         fromX = fromY = -1;
6785     }
6786     appData.animate = saveAnimate;
6787     if (appData.animate || appData.animateDragging) {
6788         /* Undo animation damage if needed */
6789         DrawPosition(FALSE, NULL);
6790     }
6791 }
6792
6793 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6794 {   // front-end-free part taken out of PieceMenuPopup
6795     int whichMenu; int xSqr, ySqr;
6796
6797     if(seekGraphUp) { // [HGM] seekgraph
6798         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6799         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6800         return -2;
6801     }
6802
6803     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6804          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6805         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6806         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6807         if(action == Press)   {
6808             originalFlip = flipView;
6809             flipView = !flipView; // temporarily flip board to see game from partners perspective
6810             DrawPosition(TRUE, partnerBoard);
6811             DisplayMessage(partnerStatus, "");
6812             partnerUp = TRUE;
6813         } else if(action == Release) {
6814             flipView = originalFlip;
6815             DrawPosition(TRUE, boards[currentMove]);
6816             partnerUp = FALSE;
6817         }
6818         return -2;
6819     }
6820
6821     xSqr = EventToSquare(x, BOARD_WIDTH);
6822     ySqr = EventToSquare(y, BOARD_HEIGHT);
6823     if (action == Release) {
6824         if(pieceSweep != EmptySquare) {
6825             EditPositionMenuEvent(pieceSweep, toX, toY);
6826             pieceSweep = EmptySquare;
6827         } else UnLoadPV(); // [HGM] pv
6828     }
6829     if (action != Press) return -2; // return code to be ignored
6830     switch (gameMode) {
6831       case IcsExamining:
6832         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6833       case EditPosition:
6834         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6835         if (xSqr < 0 || ySqr < 0) return -1;
6836         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6837         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6838         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6839         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6840         NextPiece(0);
6841         return -2;\r
6842       case IcsObserving:
6843         if(!appData.icsEngineAnalyze) return -1;
6844       case IcsPlayingWhite:
6845       case IcsPlayingBlack:
6846         if(!appData.zippyPlay) goto noZip;
6847       case AnalyzeMode:
6848       case AnalyzeFile:
6849       case MachinePlaysWhite:
6850       case MachinePlaysBlack:
6851       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6852         if (!appData.dropMenu) {
6853           LoadPV(x, y);
6854           return 2; // flag front-end to grab mouse events
6855         }
6856         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6857            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6858       case EditGame:
6859       noZip:
6860         if (xSqr < 0 || ySqr < 0) return -1;
6861         if (!appData.dropMenu || appData.testLegality &&
6862             gameInfo.variant != VariantBughouse &&
6863             gameInfo.variant != VariantCrazyhouse) return -1;
6864         whichMenu = 1; // drop menu
6865         break;
6866       default:
6867         return -1;
6868     }
6869
6870     if (((*fromX = xSqr) < 0) ||
6871         ((*fromY = ySqr) < 0)) {
6872         *fromX = *fromY = -1;
6873         return -1;
6874     }
6875     if (flipView)
6876       *fromX = BOARD_WIDTH - 1 - *fromX;
6877     else
6878       *fromY = BOARD_HEIGHT - 1 - *fromY;
6879
6880     return whichMenu;
6881 }
6882
6883 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6884 {
6885 //    char * hint = lastHint;
6886     FrontEndProgramStats stats;
6887
6888     stats.which = cps == &first ? 0 : 1;
6889     stats.depth = cpstats->depth;
6890     stats.nodes = cpstats->nodes;
6891     stats.score = cpstats->score;
6892     stats.time = cpstats->time;
6893     stats.pv = cpstats->movelist;
6894     stats.hint = lastHint;
6895     stats.an_move_index = 0;
6896     stats.an_move_count = 0;
6897
6898     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6899         stats.hint = cpstats->move_name;
6900         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6901         stats.an_move_count = cpstats->nr_moves;
6902     }
6903
6904     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
6905
6906     SetProgramStats( &stats );
6907 }
6908
6909 void
6910 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6911 {       // count all piece types
6912         int p, f, r;
6913         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6914         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6915         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6916                 p = board[r][f];
6917                 pCnt[p]++;
6918                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6919                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6920                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6921                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6922                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6923                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6924         }
6925 }
6926
6927 int
6928 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6929 {
6930         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6931         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6932
6933         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6934         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6935         if(myPawns == 2 && nMine == 3) // KPP
6936             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6937         if(myPawns == 1 && nMine == 2) // KP
6938             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6939         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6940             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6941         if(myPawns) return FALSE;
6942         if(pCnt[WhiteRook+side])
6943             return pCnt[BlackRook-side] ||
6944                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6945                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6946                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6947         if(pCnt[WhiteCannon+side]) {
6948             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6949             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6950         }
6951         if(pCnt[WhiteKnight+side])
6952             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6953         return FALSE;
6954 }
6955
6956 int
6957 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6958 {
6959         VariantClass v = gameInfo.variant;
6960
6961         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6962         if(v == VariantShatranj) return TRUE; // always winnable through baring
6963         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6964         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6965
6966         if(v == VariantXiangqi) {
6967                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6968
6969                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6970                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6971                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6972                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6973                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6974                 if(stale) // we have at least one last-rank P plus perhaps C
6975                     return majors // KPKX
6976                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6977                 else // KCA*E*
6978                     return pCnt[WhiteFerz+side] // KCAK
6979                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6980                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6981                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6982
6983         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6984                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6985
6986                 if(nMine == 1) return FALSE; // bare King
6987                 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
6988                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6989                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6990                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6991                 if(pCnt[WhiteKnight+side])
6992                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6993                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6994                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6995                 if(nBishops)
6996                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6997                 if(pCnt[WhiteAlfil+side])
6998                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6999                 if(pCnt[WhiteWazir+side])
7000                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7001         }
7002
7003         return TRUE;
7004 }
7005
7006 int
7007 Adjudicate(ChessProgramState *cps)
7008 {       // [HGM] some adjudications useful with buggy engines
7009         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7010         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7011         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7012         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7013         int k, count = 0; static int bare = 1;
7014         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7015         Boolean canAdjudicate = !appData.icsActive;
7016
7017         // most tests only when we understand the game, i.e. legality-checking on
7018             if( appData.testLegality )
7019             {   /* [HGM] Some more adjudications for obstinate engines */
7020                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7021                 static int moveCount = 6;
7022                 ChessMove result;
7023                 char *reason = NULL;
7024
7025                 /* Count what is on board. */
7026                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7027
7028                 /* Some material-based adjudications that have to be made before stalemate test */
7029                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7030                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7031                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7032                      if(canAdjudicate && appData.checkMates) {
7033                          if(engineOpponent)
7034                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7035                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7036                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7037                          return 1;
7038                      }
7039                 }
7040
7041                 /* Bare King in Shatranj (loses) or Losers (wins) */
7042                 if( nrW == 1 || nrB == 1) {
7043                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7044                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7045                      if(canAdjudicate && appData.checkMates) {
7046                          if(engineOpponent)
7047                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7048                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7049                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7050                          return 1;
7051                      }
7052                   } else
7053                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7054                   {    /* bare King */
7055                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7056                         if(canAdjudicate && appData.checkMates) {
7057                             /* but only adjudicate if adjudication enabled */
7058                             if(engineOpponent)
7059                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7060                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7061                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7062                             return 1;
7063                         }
7064                   }
7065                 } else bare = 1;
7066
7067
7068             // don't wait for engine to announce game end if we can judge ourselves
7069             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7070               case MT_CHECK:
7071                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7072                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7073                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7074                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7075                             checkCnt++;
7076                         if(checkCnt >= 2) {
7077                             reason = "Xboard adjudication: 3rd check";
7078                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7079                             break;
7080                         }
7081                     }
7082                 }
7083               case MT_NONE:
7084               default:
7085                 break;
7086               case MT_STALEMATE:
7087               case MT_STAINMATE:
7088                 reason = "Xboard adjudication: Stalemate";
7089                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7090                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7091                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7092                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7093                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7094                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7095                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7096                                                                         EP_CHECKMATE : EP_WINS);
7097                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7098                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7099                 }
7100                 break;
7101               case MT_CHECKMATE:
7102                 reason = "Xboard adjudication: Checkmate";
7103                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7104                 break;
7105             }
7106
7107                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7108                     case EP_STALEMATE:
7109                         result = GameIsDrawn; break;
7110                     case EP_CHECKMATE:
7111                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7112                     case EP_WINS:
7113                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7114                     default:
7115                         result = EndOfFile;
7116                 }
7117                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7118                     if(engineOpponent)
7119                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7120                     GameEnds( result, reason, GE_XBOARD );
7121                     return 1;
7122                 }
7123
7124                 /* Next absolutely insufficient mating material. */
7125                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7126                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7127                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7128
7129                      /* always flag draws, for judging claims */
7130                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7131
7132                      if(canAdjudicate && appData.materialDraws) {
7133                          /* but only adjudicate them if adjudication enabled */
7134                          if(engineOpponent) {
7135                            SendToProgram("force\n", engineOpponent); // suppress reply
7136                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7137                          }
7138                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7139                          return 1;
7140                      }
7141                 }
7142
7143                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7144                 if(gameInfo.variant == VariantXiangqi ?
7145                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7146                  : nrW + nrB == 4 &&
7147                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7148                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7149                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7150                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7151                    ) ) {
7152                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7153                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7154                           if(engineOpponent) {
7155                             SendToProgram("force\n", engineOpponent); // suppress reply
7156                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7157                           }
7158                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7159                           return 1;
7160                      }
7161                 } else moveCount = 6;
7162             }
7163         if (appData.debugMode) { int i;
7164             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7165                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7166                     appData.drawRepeats);
7167             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7168               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7169
7170         }
7171
7172         // Repetition draws and 50-move rule can be applied independently of legality testing
7173
7174                 /* Check for rep-draws */
7175                 count = 0;
7176                 for(k = forwardMostMove-2;
7177                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7178                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7179                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7180                     k-=2)
7181                 {   int rights=0;
7182                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7183                         /* compare castling rights */
7184                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7185                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7186                                 rights++; /* King lost rights, while rook still had them */
7187                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7188                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7189                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7190                                    rights++; /* but at least one rook lost them */
7191                         }
7192                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7193                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7194                                 rights++;
7195                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7196                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7197                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7198                                    rights++;
7199                         }
7200                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7201                             && appData.drawRepeats > 1) {
7202                              /* adjudicate after user-specified nr of repeats */
7203                              int result = GameIsDrawn;
7204                              char *details = "XBoard adjudication: repetition draw";
7205                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7206                                 // [HGM] xiangqi: check for forbidden perpetuals
7207                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7208                                 for(m=forwardMostMove; m>k; m-=2) {
7209                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7210                                         ourPerpetual = 0; // the current mover did not always check
7211                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7212                                         hisPerpetual = 0; // the opponent did not always check
7213                                 }
7214                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7215                                                                         ourPerpetual, hisPerpetual);
7216                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7217                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7218                                     details = "Xboard adjudication: perpetual checking";
7219                                 } else
7220                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7221                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7222                                 } else
7223                                 // Now check for perpetual chases
7224                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7225                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7226                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7227                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7228                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7229                                         details = "Xboard adjudication: perpetual chasing";
7230                                     } else
7231                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7232                                         break; // Abort repetition-checking loop.
7233                                 }
7234                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7235                              }
7236                              if(engineOpponent) {
7237                                SendToProgram("force\n", engineOpponent); // suppress reply
7238                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7239                              }
7240                              GameEnds( result, details, GE_XBOARD );
7241                              return 1;
7242                         }
7243                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7244                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7245                     }
7246                 }
7247
7248                 /* Now we test for 50-move draws. Determine ply count */
7249                 count = forwardMostMove;
7250                 /* look for last irreversble move */
7251                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7252                     count--;
7253                 /* if we hit starting position, add initial plies */
7254                 if( count == backwardMostMove )
7255                     count -= initialRulePlies;
7256                 count = forwardMostMove - count;
7257                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7258                         // adjust reversible move counter for checks in Xiangqi
7259                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7260                         if(i < backwardMostMove) i = backwardMostMove;
7261                         while(i <= forwardMostMove) {
7262                                 lastCheck = inCheck; // check evasion does not count
7263                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7264                                 if(inCheck || lastCheck) count--; // check does not count
7265                                 i++;
7266                         }
7267                 }
7268                 if( count >= 100)
7269                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7270                          /* this is used to judge if draw claims are legal */
7271                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7272                          if(engineOpponent) {
7273                            SendToProgram("force\n", engineOpponent); // suppress reply
7274                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7275                          }
7276                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7277                          return 1;
7278                 }
7279
7280                 /* if draw offer is pending, treat it as a draw claim
7281                  * when draw condition present, to allow engines a way to
7282                  * claim draws before making their move to avoid a race
7283                  * condition occurring after their move
7284                  */
7285                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7286                          char *p = NULL;
7287                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7288                              p = "Draw claim: 50-move rule";
7289                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7290                              p = "Draw claim: 3-fold repetition";
7291                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7292                              p = "Draw claim: insufficient mating material";
7293                          if( p != NULL && canAdjudicate) {
7294                              if(engineOpponent) {
7295                                SendToProgram("force\n", engineOpponent); // suppress reply
7296                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7297                              }
7298                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7299                              return 1;
7300                          }
7301                 }
7302
7303                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7304                     if(engineOpponent) {
7305                       SendToProgram("force\n", engineOpponent); // suppress reply
7306                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7307                     }
7308                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7309                     return 1;
7310                 }
7311         return 0;
7312 }
7313
7314 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7315 {   // [HGM] book: this routine intercepts moves to simulate book replies
7316     char *bookHit = NULL;
7317
7318     //first determine if the incoming move brings opponent into his book
7319     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7320         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7321     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7322     if(bookHit != NULL && !cps->bookSuspend) {
7323         // make sure opponent is not going to reply after receiving move to book position
7324         SendToProgram("force\n", cps);
7325         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7326     }
7327     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7328     // now arrange restart after book miss
7329     if(bookHit) {
7330         // after a book hit we never send 'go', and the code after the call to this routine
7331         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7332         char buf[MSG_SIZ];
7333         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7334         SendToProgram(buf, cps);
7335         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7336     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7337         SendToProgram("go\n", cps);
7338         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7339     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7340         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7341             SendToProgram("go\n", cps);
7342         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7343     }
7344     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7345 }
7346
7347 char *savedMessage;
7348 ChessProgramState *savedState;
7349 void DeferredBookMove(void)
7350 {
7351         if(savedState->lastPing != savedState->lastPong)
7352                     ScheduleDelayedEvent(DeferredBookMove, 10);
7353         else
7354         HandleMachineMove(savedMessage, savedState);
7355 }
7356
7357 void
7358 HandleMachineMove(message, cps)
7359      char *message;
7360      ChessProgramState *cps;
7361 {
7362     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7363     char realname[MSG_SIZ];
7364     int fromX, fromY, toX, toY;
7365     ChessMove moveType;
7366     char promoChar;
7367     char *p;
7368     int machineWhite;
7369     char *bookHit;
7370
7371     cps->userError = 0;
7372
7373 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7374     /*
7375      * Kludge to ignore BEL characters
7376      */
7377     while (*message == '\007') message++;
7378
7379     /*
7380      * [HGM] engine debug message: ignore lines starting with '#' character
7381      */
7382     if(cps->debug && *message == '#') return;
7383
7384     /*
7385      * Look for book output
7386      */
7387     if (cps == &first && bookRequested) {
7388         if (message[0] == '\t' || message[0] == ' ') {
7389             /* Part of the book output is here; append it */
7390             strcat(bookOutput, message);
7391             strcat(bookOutput, "  \n");
7392             return;
7393         } else if (bookOutput[0] != NULLCHAR) {
7394             /* All of book output has arrived; display it */
7395             char *p = bookOutput;
7396             while (*p != NULLCHAR) {
7397                 if (*p == '\t') *p = ' ';
7398                 p++;
7399             }
7400             DisplayInformation(bookOutput);
7401             bookRequested = FALSE;
7402             /* Fall through to parse the current output */
7403         }
7404     }
7405
7406     /*
7407      * Look for machine move.
7408      */
7409     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7410         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7411     {
7412         /* This method is only useful on engines that support ping */
7413         if (cps->lastPing != cps->lastPong) {
7414           if (gameMode == BeginningOfGame) {
7415             /* Extra move from before last new; ignore */
7416             if (appData.debugMode) {
7417                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7418             }
7419           } else {
7420             if (appData.debugMode) {
7421                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7422                         cps->which, gameMode);
7423             }
7424
7425             SendToProgram("undo\n", cps);
7426           }
7427           return;
7428         }
7429
7430         switch (gameMode) {
7431           case BeginningOfGame:
7432             /* Extra move from before last reset; ignore */
7433             if (appData.debugMode) {
7434                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7435             }
7436             return;
7437
7438           case EndOfGame:
7439           case IcsIdle:
7440           default:
7441             /* Extra move after we tried to stop.  The mode test is
7442                not a reliable way of detecting this problem, but it's
7443                the best we can do on engines that don't support ping.
7444             */
7445             if (appData.debugMode) {
7446                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7447                         cps->which, gameMode);
7448             }
7449             SendToProgram("undo\n", cps);
7450             return;
7451
7452           case MachinePlaysWhite:
7453           case IcsPlayingWhite:
7454             machineWhite = TRUE;
7455             break;
7456
7457           case MachinePlaysBlack:
7458           case IcsPlayingBlack:
7459             machineWhite = FALSE;
7460             break;
7461
7462           case TwoMachinesPlay:
7463             machineWhite = (cps->twoMachinesColor[0] == 'w');
7464             break;
7465         }
7466         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7467             if (appData.debugMode) {
7468                 fprintf(debugFP,
7469                         "Ignoring move out of turn by %s, gameMode %d"
7470                         ", forwardMost %d\n",
7471                         cps->which, gameMode, forwardMostMove);
7472             }
7473             return;
7474         }
7475
7476     if (appData.debugMode) { int f = forwardMostMove;
7477         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7478                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7479                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7480     }
7481         if(cps->alphaRank) AlphaRank(machineMove, 4);
7482         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7483                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7484             /* Machine move could not be parsed; ignore it. */
7485           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7486                     machineMove, _(cps->which));
7487             DisplayError(buf1, 0);
7488             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7489                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7490             if (gameMode == TwoMachinesPlay) {
7491               GameEnds(machineWhite ? BlackWins : WhiteWins,
7492                        buf1, GE_XBOARD);
7493             }
7494             return;
7495         }
7496
7497         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7498         /* So we have to redo legality test with true e.p. status here,  */
7499         /* to make sure an illegal e.p. capture does not slip through,   */
7500         /* to cause a forfeit on a justified illegal-move complaint      */
7501         /* of the opponent.                                              */
7502         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7503            ChessMove moveType;
7504            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7505                              fromY, fromX, toY, toX, promoChar);
7506             if (appData.debugMode) {
7507                 int i;
7508                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7509                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7510                 fprintf(debugFP, "castling rights\n");
7511             }
7512             if(moveType == IllegalMove) {
7513               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7514                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7515                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7516                            buf1, GE_XBOARD);
7517                 return;
7518            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7519            /* [HGM] Kludge to handle engines that send FRC-style castling
7520               when they shouldn't (like TSCP-Gothic) */
7521            switch(moveType) {
7522              case WhiteASideCastleFR:
7523              case BlackASideCastleFR:
7524                toX+=2;
7525                currentMoveString[2]++;
7526                break;
7527              case WhiteHSideCastleFR:
7528              case BlackHSideCastleFR:
7529                toX--;
7530                currentMoveString[2]--;
7531                break;
7532              default: ; // nothing to do, but suppresses warning of pedantic compilers
7533            }
7534         }
7535         hintRequested = FALSE;
7536         lastHint[0] = NULLCHAR;
7537         bookRequested = FALSE;
7538         /* Program may be pondering now */
7539         cps->maybeThinking = TRUE;
7540         if (cps->sendTime == 2) cps->sendTime = 1;
7541         if (cps->offeredDraw) cps->offeredDraw--;
7542
7543         /* [AS] Save move info*/
7544         pvInfoList[ forwardMostMove ].score = programStats.score;
7545         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7546         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7547
7548         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7549
7550         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7551         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7552             int count = 0;
7553
7554             while( count < adjudicateLossPlies ) {
7555                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7556
7557                 if( count & 1 ) {
7558                     score = -score; /* Flip score for winning side */
7559                 }
7560
7561                 if( score > adjudicateLossThreshold ) {
7562                     break;
7563                 }
7564
7565                 count++;
7566             }
7567
7568             if( count >= adjudicateLossPlies ) {
7569                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7570
7571                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7572                     "Xboard adjudication",
7573                     GE_XBOARD );
7574
7575                 return;
7576             }
7577         }
7578
7579         if(Adjudicate(cps)) {
7580             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7581             return; // [HGM] adjudicate: for all automatic game ends
7582         }
7583
7584 #if ZIPPY
7585         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7586             first.initDone) {
7587           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7588                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7589                 SendToICS("draw ");
7590                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7591           }
7592           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7593           ics_user_moved = 1;
7594           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7595                 char buf[3*MSG_SIZ];
7596
7597                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7598                         programStats.score / 100.,
7599                         programStats.depth,
7600                         programStats.time / 100.,
7601                         (unsigned int)programStats.nodes,
7602                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7603                         programStats.movelist);
7604                 SendToICS(buf);
7605 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7606           }
7607         }
7608 #endif
7609
7610         /* [AS] Clear stats for next move */
7611         ClearProgramStats();
7612         thinkOutput[0] = NULLCHAR;
7613         hiddenThinkOutputState = 0;
7614
7615         bookHit = NULL;
7616         if (gameMode == TwoMachinesPlay) {
7617             /* [HGM] relaying draw offers moved to after reception of move */
7618             /* and interpreting offer as claim if it brings draw condition */
7619             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7620                 SendToProgram("draw\n", cps->other);
7621             }
7622             if (cps->other->sendTime) {
7623                 SendTimeRemaining(cps->other,
7624                                   cps->other->twoMachinesColor[0] == 'w');
7625             }
7626             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7627             if (firstMove && !bookHit) {
7628                 firstMove = FALSE;
7629                 if (cps->other->useColors) {
7630                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7631                 }
7632                 SendToProgram("go\n", cps->other);
7633             }
7634             cps->other->maybeThinking = TRUE;
7635         }
7636
7637         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7638
7639         if (!pausing && appData.ringBellAfterMoves) {
7640             RingBell();
7641         }
7642
7643         /*
7644          * Reenable menu items that were disabled while
7645          * machine was thinking
7646          */
7647         if (gameMode != TwoMachinesPlay)
7648             SetUserThinkingEnables();
7649
7650         // [HGM] book: after book hit opponent has received move and is now in force mode
7651         // force the book reply into it, and then fake that it outputted this move by jumping
7652         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7653         if(bookHit) {
7654                 static char bookMove[MSG_SIZ]; // a bit generous?
7655
7656                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7657                 strcat(bookMove, bookHit);
7658                 message = bookMove;
7659                 cps = cps->other;
7660                 programStats.nodes = programStats.depth = programStats.time =
7661                 programStats.score = programStats.got_only_move = 0;
7662                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7663
7664                 if(cps->lastPing != cps->lastPong) {
7665                     savedMessage = message; // args for deferred call
7666                     savedState = cps;
7667                     ScheduleDelayedEvent(DeferredBookMove, 10);
7668                     return;
7669                 }
7670                 goto FakeBookMove;
7671         }
7672
7673         return;
7674     }
7675
7676     /* Set special modes for chess engines.  Later something general
7677      *  could be added here; for now there is just one kludge feature,
7678      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7679      *  when "xboard" is given as an interactive command.
7680      */
7681     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7682         cps->useSigint = FALSE;
7683         cps->useSigterm = FALSE;
7684     }
7685     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7686       ParseFeatures(message+8, cps);
7687       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7688     }
7689
7690     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7691       int dummy, s=6; char buf[MSG_SIZ];
7692       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7693       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7694       ParseFEN(boards[0], &dummy, message+s);
7695       DrawPosition(TRUE, boards[0]);
7696       startedFromSetupPosition = TRUE;
7697       return;
7698     }
7699     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7700      * want this, I was asked to put it in, and obliged.
7701      */
7702     if (!strncmp(message, "setboard ", 9)) {
7703         Board initial_position;
7704
7705         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7706
7707         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7708             DisplayError(_("Bad FEN received from engine"), 0);
7709             return ;
7710         } else {
7711            Reset(TRUE, FALSE);
7712            CopyBoard(boards[0], initial_position);
7713            initialRulePlies = FENrulePlies;
7714            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7715            else gameMode = MachinePlaysBlack;
7716            DrawPosition(FALSE, boards[currentMove]);
7717         }
7718         return;
7719     }
7720
7721     /*
7722      * Look for communication commands
7723      */
7724     if (!strncmp(message, "telluser ", 9)) {
7725         if(message[9] == '\\' && message[10] == '\\')
7726             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7727         DisplayNote(message + 9);
7728         return;
7729     }
7730     if (!strncmp(message, "tellusererror ", 14)) {
7731         cps->userError = 1;
7732         if(message[14] == '\\' && message[15] == '\\')
7733             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7734         DisplayError(message + 14, 0);
7735         return;
7736     }
7737     if (!strncmp(message, "tellopponent ", 13)) {
7738       if (appData.icsActive) {
7739         if (loggedOn) {
7740           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7741           SendToICS(buf1);
7742         }
7743       } else {
7744         DisplayNote(message + 13);
7745       }
7746       return;
7747     }
7748     if (!strncmp(message, "tellothers ", 11)) {
7749       if (appData.icsActive) {
7750         if (loggedOn) {
7751           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7752           SendToICS(buf1);
7753         }
7754       }
7755       return;
7756     }
7757     if (!strncmp(message, "tellall ", 8)) {
7758       if (appData.icsActive) {
7759         if (loggedOn) {
7760           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7761           SendToICS(buf1);
7762         }
7763       } else {
7764         DisplayNote(message + 8);
7765       }
7766       return;
7767     }
7768     if (strncmp(message, "warning", 7) == 0) {
7769         /* Undocumented feature, use tellusererror in new code */
7770         DisplayError(message, 0);
7771         return;
7772     }
7773     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7774         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7775         strcat(realname, " query");
7776         AskQuestion(realname, buf2, buf1, cps->pr);
7777         return;
7778     }
7779     /* Commands from the engine directly to ICS.  We don't allow these to be
7780      *  sent until we are logged on. Crafty kibitzes have been known to
7781      *  interfere with the login process.
7782      */
7783     if (loggedOn) {
7784         if (!strncmp(message, "tellics ", 8)) {
7785             SendToICS(message + 8);
7786             SendToICS("\n");
7787             return;
7788         }
7789         if (!strncmp(message, "tellicsnoalias ", 15)) {
7790             SendToICS(ics_prefix);
7791             SendToICS(message + 15);
7792             SendToICS("\n");
7793             return;
7794         }
7795         /* The following are for backward compatibility only */
7796         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7797             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7798             SendToICS(ics_prefix);
7799             SendToICS(message);
7800             SendToICS("\n");
7801             return;
7802         }
7803     }
7804     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7805         return;
7806     }
7807     /*
7808      * If the move is illegal, cancel it and redraw the board.
7809      * Also deal with other error cases.  Matching is rather loose
7810      * here to accommodate engines written before the spec.
7811      */
7812     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7813         strncmp(message, "Error", 5) == 0) {
7814         if (StrStr(message, "name") ||
7815             StrStr(message, "rating") || StrStr(message, "?") ||
7816             StrStr(message, "result") || StrStr(message, "board") ||
7817             StrStr(message, "bk") || StrStr(message, "computer") ||
7818             StrStr(message, "variant") || StrStr(message, "hint") ||
7819             StrStr(message, "random") || StrStr(message, "depth") ||
7820             StrStr(message, "accepted")) {
7821             return;
7822         }
7823         if (StrStr(message, "protover")) {
7824           /* Program is responding to input, so it's apparently done
7825              initializing, and this error message indicates it is
7826              protocol version 1.  So we don't need to wait any longer
7827              for it to initialize and send feature commands. */
7828           FeatureDone(cps, 1);
7829           cps->protocolVersion = 1;
7830           return;
7831         }
7832         cps->maybeThinking = FALSE;
7833
7834         if (StrStr(message, "draw")) {
7835             /* Program doesn't have "draw" command */
7836             cps->sendDrawOffers = 0;
7837             return;
7838         }
7839         if (cps->sendTime != 1 &&
7840             (StrStr(message, "time") || StrStr(message, "otim"))) {
7841           /* Program apparently doesn't have "time" or "otim" command */
7842           cps->sendTime = 0;
7843           return;
7844         }
7845         if (StrStr(message, "analyze")) {
7846             cps->analysisSupport = FALSE;
7847             cps->analyzing = FALSE;
7848             Reset(FALSE, TRUE);
7849             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7850             DisplayError(buf2, 0);
7851             return;
7852         }
7853         if (StrStr(message, "(no matching move)st")) {
7854           /* Special kludge for GNU Chess 4 only */
7855           cps->stKludge = TRUE;
7856           SendTimeControl(cps, movesPerSession, timeControl,
7857                           timeIncrement, appData.searchDepth,
7858                           searchTime);
7859           return;
7860         }
7861         if (StrStr(message, "(no matching move)sd")) {
7862           /* Special kludge for GNU Chess 4 only */
7863           cps->sdKludge = TRUE;
7864           SendTimeControl(cps, movesPerSession, timeControl,
7865                           timeIncrement, appData.searchDepth,
7866                           searchTime);
7867           return;
7868         }
7869         if (!StrStr(message, "llegal")) {
7870             return;
7871         }
7872         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7873             gameMode == IcsIdle) return;
7874         if (forwardMostMove <= backwardMostMove) return;
7875         if (pausing) PauseEvent();
7876       if(appData.forceIllegal) {
7877             // [HGM] illegal: machine refused move; force position after move into it
7878           SendToProgram("force\n", cps);
7879           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7880                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7881                 // when black is to move, while there might be nothing on a2 or black
7882                 // might already have the move. So send the board as if white has the move.
7883                 // But first we must change the stm of the engine, as it refused the last move
7884                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7885                 if(WhiteOnMove(forwardMostMove)) {
7886                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7887                     SendBoard(cps, forwardMostMove); // kludgeless board
7888                 } else {
7889                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7890                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7891                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7892                 }
7893           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7894             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7895                  gameMode == TwoMachinesPlay)
7896               SendToProgram("go\n", cps);
7897             return;
7898       } else
7899         if (gameMode == PlayFromGameFile) {
7900             /* Stop reading this game file */
7901             gameMode = EditGame;
7902             ModeHighlight();
7903         }
7904         /* [HGM] illegal-move claim should forfeit game when Xboard */
7905         /* only passes fully legal moves                            */
7906         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7907             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7908                                 "False illegal-move claim", GE_XBOARD );
7909             return; // do not take back move we tested as valid
7910         }
7911         currentMove = forwardMostMove-1;
7912         DisplayMove(currentMove-1); /* before DisplayMoveError */
7913         SwitchClocks(forwardMostMove-1); // [HGM] race
7914         DisplayBothClocks();
7915         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7916                 parseList[currentMove], _(cps->which));
7917         DisplayMoveError(buf1);
7918         DrawPosition(FALSE, boards[currentMove]);
7919         return;
7920     }
7921     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7922         /* Program has a broken "time" command that
7923            outputs a string not ending in newline.
7924            Don't use it. */
7925         cps->sendTime = 0;
7926     }
7927
7928     /*
7929      * If chess program startup fails, exit with an error message.
7930      * Attempts to recover here are futile.
7931      */
7932     if ((StrStr(message, "unknown host") != NULL)
7933         || (StrStr(message, "No remote directory") != NULL)
7934         || (StrStr(message, "not found") != NULL)
7935         || (StrStr(message, "No such file") != NULL)
7936         || (StrStr(message, "can't alloc") != NULL)
7937         || (StrStr(message, "Permission denied") != NULL)) {
7938
7939         cps->maybeThinking = FALSE;
7940         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7941                 _(cps->which), cps->program, cps->host, message);
7942         RemoveInputSource(cps->isr);
7943         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
7944             if(cps == &first) appData.noChessProgram = TRUE;
7945             DisplayError(buf1, 0);
7946         }
7947         return;
7948     }
7949
7950     /*
7951      * Look for hint output
7952      */
7953     if (sscanf(message, "Hint: %s", buf1) == 1) {
7954         if (cps == &first && hintRequested) {
7955             hintRequested = FALSE;
7956             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7957                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7958                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7959                                     PosFlags(forwardMostMove),
7960                                     fromY, fromX, toY, toX, promoChar, buf1);
7961                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7962                 DisplayInformation(buf2);
7963             } else {
7964                 /* Hint move could not be parsed!? */
7965               snprintf(buf2, sizeof(buf2),
7966                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7967                         buf1, _(cps->which));
7968                 DisplayError(buf2, 0);
7969             }
7970         } else {
7971           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7972         }
7973         return;
7974     }
7975
7976     /*
7977      * Ignore other messages if game is not in progress
7978      */
7979     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7980         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7981
7982     /*
7983      * look for win, lose, draw, or draw offer
7984      */
7985     if (strncmp(message, "1-0", 3) == 0) {
7986         char *p, *q, *r = "";
7987         p = strchr(message, '{');
7988         if (p) {
7989             q = strchr(p, '}');
7990             if (q) {
7991                 *q = NULLCHAR;
7992                 r = p + 1;
7993             }
7994         }
7995         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7996         return;
7997     } else if (strncmp(message, "0-1", 3) == 0) {
7998         char *p, *q, *r = "";
7999         p = strchr(message, '{');
8000         if (p) {
8001             q = strchr(p, '}');
8002             if (q) {
8003                 *q = NULLCHAR;
8004                 r = p + 1;
8005             }
8006         }
8007         /* Kludge for Arasan 4.1 bug */
8008         if (strcmp(r, "Black resigns") == 0) {
8009             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8010             return;
8011         }
8012         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8013         return;
8014     } else if (strncmp(message, "1/2", 3) == 0) {
8015         char *p, *q, *r = "";
8016         p = strchr(message, '{');
8017         if (p) {
8018             q = strchr(p, '}');
8019             if (q) {
8020                 *q = NULLCHAR;
8021                 r = p + 1;
8022             }
8023         }
8024
8025         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8026         return;
8027
8028     } else if (strncmp(message, "White resign", 12) == 0) {
8029         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8030         return;
8031     } else if (strncmp(message, "Black resign", 12) == 0) {
8032         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8033         return;
8034     } else if (strncmp(message, "White matches", 13) == 0 ||
8035                strncmp(message, "Black matches", 13) == 0   ) {
8036         /* [HGM] ignore GNUShogi noises */
8037         return;
8038     } else if (strncmp(message, "White", 5) == 0 &&
8039                message[5] != '(' &&
8040                StrStr(message, "Black") == NULL) {
8041         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8042         return;
8043     } else if (strncmp(message, "Black", 5) == 0 &&
8044                message[5] != '(') {
8045         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8046         return;
8047     } else if (strcmp(message, "resign") == 0 ||
8048                strcmp(message, "computer resigns") == 0) {
8049         switch (gameMode) {
8050           case MachinePlaysBlack:
8051           case IcsPlayingBlack:
8052             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8053             break;
8054           case MachinePlaysWhite:
8055           case IcsPlayingWhite:
8056             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8057             break;
8058           case TwoMachinesPlay:
8059             if (cps->twoMachinesColor[0] == 'w')
8060               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8061             else
8062               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8063             break;
8064           default:
8065             /* can't happen */
8066             break;
8067         }
8068         return;
8069     } else if (strncmp(message, "opponent mates", 14) == 0) {
8070         switch (gameMode) {
8071           case MachinePlaysBlack:
8072           case IcsPlayingBlack:
8073             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8074             break;
8075           case MachinePlaysWhite:
8076           case IcsPlayingWhite:
8077             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8078             break;
8079           case TwoMachinesPlay:
8080             if (cps->twoMachinesColor[0] == 'w')
8081               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8082             else
8083               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8084             break;
8085           default:
8086             /* can't happen */
8087             break;
8088         }
8089         return;
8090     } else if (strncmp(message, "computer mates", 14) == 0) {
8091         switch (gameMode) {
8092           case MachinePlaysBlack:
8093           case IcsPlayingBlack:
8094             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8095             break;
8096           case MachinePlaysWhite:
8097           case IcsPlayingWhite:
8098             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8099             break;
8100           case TwoMachinesPlay:
8101             if (cps->twoMachinesColor[0] == 'w')
8102               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8103             else
8104               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8105             break;
8106           default:
8107             /* can't happen */
8108             break;
8109         }
8110         return;
8111     } else if (strncmp(message, "checkmate", 9) == 0) {
8112         if (WhiteOnMove(forwardMostMove)) {
8113             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8114         } else {
8115             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8116         }
8117         return;
8118     } else if (strstr(message, "Draw") != NULL ||
8119                strstr(message, "game is a draw") != NULL) {
8120         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8121         return;
8122     } else if (strstr(message, "offer") != NULL &&
8123                strstr(message, "draw") != NULL) {
8124 #if ZIPPY
8125         if (appData.zippyPlay && first.initDone) {
8126             /* Relay offer to ICS */
8127             SendToICS(ics_prefix);
8128             SendToICS("draw\n");
8129         }
8130 #endif
8131         cps->offeredDraw = 2; /* valid until this engine moves twice */
8132         if (gameMode == TwoMachinesPlay) {
8133             if (cps->other->offeredDraw) {
8134                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8135             /* [HGM] in two-machine mode we delay relaying draw offer      */
8136             /* until after we also have move, to see if it is really claim */
8137             }
8138         } else if (gameMode == MachinePlaysWhite ||
8139                    gameMode == MachinePlaysBlack) {
8140           if (userOfferedDraw) {
8141             DisplayInformation(_("Machine accepts your draw offer"));
8142             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8143           } else {
8144             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8145           }
8146         }
8147     }
8148
8149
8150     /*
8151      * Look for thinking output
8152      */
8153     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8154           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8155                                 ) {
8156         int plylev, mvleft, mvtot, curscore, time;
8157         char mvname[MOVE_LEN];
8158         u64 nodes; // [DM]
8159         char plyext;
8160         int ignore = FALSE;
8161         int prefixHint = FALSE;
8162         mvname[0] = NULLCHAR;
8163
8164         switch (gameMode) {
8165           case MachinePlaysBlack:
8166           case IcsPlayingBlack:
8167             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8168             break;
8169           case MachinePlaysWhite:
8170           case IcsPlayingWhite:
8171             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8172             break;
8173           case AnalyzeMode:
8174           case AnalyzeFile:
8175             break;
8176           case IcsObserving: /* [DM] icsEngineAnalyze */
8177             if (!appData.icsEngineAnalyze) ignore = TRUE;
8178             break;
8179           case TwoMachinesPlay:
8180             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8181                 ignore = TRUE;
8182             }
8183             break;
8184           default:
8185             ignore = TRUE;
8186             break;
8187         }
8188
8189         if (!ignore) {
8190             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8191             buf1[0] = NULLCHAR;
8192             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8193                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8194
8195                 if (plyext != ' ' && plyext != '\t') {
8196                     time *= 100;
8197                 }
8198
8199                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8200                 if( cps->scoreIsAbsolute &&
8201                     ( gameMode == MachinePlaysBlack ||
8202                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8203                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8204                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8205                      !WhiteOnMove(currentMove)
8206                     ) )
8207                 {
8208                     curscore = -curscore;
8209                 }
8210
8211
8212                 tempStats.depth = plylev;
8213                 tempStats.nodes = nodes;
8214                 tempStats.time = time;
8215                 tempStats.score = curscore;
8216                 tempStats.got_only_move = 0;
8217
8218                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8219                         int ticklen;
8220
8221                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8222                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8223                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8224                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8225                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8226                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8227                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8228                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8229                 }
8230
8231                 /* Buffer overflow protection */
8232                 if (buf1[0] != NULLCHAR) {
8233                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8234                         && appData.debugMode) {
8235                         fprintf(debugFP,
8236                                 "PV is too long; using the first %u bytes.\n",
8237                                 (unsigned) sizeof(tempStats.movelist) - 1);
8238                     }
8239
8240                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8241                 } else {
8242                     sprintf(tempStats.movelist, " no PV\n");
8243                 }
8244
8245                 if (tempStats.seen_stat) {
8246                     tempStats.ok_to_send = 1;
8247                 }
8248
8249                 if (strchr(tempStats.movelist, '(') != NULL) {
8250                     tempStats.line_is_book = 1;
8251                     tempStats.nr_moves = 0;
8252                     tempStats.moves_left = 0;
8253                 } else {
8254                     tempStats.line_is_book = 0;
8255                 }
8256
8257                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8258                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8259
8260                 SendProgramStatsToFrontend( cps, &tempStats );
8261
8262                 /*
8263                     [AS] Protect the thinkOutput buffer from overflow... this
8264                     is only useful if buf1 hasn't overflowed first!
8265                 */
8266                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8267                          plylev,
8268                          (gameMode == TwoMachinesPlay ?
8269                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8270                          ((double) curscore) / 100.0,
8271                          prefixHint ? lastHint : "",
8272                          prefixHint ? " " : "" );
8273
8274                 if( buf1[0] != NULLCHAR ) {
8275                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8276
8277                     if( strlen(buf1) > max_len ) {
8278                         if( appData.debugMode) {
8279                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8280                         }
8281                         buf1[max_len+1] = '\0';
8282                     }
8283
8284                     strcat( thinkOutput, buf1 );
8285                 }
8286
8287                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8288                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8289                     DisplayMove(currentMove - 1);
8290                 }
8291                 return;
8292
8293             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8294                 /* crafty (9.25+) says "(only move) <move>"
8295                  * if there is only 1 legal move
8296                  */
8297                 sscanf(p, "(only move) %s", buf1);
8298                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8299                 sprintf(programStats.movelist, "%s (only move)", buf1);
8300                 programStats.depth = 1;
8301                 programStats.nr_moves = 1;
8302                 programStats.moves_left = 1;
8303                 programStats.nodes = 1;
8304                 programStats.time = 1;
8305                 programStats.got_only_move = 1;
8306
8307                 /* Not really, but we also use this member to
8308                    mean "line isn't going to change" (Crafty
8309                    isn't searching, so stats won't change) */
8310                 programStats.line_is_book = 1;
8311
8312                 SendProgramStatsToFrontend( cps, &programStats );
8313
8314                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8315                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8316                     DisplayMove(currentMove - 1);
8317                 }
8318                 return;
8319             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8320                               &time, &nodes, &plylev, &mvleft,
8321                               &mvtot, mvname) >= 5) {
8322                 /* The stat01: line is from Crafty (9.29+) in response
8323                    to the "." command */
8324                 programStats.seen_stat = 1;
8325                 cps->maybeThinking = TRUE;
8326
8327                 if (programStats.got_only_move || !appData.periodicUpdates)
8328                   return;
8329
8330                 programStats.depth = plylev;
8331                 programStats.time = time;
8332                 programStats.nodes = nodes;
8333                 programStats.moves_left = mvleft;
8334                 programStats.nr_moves = mvtot;
8335                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8336                 programStats.ok_to_send = 1;
8337                 programStats.movelist[0] = '\0';
8338
8339                 SendProgramStatsToFrontend( cps, &programStats );
8340
8341                 return;
8342
8343             } else if (strncmp(message,"++",2) == 0) {
8344                 /* Crafty 9.29+ outputs this */
8345                 programStats.got_fail = 2;
8346                 return;
8347
8348             } else if (strncmp(message,"--",2) == 0) {
8349                 /* Crafty 9.29+ outputs this */
8350                 programStats.got_fail = 1;
8351                 return;
8352
8353             } else if (thinkOutput[0] != NULLCHAR &&
8354                        strncmp(message, "    ", 4) == 0) {
8355                 unsigned message_len;
8356
8357                 p = message;
8358                 while (*p && *p == ' ') p++;
8359
8360                 message_len = strlen( p );
8361
8362                 /* [AS] Avoid buffer overflow */
8363                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8364                     strcat(thinkOutput, " ");
8365                     strcat(thinkOutput, p);
8366                 }
8367
8368                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8369                     strcat(programStats.movelist, " ");
8370                     strcat(programStats.movelist, p);
8371                 }
8372
8373                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8374                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8375                     DisplayMove(currentMove - 1);
8376                 }
8377                 return;
8378             }
8379         }
8380         else {
8381             buf1[0] = NULLCHAR;
8382
8383             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8384                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8385             {
8386                 ChessProgramStats cpstats;
8387
8388                 if (plyext != ' ' && plyext != '\t') {
8389                     time *= 100;
8390                 }
8391
8392                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8393                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8394                     curscore = -curscore;
8395                 }
8396
8397                 cpstats.depth = plylev;
8398                 cpstats.nodes = nodes;
8399                 cpstats.time = time;
8400                 cpstats.score = curscore;
8401                 cpstats.got_only_move = 0;
8402                 cpstats.movelist[0] = '\0';
8403
8404                 if (buf1[0] != NULLCHAR) {
8405                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8406                 }
8407
8408                 cpstats.ok_to_send = 0;
8409                 cpstats.line_is_book = 0;
8410                 cpstats.nr_moves = 0;
8411                 cpstats.moves_left = 0;
8412
8413                 SendProgramStatsToFrontend( cps, &cpstats );
8414             }
8415         }
8416     }
8417 }
8418
8419
8420 /* Parse a game score from the character string "game", and
8421    record it as the history of the current game.  The game
8422    score is NOT assumed to start from the standard position.
8423    The display is not updated in any way.
8424    */
8425 void
8426 ParseGameHistory(game)
8427      char *game;
8428 {
8429     ChessMove moveType;
8430     int fromX, fromY, toX, toY, boardIndex;
8431     char promoChar;
8432     char *p, *q;
8433     char buf[MSG_SIZ];
8434
8435     if (appData.debugMode)
8436       fprintf(debugFP, "Parsing game history: %s\n", game);
8437
8438     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8439     gameInfo.site = StrSave(appData.icsHost);
8440     gameInfo.date = PGNDate();
8441     gameInfo.round = StrSave("-");
8442
8443     /* Parse out names of players */
8444     while (*game == ' ') game++;
8445     p = buf;
8446     while (*game != ' ') *p++ = *game++;
8447     *p = NULLCHAR;
8448     gameInfo.white = StrSave(buf);
8449     while (*game == ' ') game++;
8450     p = buf;
8451     while (*game != ' ' && *game != '\n') *p++ = *game++;
8452     *p = NULLCHAR;
8453     gameInfo.black = StrSave(buf);
8454
8455     /* Parse moves */
8456     boardIndex = blackPlaysFirst ? 1 : 0;
8457     yynewstr(game);
8458     for (;;) {
8459         yyboardindex = boardIndex;
8460         moveType = (ChessMove) Myylex();
8461         switch (moveType) {
8462           case IllegalMove:             /* maybe suicide chess, etc. */
8463   if (appData.debugMode) {
8464     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8465     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8466     setbuf(debugFP, NULL);
8467   }
8468           case WhitePromotion:
8469           case BlackPromotion:
8470           case WhiteNonPromotion:
8471           case BlackNonPromotion:
8472           case NormalMove:
8473           case WhiteCapturesEnPassant:
8474           case BlackCapturesEnPassant:
8475           case WhiteKingSideCastle:
8476           case WhiteQueenSideCastle:
8477           case BlackKingSideCastle:
8478           case BlackQueenSideCastle:
8479           case WhiteKingSideCastleWild:
8480           case WhiteQueenSideCastleWild:
8481           case BlackKingSideCastleWild:
8482           case BlackQueenSideCastleWild:
8483           /* PUSH Fabien */
8484           case WhiteHSideCastleFR:
8485           case WhiteASideCastleFR:
8486           case BlackHSideCastleFR:
8487           case BlackASideCastleFR:
8488           /* POP Fabien */
8489             fromX = currentMoveString[0] - AAA;
8490             fromY = currentMoveString[1] - ONE;
8491             toX = currentMoveString[2] - AAA;
8492             toY = currentMoveString[3] - ONE;
8493             promoChar = currentMoveString[4];
8494             break;
8495           case WhiteDrop:
8496           case BlackDrop:
8497             fromX = moveType == WhiteDrop ?
8498               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8499             (int) CharToPiece(ToLower(currentMoveString[0]));
8500             fromY = DROP_RANK;
8501             toX = currentMoveString[2] - AAA;
8502             toY = currentMoveString[3] - ONE;
8503             promoChar = NULLCHAR;
8504             break;
8505           case AmbiguousMove:
8506             /* bug? */
8507             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8508   if (appData.debugMode) {
8509     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8510     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8511     setbuf(debugFP, NULL);
8512   }
8513             DisplayError(buf, 0);
8514             return;
8515           case ImpossibleMove:
8516             /* bug? */
8517             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8518   if (appData.debugMode) {
8519     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8520     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8521     setbuf(debugFP, NULL);
8522   }
8523             DisplayError(buf, 0);
8524             return;
8525           case EndOfFile:
8526             if (boardIndex < backwardMostMove) {
8527                 /* Oops, gap.  How did that happen? */
8528                 DisplayError(_("Gap in move list"), 0);
8529                 return;
8530             }
8531             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8532             if (boardIndex > forwardMostMove) {
8533                 forwardMostMove = boardIndex;
8534             }
8535             return;
8536           case ElapsedTime:
8537             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8538                 strcat(parseList[boardIndex-1], " ");
8539                 strcat(parseList[boardIndex-1], yy_text);
8540             }
8541             continue;
8542           case Comment:
8543           case PGNTag:
8544           case NAG:
8545           default:
8546             /* ignore */
8547             continue;
8548           case WhiteWins:
8549           case BlackWins:
8550           case GameIsDrawn:
8551           case GameUnfinished:
8552             if (gameMode == IcsExamining) {
8553                 if (boardIndex < backwardMostMove) {
8554                     /* Oops, gap.  How did that happen? */
8555                     return;
8556                 }
8557                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8558                 return;
8559             }
8560             gameInfo.result = moveType;
8561             p = strchr(yy_text, '{');
8562             if (p == NULL) p = strchr(yy_text, '(');
8563             if (p == NULL) {
8564                 p = yy_text;
8565                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8566             } else {
8567                 q = strchr(p, *p == '{' ? '}' : ')');
8568                 if (q != NULL) *q = NULLCHAR;
8569                 p++;
8570             }
8571             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8572             gameInfo.resultDetails = StrSave(p);
8573             continue;
8574         }
8575         if (boardIndex >= forwardMostMove &&
8576             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8577             backwardMostMove = blackPlaysFirst ? 1 : 0;
8578             return;
8579         }
8580         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8581                                  fromY, fromX, toY, toX, promoChar,
8582                                  parseList[boardIndex]);
8583         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8584         /* currentMoveString is set as a side-effect of yylex */
8585         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8586         strcat(moveList[boardIndex], "\n");
8587         boardIndex++;
8588         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8589         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8590           case MT_NONE:
8591           case MT_STALEMATE:
8592           default:
8593             break;
8594           case MT_CHECK:
8595             if(gameInfo.variant != VariantShogi)
8596                 strcat(parseList[boardIndex - 1], "+");
8597             break;
8598           case MT_CHECKMATE:
8599           case MT_STAINMATE:
8600             strcat(parseList[boardIndex - 1], "#");
8601             break;
8602         }
8603     }
8604 }
8605
8606
8607 /* Apply a move to the given board  */
8608 void
8609 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8610      int fromX, fromY, toX, toY;
8611      int promoChar;
8612      Board board;
8613 {
8614   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8615   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8616
8617     /* [HGM] compute & store e.p. status and castling rights for new position */
8618     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8619
8620       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8621       oldEP = (signed char)board[EP_STATUS];
8622       board[EP_STATUS] = EP_NONE;
8623
8624       if( board[toY][toX] != EmptySquare )
8625            board[EP_STATUS] = EP_CAPTURE;
8626
8627   if (fromY == DROP_RANK) {
8628         /* must be first */
8629         piece = board[toY][toX] = (ChessSquare) fromX;
8630   } else {
8631       int i;
8632
8633       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8634            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8635                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8636       } else
8637       if( board[fromY][fromX] == WhitePawn ) {
8638            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8639                board[EP_STATUS] = EP_PAWN_MOVE;
8640            if( toY-fromY==2) {
8641                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8642                         gameInfo.variant != VariantBerolina || toX < fromX)
8643                       board[EP_STATUS] = toX | berolina;
8644                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8645                         gameInfo.variant != VariantBerolina || toX > fromX)
8646                       board[EP_STATUS] = toX;
8647            }
8648       } else
8649       if( board[fromY][fromX] == BlackPawn ) {
8650            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8651                board[EP_STATUS] = EP_PAWN_MOVE;
8652            if( toY-fromY== -2) {
8653                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8654                         gameInfo.variant != VariantBerolina || toX < fromX)
8655                       board[EP_STATUS] = toX | berolina;
8656                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8657                         gameInfo.variant != VariantBerolina || toX > fromX)
8658                       board[EP_STATUS] = toX;
8659            }
8660        }
8661
8662        for(i=0; i<nrCastlingRights; i++) {
8663            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8664               board[CASTLING][i] == toX   && castlingRank[i] == toY
8665              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8666        }
8667
8668      if (fromX == toX && fromY == toY) return;
8669
8670      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8671      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8672      if(gameInfo.variant == VariantKnightmate)
8673          king += (int) WhiteUnicorn - (int) WhiteKing;
8674
8675     /* Code added by Tord: */
8676     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8677     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8678         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8679       board[fromY][fromX] = EmptySquare;
8680       board[toY][toX] = EmptySquare;
8681       if((toX > fromX) != (piece == WhiteRook)) {
8682         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8683       } else {
8684         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8685       }
8686     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8687                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8688       board[fromY][fromX] = EmptySquare;
8689       board[toY][toX] = EmptySquare;
8690       if((toX > fromX) != (piece == BlackRook)) {
8691         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8692       } else {
8693         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8694       }
8695     /* End of code added by Tord */
8696
8697     } else if (board[fromY][fromX] == king
8698         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8699         && toY == fromY && toX > fromX+1) {
8700         board[fromY][fromX] = EmptySquare;
8701         board[toY][toX] = king;
8702         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8703         board[fromY][BOARD_RGHT-1] = EmptySquare;
8704     } else if (board[fromY][fromX] == king
8705         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8706                && toY == fromY && toX < fromX-1) {
8707         board[fromY][fromX] = EmptySquare;
8708         board[toY][toX] = king;
8709         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8710         board[fromY][BOARD_LEFT] = EmptySquare;
8711     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8712                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8713                && toY >= BOARD_HEIGHT-promoRank
8714                ) {
8715         /* white pawn promotion */
8716         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8717         if (board[toY][toX] == EmptySquare) {
8718             board[toY][toX] = WhiteQueen;
8719         }
8720         if(gameInfo.variant==VariantBughouse ||
8721            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8722             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8723         board[fromY][fromX] = EmptySquare;
8724     } else if ((fromY == BOARD_HEIGHT-4)
8725                && (toX != fromX)
8726                && gameInfo.variant != VariantXiangqi
8727                && gameInfo.variant != VariantBerolina
8728                && (board[fromY][fromX] == WhitePawn)
8729                && (board[toY][toX] == EmptySquare)) {
8730         board[fromY][fromX] = EmptySquare;
8731         board[toY][toX] = WhitePawn;
8732         captured = board[toY - 1][toX];
8733         board[toY - 1][toX] = EmptySquare;
8734     } else if ((fromY == BOARD_HEIGHT-4)
8735                && (toX == fromX)
8736                && gameInfo.variant == VariantBerolina
8737                && (board[fromY][fromX] == WhitePawn)
8738                && (board[toY][toX] == EmptySquare)) {
8739         board[fromY][fromX] = EmptySquare;
8740         board[toY][toX] = WhitePawn;
8741         if(oldEP & EP_BEROLIN_A) {
8742                 captured = board[fromY][fromX-1];
8743                 board[fromY][fromX-1] = EmptySquare;
8744         }else{  captured = board[fromY][fromX+1];
8745                 board[fromY][fromX+1] = EmptySquare;
8746         }
8747     } else if (board[fromY][fromX] == king
8748         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8749                && toY == fromY && toX > fromX+1) {
8750         board[fromY][fromX] = EmptySquare;
8751         board[toY][toX] = king;
8752         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8753         board[fromY][BOARD_RGHT-1] = EmptySquare;
8754     } else if (board[fromY][fromX] == king
8755         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8756                && toY == fromY && toX < fromX-1) {
8757         board[fromY][fromX] = EmptySquare;
8758         board[toY][toX] = king;
8759         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8760         board[fromY][BOARD_LEFT] = EmptySquare;
8761     } else if (fromY == 7 && fromX == 3
8762                && board[fromY][fromX] == BlackKing
8763                && toY == 7 && toX == 5) {
8764         board[fromY][fromX] = EmptySquare;
8765         board[toY][toX] = BlackKing;
8766         board[fromY][7] = EmptySquare;
8767         board[toY][4] = BlackRook;
8768     } else if (fromY == 7 && fromX == 3
8769                && board[fromY][fromX] == BlackKing
8770                && toY == 7 && toX == 1) {
8771         board[fromY][fromX] = EmptySquare;
8772         board[toY][toX] = BlackKing;
8773         board[fromY][0] = EmptySquare;
8774         board[toY][2] = BlackRook;
8775     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8776                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8777                && toY < promoRank
8778                ) {
8779         /* black pawn promotion */
8780         board[toY][toX] = CharToPiece(ToLower(promoChar));
8781         if (board[toY][toX] == EmptySquare) {
8782             board[toY][toX] = BlackQueen;
8783         }
8784         if(gameInfo.variant==VariantBughouse ||
8785            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8786             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8787         board[fromY][fromX] = EmptySquare;
8788     } else if ((fromY == 3)
8789                && (toX != fromX)
8790                && gameInfo.variant != VariantXiangqi
8791                && gameInfo.variant != VariantBerolina
8792                && (board[fromY][fromX] == BlackPawn)
8793                && (board[toY][toX] == EmptySquare)) {
8794         board[fromY][fromX] = EmptySquare;
8795         board[toY][toX] = BlackPawn;
8796         captured = board[toY + 1][toX];
8797         board[toY + 1][toX] = EmptySquare;
8798     } else if ((fromY == 3)
8799                && (toX == fromX)
8800                && gameInfo.variant == VariantBerolina
8801                && (board[fromY][fromX] == BlackPawn)
8802                && (board[toY][toX] == EmptySquare)) {
8803         board[fromY][fromX] = EmptySquare;
8804         board[toY][toX] = BlackPawn;
8805         if(oldEP & EP_BEROLIN_A) {
8806                 captured = board[fromY][fromX-1];
8807                 board[fromY][fromX-1] = EmptySquare;
8808         }else{  captured = board[fromY][fromX+1];
8809                 board[fromY][fromX+1] = EmptySquare;
8810         }
8811     } else {
8812         board[toY][toX] = board[fromY][fromX];
8813         board[fromY][fromX] = EmptySquare;
8814     }
8815   }
8816
8817     if (gameInfo.holdingsWidth != 0) {
8818
8819       /* !!A lot more code needs to be written to support holdings  */
8820       /* [HGM] OK, so I have written it. Holdings are stored in the */
8821       /* penultimate board files, so they are automaticlly stored   */
8822       /* in the game history.                                       */
8823       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8824                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8825         /* Delete from holdings, by decreasing count */
8826         /* and erasing image if necessary            */
8827         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8828         if(p < (int) BlackPawn) { /* white drop */
8829              p -= (int)WhitePawn;
8830                  p = PieceToNumber((ChessSquare)p);
8831              if(p >= gameInfo.holdingsSize) p = 0;
8832              if(--board[p][BOARD_WIDTH-2] <= 0)
8833                   board[p][BOARD_WIDTH-1] = EmptySquare;
8834              if((int)board[p][BOARD_WIDTH-2] < 0)
8835                         board[p][BOARD_WIDTH-2] = 0;
8836         } else {                  /* black drop */
8837              p -= (int)BlackPawn;
8838                  p = PieceToNumber((ChessSquare)p);
8839              if(p >= gameInfo.holdingsSize) p = 0;
8840              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8841                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8842              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8843                         board[BOARD_HEIGHT-1-p][1] = 0;
8844         }
8845       }
8846       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8847           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8848         /* [HGM] holdings: Add to holdings, if holdings exist */
8849         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8850                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8851                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8852         }
8853         p = (int) captured;
8854         if (p >= (int) BlackPawn) {
8855           p -= (int)BlackPawn;
8856           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8857                   /* in Shogi restore piece to its original  first */
8858                   captured = (ChessSquare) (DEMOTED captured);
8859                   p = DEMOTED p;
8860           }
8861           p = PieceToNumber((ChessSquare)p);
8862           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8863           board[p][BOARD_WIDTH-2]++;
8864           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8865         } else {
8866           p -= (int)WhitePawn;
8867           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8868                   captured = (ChessSquare) (DEMOTED captured);
8869                   p = DEMOTED p;
8870           }
8871           p = PieceToNumber((ChessSquare)p);
8872           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8873           board[BOARD_HEIGHT-1-p][1]++;
8874           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8875         }
8876       }
8877     } else if (gameInfo.variant == VariantAtomic) {
8878       if (captured != EmptySquare) {
8879         int y, x;
8880         for (y = toY-1; y <= toY+1; y++) {
8881           for (x = toX-1; x <= toX+1; x++) {
8882             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8883                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8884               board[y][x] = EmptySquare;
8885             }
8886           }
8887         }
8888         board[toY][toX] = EmptySquare;
8889       }
8890     }
8891     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8892         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8893     } else
8894     if(promoChar == '+') {
8895         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8896         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8897     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8898         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8899     }
8900     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8901                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8902         // [HGM] superchess: take promotion piece out of holdings
8903         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8904         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8905             if(!--board[k][BOARD_WIDTH-2])
8906                 board[k][BOARD_WIDTH-1] = EmptySquare;
8907         } else {
8908             if(!--board[BOARD_HEIGHT-1-k][1])
8909                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8910         }
8911     }
8912
8913 }
8914
8915 /* Updates forwardMostMove */
8916 void
8917 MakeMove(fromX, fromY, toX, toY, promoChar)
8918      int fromX, fromY, toX, toY;
8919      int promoChar;
8920 {
8921 //    forwardMostMove++; // [HGM] bare: moved downstream
8922
8923     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8924         int timeLeft; static int lastLoadFlag=0; int king, piece;
8925         piece = boards[forwardMostMove][fromY][fromX];
8926         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8927         if(gameInfo.variant == VariantKnightmate)
8928             king += (int) WhiteUnicorn - (int) WhiteKing;
8929         if(forwardMostMove == 0) {
8930             if(blackPlaysFirst)
8931                 fprintf(serverMoves, "%s;", second.tidy);
8932             fprintf(serverMoves, "%s;", first.tidy);
8933             if(!blackPlaysFirst)
8934                 fprintf(serverMoves, "%s;", second.tidy);
8935         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8936         lastLoadFlag = loadFlag;
8937         // print base move
8938         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8939         // print castling suffix
8940         if( toY == fromY && piece == king ) {
8941             if(toX-fromX > 1)
8942                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8943             if(fromX-toX >1)
8944                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8945         }
8946         // e.p. suffix
8947         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8948              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8949              boards[forwardMostMove][toY][toX] == EmptySquare
8950              && fromX != toX && fromY != toY)
8951                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8952         // promotion suffix
8953         if(promoChar != NULLCHAR)
8954                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8955         if(!loadFlag) {
8956             fprintf(serverMoves, "/%d/%d",
8957                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8958             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8959             else                      timeLeft = blackTimeRemaining/1000;
8960             fprintf(serverMoves, "/%d", timeLeft);
8961         }
8962         fflush(serverMoves);
8963     }
8964
8965     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8966       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8967                         0, 1);
8968       return;
8969     }
8970     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8971     if (commentList[forwardMostMove+1] != NULL) {
8972         free(commentList[forwardMostMove+1]);
8973         commentList[forwardMostMove+1] = NULL;
8974     }
8975     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8976     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8977     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8978     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8979     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8980     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8981     gameInfo.result = GameUnfinished;
8982     if (gameInfo.resultDetails != NULL) {
8983         free(gameInfo.resultDetails);
8984         gameInfo.resultDetails = NULL;
8985     }
8986     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8987                               moveList[forwardMostMove - 1]);
8988     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8989                              PosFlags(forwardMostMove - 1),
8990                              fromY, fromX, toY, toX, promoChar,
8991                              parseList[forwardMostMove - 1]);
8992     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8993       case MT_NONE:
8994       case MT_STALEMATE:
8995       default:
8996         break;
8997       case MT_CHECK:
8998         if(gameInfo.variant != VariantShogi)
8999             strcat(parseList[forwardMostMove - 1], "+");
9000         break;
9001       case MT_CHECKMATE:
9002       case MT_STAINMATE:
9003         strcat(parseList[forwardMostMove - 1], "#");
9004         break;
9005     }
9006     if (appData.debugMode) {
9007         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9008     }
9009
9010 }
9011
9012 /* Updates currentMove if not pausing */
9013 void
9014 ShowMove(fromX, fromY, toX, toY)
9015 {
9016     int instant = (gameMode == PlayFromGameFile) ?
9017         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9018     if(appData.noGUI) return;
9019     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9020         if (!instant) {
9021             if (forwardMostMove == currentMove + 1) {
9022                 AnimateMove(boards[forwardMostMove - 1],
9023                             fromX, fromY, toX, toY);
9024             }
9025             if (appData.highlightLastMove) {
9026                 SetHighlights(fromX, fromY, toX, toY);
9027             }
9028         }
9029         currentMove = forwardMostMove;
9030     }
9031
9032     if (instant) return;
9033
9034     DisplayMove(currentMove - 1);
9035     DrawPosition(FALSE, boards[currentMove]);
9036     DisplayBothClocks();
9037     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9038 }
9039
9040 void SendEgtPath(ChessProgramState *cps)
9041 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9042         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9043
9044         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9045
9046         while(*p) {
9047             char c, *q = name+1, *r, *s;
9048
9049             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9050             while(*p && *p != ',') *q++ = *p++;
9051             *q++ = ':'; *q = 0;
9052             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9053                 strcmp(name, ",nalimov:") == 0 ) {
9054                 // take nalimov path from the menu-changeable option first, if it is defined
9055               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9056                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9057             } else
9058             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9059                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9060                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9061                 s = r = StrStr(s, ":") + 1; // beginning of path info
9062                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9063                 c = *r; *r = 0;             // temporarily null-terminate path info
9064                     *--q = 0;               // strip of trailig ':' from name
9065                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9066                 *r = c;
9067                 SendToProgram(buf,cps);     // send egtbpath command for this format
9068             }
9069             if(*p == ',') p++; // read away comma to position for next format name
9070         }
9071 }
9072
9073 void
9074 InitChessProgram(cps, setup)
9075      ChessProgramState *cps;
9076      int setup; /* [HGM] needed to setup FRC opening position */
9077 {
9078     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9079     if (appData.noChessProgram) return;
9080     hintRequested = FALSE;
9081     bookRequested = FALSE;
9082
9083     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9084     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9085     if(cps->memSize) { /* [HGM] memory */
9086       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9087         SendToProgram(buf, cps);
9088     }
9089     SendEgtPath(cps); /* [HGM] EGT */
9090     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9091       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9092         SendToProgram(buf, cps);
9093     }
9094
9095     SendToProgram(cps->initString, cps);
9096     if (gameInfo.variant != VariantNormal &&
9097         gameInfo.variant != VariantLoadable
9098         /* [HGM] also send variant if board size non-standard */
9099         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9100                                             ) {
9101       char *v = VariantName(gameInfo.variant);
9102       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9103         /* [HGM] in protocol 1 we have to assume all variants valid */
9104         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9105         DisplayFatalError(buf, 0, 1);
9106         return;
9107       }
9108
9109       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9110       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9111       if( gameInfo.variant == VariantXiangqi )
9112            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9113       if( gameInfo.variant == VariantShogi )
9114            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9115       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9116            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9117       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9118           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9119            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9120       if( gameInfo.variant == VariantCourier )
9121            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9122       if( gameInfo.variant == VariantSuper )
9123            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9124       if( gameInfo.variant == VariantGreat )
9125            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9126       if( gameInfo.variant == VariantSChess )
9127            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9128
9129       if(overruled) {
9130         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9131                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9132            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9133            if(StrStr(cps->variants, b) == NULL) {
9134                // specific sized variant not known, check if general sizing allowed
9135                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9136                    if(StrStr(cps->variants, "boardsize") == NULL) {
9137                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9138                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9139                        DisplayFatalError(buf, 0, 1);
9140                        return;
9141                    }
9142                    /* [HGM] here we really should compare with the maximum supported board size */
9143                }
9144            }
9145       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9146       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9147       SendToProgram(buf, cps);
9148     }
9149     currentlyInitializedVariant = gameInfo.variant;
9150
9151     /* [HGM] send opening position in FRC to first engine */
9152     if(setup) {
9153           SendToProgram("force\n", cps);
9154           SendBoard(cps, 0);
9155           /* engine is now in force mode! Set flag to wake it up after first move. */
9156           setboardSpoiledMachineBlack = 1;
9157     }
9158
9159     if (cps->sendICS) {
9160       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9161       SendToProgram(buf, cps);
9162     }
9163     cps->maybeThinking = FALSE;
9164     cps->offeredDraw = 0;
9165     if (!appData.icsActive) {
9166         SendTimeControl(cps, movesPerSession, timeControl,
9167                         timeIncrement, appData.searchDepth,
9168                         searchTime);
9169     }
9170     if (appData.showThinking
9171         // [HGM] thinking: four options require thinking output to be sent
9172         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9173                                 ) {
9174         SendToProgram("post\n", cps);
9175     }
9176     SendToProgram("hard\n", cps);
9177     if (!appData.ponderNextMove) {
9178         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9179            it without being sure what state we are in first.  "hard"
9180            is not a toggle, so that one is OK.
9181          */
9182         SendToProgram("easy\n", cps);
9183     }
9184     if (cps->usePing) {
9185       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9186       SendToProgram(buf, cps);
9187     }
9188     cps->initDone = TRUE;
9189 }
9190
9191
9192 void
9193 StartChessProgram(cps)
9194      ChessProgramState *cps;
9195 {
9196     char buf[MSG_SIZ];
9197     int err;
9198
9199     if (appData.noChessProgram) return;
9200     cps->initDone = FALSE;
9201
9202     if (strcmp(cps->host, "localhost") == 0) {
9203         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9204     } else if (*appData.remoteShell == NULLCHAR) {
9205         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9206     } else {
9207         if (*appData.remoteUser == NULLCHAR) {
9208           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9209                     cps->program);
9210         } else {
9211           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9212                     cps->host, appData.remoteUser, cps->program);
9213         }
9214         err = StartChildProcess(buf, "", &cps->pr);
9215     }
9216
9217     if (err != 0) {
9218       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9219         DisplayFatalError(buf, err, 1);
9220         cps->pr = NoProc;
9221         cps->isr = NULL;
9222         return;
9223     }
9224
9225     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9226     if (cps->protocolVersion > 1) {
9227       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9228       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9229       cps->comboCnt = 0;  //                and values of combo boxes
9230       SendToProgram(buf, cps);
9231     } else {
9232       SendToProgram("xboard\n", cps);
9233     }
9234 }
9235
9236
9237 void
9238 TwoMachinesEventIfReady P((void))
9239 {
9240   if (first.lastPing != first.lastPong) {
9241     DisplayMessage("", _("Waiting for first chess program"));
9242     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9243     return;
9244   }
9245   if (second.lastPing != second.lastPong) {
9246     DisplayMessage("", _("Waiting for second chess program"));
9247     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9248     return;
9249   }
9250   ThawUI();
9251   TwoMachinesEvent();
9252 }
9253
9254 void
9255 NextMatchGame P((void))
9256 {
9257     int index; /* [HGM] autoinc: step load index during match */
9258     Reset(FALSE, TRUE);
9259     if (*appData.loadGameFile != NULLCHAR) {
9260         index = appData.loadGameIndex;
9261         if(index < 0) { // [HGM] autoinc
9262             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9263             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9264         }
9265         LoadGameFromFile(appData.loadGameFile,
9266                          index,
9267                          appData.loadGameFile, FALSE);
9268     } else if (*appData.loadPositionFile != NULLCHAR) {
9269         index = appData.loadPositionIndex;
9270         if(index < 0) { // [HGM] autoinc
9271             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9272             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9273         }
9274         LoadPositionFromFile(appData.loadPositionFile,
9275                              index,
9276                              appData.loadPositionFile);
9277     }
9278     TwoMachinesEventIfReady();
9279 }
9280
9281 void UserAdjudicationEvent( int result )
9282 {
9283     ChessMove gameResult = GameIsDrawn;
9284
9285     if( result > 0 ) {
9286         gameResult = WhiteWins;
9287     }
9288     else if( result < 0 ) {
9289         gameResult = BlackWins;
9290     }
9291
9292     if( gameMode == TwoMachinesPlay ) {
9293         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9294     }
9295 }
9296
9297
9298 // [HGM] save: calculate checksum of game to make games easily identifiable
9299 int StringCheckSum(char *s)
9300 {
9301         int i = 0;
9302         if(s==NULL) return 0;
9303         while(*s) i = i*259 + *s++;
9304         return i;
9305 }
9306
9307 int GameCheckSum()
9308 {
9309         int i, sum=0;
9310         for(i=backwardMostMove; i<forwardMostMove; i++) {
9311                 sum += pvInfoList[i].depth;
9312                 sum += StringCheckSum(parseList[i]);
9313                 sum += StringCheckSum(commentList[i]);
9314                 sum *= 261;
9315         }
9316         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9317         return sum + StringCheckSum(commentList[i]);
9318 } // end of save patch
9319
9320 void
9321 GameEnds(result, resultDetails, whosays)
9322      ChessMove result;
9323      char *resultDetails;
9324      int whosays;
9325 {
9326     GameMode nextGameMode;
9327     int isIcsGame;
9328     char buf[MSG_SIZ], popupRequested = 0;
9329
9330     if(endingGame) return; /* [HGM] crash: forbid recursion */
9331     endingGame = 1;
9332     if(twoBoards) { // [HGM] dual: switch back to one board
9333         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9334         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9335     }
9336     if (appData.debugMode) {
9337       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9338               result, resultDetails ? resultDetails : "(null)", whosays);
9339     }
9340
9341     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9342
9343     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9344         /* If we are playing on ICS, the server decides when the
9345            game is over, but the engine can offer to draw, claim
9346            a draw, or resign.
9347          */
9348 #if ZIPPY
9349         if (appData.zippyPlay && first.initDone) {
9350             if (result == GameIsDrawn) {
9351                 /* In case draw still needs to be claimed */
9352                 SendToICS(ics_prefix);
9353                 SendToICS("draw\n");
9354             } else if (StrCaseStr(resultDetails, "resign")) {
9355                 SendToICS(ics_prefix);
9356                 SendToICS("resign\n");
9357             }
9358         }
9359 #endif
9360         endingGame = 0; /* [HGM] crash */
9361         return;
9362     }
9363
9364     /* If we're loading the game from a file, stop */
9365     if (whosays == GE_FILE) {
9366       (void) StopLoadGameTimer();
9367       gameFileFP = NULL;
9368     }
9369
9370     /* Cancel draw offers */
9371     first.offeredDraw = second.offeredDraw = 0;
9372
9373     /* If this is an ICS game, only ICS can really say it's done;
9374        if not, anyone can. */
9375     isIcsGame = (gameMode == IcsPlayingWhite ||
9376                  gameMode == IcsPlayingBlack ||
9377                  gameMode == IcsObserving    ||
9378                  gameMode == IcsExamining);
9379
9380     if (!isIcsGame || whosays == GE_ICS) {
9381         /* OK -- not an ICS game, or ICS said it was done */
9382         StopClocks();
9383         if (!isIcsGame && !appData.noChessProgram)
9384           SetUserThinkingEnables();
9385
9386         /* [HGM] if a machine claims the game end we verify this claim */
9387         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9388             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9389                 char claimer;
9390                 ChessMove trueResult = (ChessMove) -1;
9391
9392                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9393                                             first.twoMachinesColor[0] :
9394                                             second.twoMachinesColor[0] ;
9395
9396                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9397                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9398                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9399                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9400                 } else
9401                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9402                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9403                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9404                 } else
9405                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9406                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9407                 }
9408
9409                 // now verify win claims, but not in drop games, as we don't understand those yet
9410                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9411                                                  || gameInfo.variant == VariantGreat) &&
9412                     (result == WhiteWins && claimer == 'w' ||
9413                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9414                       if (appData.debugMode) {
9415                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9416                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9417                       }
9418                       if(result != trueResult) {
9419                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9420                               result = claimer == 'w' ? BlackWins : WhiteWins;
9421                               resultDetails = buf;
9422                       }
9423                 } else
9424                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9425                     && (forwardMostMove <= backwardMostMove ||
9426                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9427                         (claimer=='b')==(forwardMostMove&1))
9428                                                                                   ) {
9429                       /* [HGM] verify: draws that were not flagged are false claims */
9430                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9431                       result = claimer == 'w' ? BlackWins : WhiteWins;
9432                       resultDetails = buf;
9433                 }
9434                 /* (Claiming a loss is accepted no questions asked!) */
9435             }
9436             /* [HGM] bare: don't allow bare King to win */
9437             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9438                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9439                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9440                && result != GameIsDrawn)
9441             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9442                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9443                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9444                         if(p >= 0 && p <= (int)WhiteKing) k++;
9445                 }
9446                 if (appData.debugMode) {
9447                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9448                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9449                 }
9450                 if(k <= 1) {
9451                         result = GameIsDrawn;
9452                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9453                         resultDetails = buf;
9454                 }
9455             }
9456         }
9457
9458
9459         if(serverMoves != NULL && !loadFlag) { char c = '=';
9460             if(result==WhiteWins) c = '+';
9461             if(result==BlackWins) c = '-';
9462             if(resultDetails != NULL)
9463                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9464         }
9465         if (resultDetails != NULL) {
9466             gameInfo.result = result;
9467             gameInfo.resultDetails = StrSave(resultDetails);
9468
9469             /* display last move only if game was not loaded from file */
9470             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9471                 DisplayMove(currentMove - 1);
9472
9473             if (forwardMostMove != 0) {
9474                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9475                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9476                                                                 ) {
9477                     if (*appData.saveGameFile != NULLCHAR) {
9478                         SaveGameToFile(appData.saveGameFile, TRUE);
9479                     } else if (appData.autoSaveGames) {
9480                         AutoSaveGame();
9481                     }
9482                     if (*appData.savePositionFile != NULLCHAR) {
9483                         SavePositionToFile(appData.savePositionFile);
9484                     }
9485                 }
9486             }
9487
9488             /* Tell program how game ended in case it is learning */
9489             /* [HGM] Moved this to after saving the PGN, just in case */
9490             /* engine died and we got here through time loss. In that */
9491             /* case we will get a fatal error writing the pipe, which */
9492             /* would otherwise lose us the PGN.                       */
9493             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9494             /* output during GameEnds should never be fatal anymore   */
9495             if (gameMode == MachinePlaysWhite ||
9496                 gameMode == MachinePlaysBlack ||
9497                 gameMode == TwoMachinesPlay ||
9498                 gameMode == IcsPlayingWhite ||
9499                 gameMode == IcsPlayingBlack ||
9500                 gameMode == BeginningOfGame) {
9501                 char buf[MSG_SIZ];
9502                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9503                         resultDetails);
9504                 if (first.pr != NoProc) {
9505                     SendToProgram(buf, &first);
9506                 }
9507                 if (second.pr != NoProc &&
9508                     gameMode == TwoMachinesPlay) {
9509                     SendToProgram(buf, &second);
9510                 }
9511             }
9512         }
9513
9514         if (appData.icsActive) {
9515             if (appData.quietPlay &&
9516                 (gameMode == IcsPlayingWhite ||
9517                  gameMode == IcsPlayingBlack)) {
9518                 SendToICS(ics_prefix);
9519                 SendToICS("set shout 1\n");
9520             }
9521             nextGameMode = IcsIdle;
9522             ics_user_moved = FALSE;
9523             /* clean up premove.  It's ugly when the game has ended and the
9524              * premove highlights are still on the board.
9525              */
9526             if (gotPremove) {
9527               gotPremove = FALSE;
9528               ClearPremoveHighlights();
9529               DrawPosition(FALSE, boards[currentMove]);
9530             }
9531             if (whosays == GE_ICS) {
9532                 switch (result) {
9533                 case WhiteWins:
9534                     if (gameMode == IcsPlayingWhite)
9535                         PlayIcsWinSound();
9536                     else if(gameMode == IcsPlayingBlack)
9537                         PlayIcsLossSound();
9538                     break;
9539                 case BlackWins:
9540                     if (gameMode == IcsPlayingBlack)
9541                         PlayIcsWinSound();
9542                     else if(gameMode == IcsPlayingWhite)
9543                         PlayIcsLossSound();
9544                     break;
9545                 case GameIsDrawn:
9546                     PlayIcsDrawSound();
9547                     break;
9548                 default:
9549                     PlayIcsUnfinishedSound();
9550                 }
9551             }
9552         } else if (gameMode == EditGame ||
9553                    gameMode == PlayFromGameFile ||
9554                    gameMode == AnalyzeMode ||
9555                    gameMode == AnalyzeFile) {
9556             nextGameMode = gameMode;
9557         } else {
9558             nextGameMode = EndOfGame;
9559         }
9560         pausing = FALSE;
9561         ModeHighlight();
9562     } else {
9563         nextGameMode = gameMode;
9564     }
9565
9566     if (appData.noChessProgram) {
9567         gameMode = nextGameMode;
9568         ModeHighlight();
9569         endingGame = 0; /* [HGM] crash */
9570         return;
9571     }
9572
9573     if (first.reuse) {
9574         /* Put first chess program into idle state */
9575         if (first.pr != NoProc &&
9576             (gameMode == MachinePlaysWhite ||
9577              gameMode == MachinePlaysBlack ||
9578              gameMode == TwoMachinesPlay ||
9579              gameMode == IcsPlayingWhite ||
9580              gameMode == IcsPlayingBlack ||
9581              gameMode == BeginningOfGame)) {
9582             SendToProgram("force\n", &first);
9583             if (first.usePing) {
9584               char buf[MSG_SIZ];
9585               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9586               SendToProgram(buf, &first);
9587             }
9588         }
9589     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9590         /* Kill off first chess program */
9591         if (first.isr != NULL)
9592           RemoveInputSource(first.isr);
9593         first.isr = NULL;
9594
9595         if (first.pr != NoProc) {
9596             ExitAnalyzeMode();
9597             DoSleep( appData.delayBeforeQuit );
9598             SendToProgram("quit\n", &first);
9599             DoSleep( appData.delayAfterQuit );
9600             DestroyChildProcess(first.pr, first.useSigterm);
9601         }
9602         first.pr = NoProc;
9603     }
9604     if (second.reuse) {
9605         /* Put second chess program into idle state */
9606         if (second.pr != NoProc &&
9607             gameMode == TwoMachinesPlay) {
9608             SendToProgram("force\n", &second);
9609             if (second.usePing) {
9610               char buf[MSG_SIZ];
9611               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9612               SendToProgram(buf, &second);
9613             }
9614         }
9615     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9616         /* Kill off second chess program */
9617         if (second.isr != NULL)
9618           RemoveInputSource(second.isr);
9619         second.isr = NULL;
9620
9621         if (second.pr != NoProc) {
9622             DoSleep( appData.delayBeforeQuit );
9623             SendToProgram("quit\n", &second);
9624             DoSleep( appData.delayAfterQuit );
9625             DestroyChildProcess(second.pr, second.useSigterm);
9626         }
9627         second.pr = NoProc;
9628     }
9629
9630     if (matchMode && gameMode == TwoMachinesPlay) {
9631         switch (result) {
9632         case WhiteWins:
9633           if (first.twoMachinesColor[0] == 'w') {
9634             first.matchWins++;
9635           } else {
9636             second.matchWins++;
9637           }
9638           break;
9639         case BlackWins:
9640           if (first.twoMachinesColor[0] == 'b') {
9641             first.matchWins++;
9642           } else {
9643             second.matchWins++;
9644           }
9645           break;
9646         default:
9647           break;
9648         }
9649         if (matchGame < appData.matchGames) {
9650             char *tmp;
9651             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9652                 tmp = first.twoMachinesColor;
9653                 first.twoMachinesColor = second.twoMachinesColor;
9654                 second.twoMachinesColor = tmp;
9655             }
9656             gameMode = nextGameMode;
9657             matchGame++;
9658             if(appData.matchPause>10000 || appData.matchPause<10)
9659                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9660             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9661             endingGame = 0; /* [HGM] crash */
9662             return;
9663         } else {
9664             gameMode = nextGameMode;
9665             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9666                      first.tidy, second.tidy,
9667                      first.matchWins, second.matchWins,
9668                      appData.matchGames - (first.matchWins + second.matchWins));
9669             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9670             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9671                 first.twoMachinesColor = "black\n";
9672                 second.twoMachinesColor = "white\n";
9673             } else {
9674                 first.twoMachinesColor = "white\n";
9675                 second.twoMachinesColor = "black\n";
9676             }
9677         }
9678     }
9679     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9680         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9681       ExitAnalyzeMode();
9682     gameMode = nextGameMode;
9683     ModeHighlight();
9684     endingGame = 0;  /* [HGM] crash */
9685     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9686       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9687         matchMode = FALSE; appData.matchGames = matchGame = 0;
9688         DisplayNote(buf);
9689       }
9690     }
9691 }
9692
9693 /* Assumes program was just initialized (initString sent).
9694    Leaves program in force mode. */
9695 void
9696 FeedMovesToProgram(cps, upto)
9697      ChessProgramState *cps;
9698      int upto;
9699 {
9700     int i;
9701
9702     if (appData.debugMode)
9703       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9704               startedFromSetupPosition ? "position and " : "",
9705               backwardMostMove, upto, cps->which);
9706     if(currentlyInitializedVariant != gameInfo.variant) {
9707       char buf[MSG_SIZ];
9708         // [HGM] variantswitch: make engine aware of new variant
9709         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9710                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9711         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9712         SendToProgram(buf, cps);
9713         currentlyInitializedVariant = gameInfo.variant;
9714     }
9715     SendToProgram("force\n", cps);
9716     if (startedFromSetupPosition) {
9717         SendBoard(cps, backwardMostMove);
9718     if (appData.debugMode) {
9719         fprintf(debugFP, "feedMoves\n");
9720     }
9721     }
9722     for (i = backwardMostMove; i < upto; i++) {
9723         SendMoveToProgram(i, cps);
9724     }
9725 }
9726
9727
9728 void
9729 ResurrectChessProgram()
9730 {
9731      /* The chess program may have exited.
9732         If so, restart it and feed it all the moves made so far. */
9733
9734     if (appData.noChessProgram || first.pr != NoProc) return;
9735
9736     StartChessProgram(&first);
9737     InitChessProgram(&first, FALSE);
9738     FeedMovesToProgram(&first, currentMove);
9739
9740     if (!first.sendTime) {
9741         /* can't tell gnuchess what its clock should read,
9742            so we bow to its notion. */
9743         ResetClocks();
9744         timeRemaining[0][currentMove] = whiteTimeRemaining;
9745         timeRemaining[1][currentMove] = blackTimeRemaining;
9746     }
9747
9748     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9749                 appData.icsEngineAnalyze) && first.analysisSupport) {
9750       SendToProgram("analyze\n", &first);
9751       first.analyzing = TRUE;
9752     }
9753 }
9754
9755 /*
9756  * Button procedures
9757  */
9758 void
9759 Reset(redraw, init)
9760      int redraw, init;
9761 {
9762     int i;
9763
9764     if (appData.debugMode) {
9765         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9766                 redraw, init, gameMode);
9767     }
9768     CleanupTail(); // [HGM] vari: delete any stored variations
9769     pausing = pauseExamInvalid = FALSE;
9770     startedFromSetupPosition = blackPlaysFirst = FALSE;
9771     firstMove = TRUE;
9772     whiteFlag = blackFlag = FALSE;
9773     userOfferedDraw = FALSE;
9774     hintRequested = bookRequested = FALSE;
9775     first.maybeThinking = FALSE;
9776     second.maybeThinking = FALSE;
9777     first.bookSuspend = FALSE; // [HGM] book
9778     second.bookSuspend = FALSE;
9779     thinkOutput[0] = NULLCHAR;
9780     lastHint[0] = NULLCHAR;
9781     ClearGameInfo(&gameInfo);
9782     gameInfo.variant = StringToVariant(appData.variant);
9783     ics_user_moved = ics_clock_paused = FALSE;
9784     ics_getting_history = H_FALSE;
9785     ics_gamenum = -1;
9786     white_holding[0] = black_holding[0] = NULLCHAR;
9787     ClearProgramStats();
9788     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9789
9790     ResetFrontEnd();
9791     ClearHighlights();
9792     flipView = appData.flipView;
9793     ClearPremoveHighlights();
9794     gotPremove = FALSE;
9795     alarmSounded = FALSE;
9796
9797     GameEnds(EndOfFile, NULL, GE_PLAYER);
9798     if(appData.serverMovesName != NULL) {
9799         /* [HGM] prepare to make moves file for broadcasting */
9800         clock_t t = clock();
9801         if(serverMoves != NULL) fclose(serverMoves);
9802         serverMoves = fopen(appData.serverMovesName, "r");
9803         if(serverMoves != NULL) {
9804             fclose(serverMoves);
9805             /* delay 15 sec before overwriting, so all clients can see end */
9806             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9807         }
9808         serverMoves = fopen(appData.serverMovesName, "w");
9809     }
9810
9811     ExitAnalyzeMode();
9812     gameMode = BeginningOfGame;
9813     ModeHighlight();
9814     if(appData.icsActive) gameInfo.variant = VariantNormal;
9815     currentMove = forwardMostMove = backwardMostMove = 0;
9816     InitPosition(redraw);
9817     for (i = 0; i < MAX_MOVES; i++) {
9818         if (commentList[i] != NULL) {
9819             free(commentList[i]);
9820             commentList[i] = NULL;
9821         }
9822     }
9823     ResetClocks();
9824     timeRemaining[0][0] = whiteTimeRemaining;
9825     timeRemaining[1][0] = blackTimeRemaining;
9826     if (first.pr == NULL) {
9827         StartChessProgram(&first);
9828     }
9829     if (init) {
9830             InitChessProgram(&first, startedFromSetupPosition);
9831     }
9832     DisplayTitle("");
9833     DisplayMessage("", "");
9834     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9835     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9836 }
9837
9838 void
9839 AutoPlayGameLoop()
9840 {
9841     for (;;) {
9842         if (!AutoPlayOneMove())
9843           return;
9844         if (matchMode || appData.timeDelay == 0)
9845           continue;
9846         if (appData.timeDelay < 0)
9847           return;
9848         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9849         break;
9850     }
9851 }
9852
9853
9854 int
9855 AutoPlayOneMove()
9856 {
9857     int fromX, fromY, toX, toY;
9858
9859     if (appData.debugMode) {
9860       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9861     }
9862
9863     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9864       return FALSE;
9865
9866     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9867       pvInfoList[currentMove].depth = programStats.depth;
9868       pvInfoList[currentMove].score = programStats.score;
9869       pvInfoList[currentMove].time  = 0;
9870       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9871     }
9872
9873     if (currentMove >= forwardMostMove) {
9874       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9875       gameMode = EditGame;
9876       ModeHighlight();
9877
9878       /* [AS] Clear current move marker at the end of a game */
9879       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9880
9881       return FALSE;
9882     }
9883
9884     toX = moveList[currentMove][2] - AAA;
9885     toY = moveList[currentMove][3] - ONE;
9886
9887     if (moveList[currentMove][1] == '@') {
9888         if (appData.highlightLastMove) {
9889             SetHighlights(-1, -1, toX, toY);
9890         }
9891     } else {
9892         fromX = moveList[currentMove][0] - AAA;
9893         fromY = moveList[currentMove][1] - ONE;
9894
9895         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9896
9897         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9898
9899         if (appData.highlightLastMove) {
9900             SetHighlights(fromX, fromY, toX, toY);
9901         }
9902     }
9903     DisplayMove(currentMove);
9904     SendMoveToProgram(currentMove++, &first);
9905     DisplayBothClocks();
9906     DrawPosition(FALSE, boards[currentMove]);
9907     // [HGM] PV info: always display, routine tests if empty
9908     DisplayComment(currentMove - 1, commentList[currentMove]);
9909     return TRUE;
9910 }
9911
9912
9913 int
9914 LoadGameOneMove(readAhead)
9915      ChessMove readAhead;
9916 {
9917     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9918     char promoChar = NULLCHAR;
9919     ChessMove moveType;
9920     char move[MSG_SIZ];
9921     char *p, *q;
9922
9923     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9924         gameMode != AnalyzeMode && gameMode != Training) {
9925         gameFileFP = NULL;
9926         return FALSE;
9927     }
9928
9929     yyboardindex = forwardMostMove;
9930     if (readAhead != EndOfFile) {
9931       moveType = readAhead;
9932     } else {
9933       if (gameFileFP == NULL)
9934           return FALSE;
9935       moveType = (ChessMove) Myylex();
9936     }
9937
9938     done = FALSE;
9939     switch (moveType) {
9940       case Comment:
9941         if (appData.debugMode)
9942           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9943         p = yy_text;
9944
9945         /* append the comment but don't display it */
9946         AppendComment(currentMove, p, FALSE);
9947         return TRUE;
9948
9949       case WhiteCapturesEnPassant:
9950       case BlackCapturesEnPassant:
9951       case WhitePromotion:
9952       case BlackPromotion:
9953       case WhiteNonPromotion:
9954       case BlackNonPromotion:
9955       case NormalMove:
9956       case WhiteKingSideCastle:
9957       case WhiteQueenSideCastle:
9958       case BlackKingSideCastle:
9959       case BlackQueenSideCastle:
9960       case WhiteKingSideCastleWild:
9961       case WhiteQueenSideCastleWild:
9962       case BlackKingSideCastleWild:
9963       case BlackQueenSideCastleWild:
9964       /* PUSH Fabien */
9965       case WhiteHSideCastleFR:
9966       case WhiteASideCastleFR:
9967       case BlackHSideCastleFR:
9968       case BlackASideCastleFR:
9969       /* POP Fabien */
9970         if (appData.debugMode)
9971           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9972         fromX = currentMoveString[0] - AAA;
9973         fromY = currentMoveString[1] - ONE;
9974         toX = currentMoveString[2] - AAA;
9975         toY = currentMoveString[3] - ONE;
9976         promoChar = currentMoveString[4];
9977         break;
9978
9979       case WhiteDrop:
9980       case BlackDrop:
9981         if (appData.debugMode)
9982           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9983         fromX = moveType == WhiteDrop ?
9984           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9985         (int) CharToPiece(ToLower(currentMoveString[0]));
9986         fromY = DROP_RANK;
9987         toX = currentMoveString[2] - AAA;
9988         toY = currentMoveString[3] - ONE;
9989         break;
9990
9991       case WhiteWins:
9992       case BlackWins:
9993       case GameIsDrawn:
9994       case GameUnfinished:
9995         if (appData.debugMode)
9996           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9997         p = strchr(yy_text, '{');
9998         if (p == NULL) p = strchr(yy_text, '(');
9999         if (p == NULL) {
10000             p = yy_text;
10001             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10002         } else {
10003             q = strchr(p, *p == '{' ? '}' : ')');
10004             if (q != NULL) *q = NULLCHAR;
10005             p++;
10006         }
10007         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10008         GameEnds(moveType, p, GE_FILE);
10009         done = TRUE;
10010         if (cmailMsgLoaded) {
10011             ClearHighlights();
10012             flipView = WhiteOnMove(currentMove);
10013             if (moveType == GameUnfinished) flipView = !flipView;
10014             if (appData.debugMode)
10015               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10016         }
10017         break;
10018
10019       case EndOfFile:
10020         if (appData.debugMode)
10021           fprintf(debugFP, "Parser hit end of file\n");
10022         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10023           case MT_NONE:
10024           case MT_CHECK:
10025             break;
10026           case MT_CHECKMATE:
10027           case MT_STAINMATE:
10028             if (WhiteOnMove(currentMove)) {
10029                 GameEnds(BlackWins, "Black mates", GE_FILE);
10030             } else {
10031                 GameEnds(WhiteWins, "White mates", GE_FILE);
10032             }
10033             break;
10034           case MT_STALEMATE:
10035             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10036             break;
10037         }
10038         done = TRUE;
10039         break;
10040
10041       case MoveNumberOne:
10042         if (lastLoadGameStart == GNUChessGame) {
10043             /* GNUChessGames have numbers, but they aren't move numbers */
10044             if (appData.debugMode)
10045               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10046                       yy_text, (int) moveType);
10047             return LoadGameOneMove(EndOfFile); /* tail recursion */
10048         }
10049         /* else fall thru */
10050
10051       case XBoardGame:
10052       case GNUChessGame:
10053       case PGNTag:
10054         /* Reached start of next game in file */
10055         if (appData.debugMode)
10056           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10057         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10058           case MT_NONE:
10059           case MT_CHECK:
10060             break;
10061           case MT_CHECKMATE:
10062           case MT_STAINMATE:
10063             if (WhiteOnMove(currentMove)) {
10064                 GameEnds(BlackWins, "Black mates", GE_FILE);
10065             } else {
10066                 GameEnds(WhiteWins, "White mates", GE_FILE);
10067             }
10068             break;
10069           case MT_STALEMATE:
10070             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10071             break;
10072         }
10073         done = TRUE;
10074         break;
10075
10076       case PositionDiagram:     /* should not happen; ignore */
10077       case ElapsedTime:         /* ignore */
10078       case NAG:                 /* ignore */
10079         if (appData.debugMode)
10080           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10081                   yy_text, (int) moveType);
10082         return LoadGameOneMove(EndOfFile); /* tail recursion */
10083
10084       case IllegalMove:
10085         if (appData.testLegality) {
10086             if (appData.debugMode)
10087               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10088             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10089                     (forwardMostMove / 2) + 1,
10090                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10091             DisplayError(move, 0);
10092             done = TRUE;
10093         } else {
10094             if (appData.debugMode)
10095               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10096                       yy_text, currentMoveString);
10097             fromX = currentMoveString[0] - AAA;
10098             fromY = currentMoveString[1] - ONE;
10099             toX = currentMoveString[2] - AAA;
10100             toY = currentMoveString[3] - ONE;
10101             promoChar = currentMoveString[4];
10102         }
10103         break;
10104
10105       case AmbiguousMove:
10106         if (appData.debugMode)
10107           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10108         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10109                 (forwardMostMove / 2) + 1,
10110                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10111         DisplayError(move, 0);
10112         done = TRUE;
10113         break;
10114
10115       default:
10116       case ImpossibleMove:
10117         if (appData.debugMode)
10118           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10119         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10120                 (forwardMostMove / 2) + 1,
10121                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10122         DisplayError(move, 0);
10123         done = TRUE;
10124         break;
10125     }
10126
10127     if (done) {
10128         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10129             DrawPosition(FALSE, boards[currentMove]);
10130             DisplayBothClocks();
10131             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10132               DisplayComment(currentMove - 1, commentList[currentMove]);
10133         }
10134         (void) StopLoadGameTimer();
10135         gameFileFP = NULL;
10136         cmailOldMove = forwardMostMove;
10137         return FALSE;
10138     } else {
10139         /* currentMoveString is set as a side-effect of yylex */
10140
10141         thinkOutput[0] = NULLCHAR;
10142         MakeMove(fromX, fromY, toX, toY, promoChar);
10143         currentMove = forwardMostMove;
10144         return TRUE;
10145     }
10146 }
10147
10148 /* Load the nth game from the given file */
10149 int
10150 LoadGameFromFile(filename, n, title, useList)
10151      char *filename;
10152      int n;
10153      char *title;
10154      /*Boolean*/ int useList;
10155 {
10156     FILE *f;
10157     char buf[MSG_SIZ];
10158
10159     if (strcmp(filename, "-") == 0) {
10160         f = stdin;
10161         title = "stdin";
10162     } else {
10163         f = fopen(filename, "rb");
10164         if (f == NULL) {
10165           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10166             DisplayError(buf, errno);
10167             return FALSE;
10168         }
10169     }
10170     if (fseek(f, 0, 0) == -1) {
10171         /* f is not seekable; probably a pipe */
10172         useList = FALSE;
10173     }
10174     if (useList && n == 0) {
10175         int error = GameListBuild(f);
10176         if (error) {
10177             DisplayError(_("Cannot build game list"), error);
10178         } else if (!ListEmpty(&gameList) &&
10179                    ((ListGame *) gameList.tailPred)->number > 1) {
10180             GameListPopUp(f, title);
10181             return TRUE;
10182         }
10183         GameListDestroy();
10184         n = 1;
10185     }
10186     if (n == 0) n = 1;
10187     return LoadGame(f, n, title, FALSE);
10188 }
10189
10190
10191 void
10192 MakeRegisteredMove()
10193 {
10194     int fromX, fromY, toX, toY;
10195     char promoChar;
10196     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10197         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10198           case CMAIL_MOVE:
10199           case CMAIL_DRAW:
10200             if (appData.debugMode)
10201               fprintf(debugFP, "Restoring %s for game %d\n",
10202                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10203
10204             thinkOutput[0] = NULLCHAR;
10205             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10206             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10207             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10208             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10209             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10210             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10211             MakeMove(fromX, fromY, toX, toY, promoChar);
10212             ShowMove(fromX, fromY, toX, toY);
10213
10214             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10215               case MT_NONE:
10216               case MT_CHECK:
10217                 break;
10218
10219               case MT_CHECKMATE:
10220               case MT_STAINMATE:
10221                 if (WhiteOnMove(currentMove)) {
10222                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10223                 } else {
10224                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10225                 }
10226                 break;
10227
10228               case MT_STALEMATE:
10229                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10230                 break;
10231             }
10232
10233             break;
10234
10235           case CMAIL_RESIGN:
10236             if (WhiteOnMove(currentMove)) {
10237                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10238             } else {
10239                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10240             }
10241             break;
10242
10243           case CMAIL_ACCEPT:
10244             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10245             break;
10246
10247           default:
10248             break;
10249         }
10250     }
10251
10252     return;
10253 }
10254
10255 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10256 int
10257 CmailLoadGame(f, gameNumber, title, useList)
10258      FILE *f;
10259      int gameNumber;
10260      char *title;
10261      int useList;
10262 {
10263     int retVal;
10264
10265     if (gameNumber > nCmailGames) {
10266         DisplayError(_("No more games in this message"), 0);
10267         return FALSE;
10268     }
10269     if (f == lastLoadGameFP) {
10270         int offset = gameNumber - lastLoadGameNumber;
10271         if (offset == 0) {
10272             cmailMsg[0] = NULLCHAR;
10273             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10274                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10275                 nCmailMovesRegistered--;
10276             }
10277             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10278             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10279                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10280             }
10281         } else {
10282             if (! RegisterMove()) return FALSE;
10283         }
10284     }
10285
10286     retVal = LoadGame(f, gameNumber, title, useList);
10287
10288     /* Make move registered during previous look at this game, if any */
10289     MakeRegisteredMove();
10290
10291     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10292         commentList[currentMove]
10293           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10294         DisplayComment(currentMove - 1, commentList[currentMove]);
10295     }
10296
10297     return retVal;
10298 }
10299
10300 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10301 int
10302 ReloadGame(offset)
10303      int offset;
10304 {
10305     int gameNumber = lastLoadGameNumber + offset;
10306     if (lastLoadGameFP == NULL) {
10307         DisplayError(_("No game has been loaded yet"), 0);
10308         return FALSE;
10309     }
10310     if (gameNumber <= 0) {
10311         DisplayError(_("Can't back up any further"), 0);
10312         return FALSE;
10313     }
10314     if (cmailMsgLoaded) {
10315         return CmailLoadGame(lastLoadGameFP, gameNumber,
10316                              lastLoadGameTitle, lastLoadGameUseList);
10317     } else {
10318         return LoadGame(lastLoadGameFP, gameNumber,
10319                         lastLoadGameTitle, lastLoadGameUseList);
10320     }
10321 }
10322
10323
10324
10325 /* Load the nth game from open file f */
10326 int
10327 LoadGame(f, gameNumber, title, useList)
10328      FILE *f;
10329      int gameNumber;
10330      char *title;
10331      int useList;
10332 {
10333     ChessMove cm;
10334     char buf[MSG_SIZ];
10335     int gn = gameNumber;
10336     ListGame *lg = NULL;
10337     int numPGNTags = 0;
10338     int err;
10339     GameMode oldGameMode;
10340     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10341
10342     if (appData.debugMode)
10343         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10344
10345     if (gameMode == Training )
10346         SetTrainingModeOff();
10347
10348     oldGameMode = gameMode;
10349     if (gameMode != BeginningOfGame) {
10350       Reset(FALSE, TRUE);
10351     }
10352
10353     gameFileFP = f;
10354     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10355         fclose(lastLoadGameFP);
10356     }
10357
10358     if (useList) {
10359         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10360
10361         if (lg) {
10362             fseek(f, lg->offset, 0);
10363             GameListHighlight(gameNumber);
10364             gn = 1;
10365         }
10366         else {
10367             DisplayError(_("Game number out of range"), 0);
10368             return FALSE;
10369         }
10370     } else {
10371         GameListDestroy();
10372         if (fseek(f, 0, 0) == -1) {
10373             if (f == lastLoadGameFP ?
10374                 gameNumber == lastLoadGameNumber + 1 :
10375                 gameNumber == 1) {
10376                 gn = 1;
10377             } else {
10378                 DisplayError(_("Can't seek on game file"), 0);
10379                 return FALSE;
10380             }
10381         }
10382     }
10383     lastLoadGameFP = f;
10384     lastLoadGameNumber = gameNumber;
10385     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10386     lastLoadGameUseList = useList;
10387
10388     yynewfile(f);
10389
10390     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10391       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10392                 lg->gameInfo.black);
10393             DisplayTitle(buf);
10394     } else if (*title != NULLCHAR) {
10395         if (gameNumber > 1) {
10396           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10397             DisplayTitle(buf);
10398         } else {
10399             DisplayTitle(title);
10400         }
10401     }
10402
10403     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10404         gameMode = PlayFromGameFile;
10405         ModeHighlight();
10406     }
10407
10408     currentMove = forwardMostMove = backwardMostMove = 0;
10409     CopyBoard(boards[0], initialPosition);
10410     StopClocks();
10411
10412     /*
10413      * Skip the first gn-1 games in the file.
10414      * Also skip over anything that precedes an identifiable
10415      * start of game marker, to avoid being confused by
10416      * garbage at the start of the file.  Currently
10417      * recognized start of game markers are the move number "1",
10418      * the pattern "gnuchess .* game", the pattern
10419      * "^[#;%] [^ ]* game file", and a PGN tag block.
10420      * A game that starts with one of the latter two patterns
10421      * will also have a move number 1, possibly
10422      * following a position diagram.
10423      * 5-4-02: Let's try being more lenient and allowing a game to
10424      * start with an unnumbered move.  Does that break anything?
10425      */
10426     cm = lastLoadGameStart = EndOfFile;
10427     while (gn > 0) {
10428         yyboardindex = forwardMostMove;
10429         cm = (ChessMove) Myylex();
10430         switch (cm) {
10431           case EndOfFile:
10432             if (cmailMsgLoaded) {
10433                 nCmailGames = CMAIL_MAX_GAMES - gn;
10434             } else {
10435                 Reset(TRUE, TRUE);
10436                 DisplayError(_("Game not found in file"), 0);
10437             }
10438             return FALSE;
10439
10440           case GNUChessGame:
10441           case XBoardGame:
10442             gn--;
10443             lastLoadGameStart = cm;
10444             break;
10445
10446           case MoveNumberOne:
10447             switch (lastLoadGameStart) {
10448               case GNUChessGame:
10449               case XBoardGame:
10450               case PGNTag:
10451                 break;
10452               case MoveNumberOne:
10453               case EndOfFile:
10454                 gn--;           /* count this game */
10455                 lastLoadGameStart = cm;
10456                 break;
10457               default:
10458                 /* impossible */
10459                 break;
10460             }
10461             break;
10462
10463           case PGNTag:
10464             switch (lastLoadGameStart) {
10465               case GNUChessGame:
10466               case PGNTag:
10467               case MoveNumberOne:
10468               case EndOfFile:
10469                 gn--;           /* count this game */
10470                 lastLoadGameStart = cm;
10471                 break;
10472               case XBoardGame:
10473                 lastLoadGameStart = cm; /* game counted already */
10474                 break;
10475               default:
10476                 /* impossible */
10477                 break;
10478             }
10479             if (gn > 0) {
10480                 do {
10481                     yyboardindex = forwardMostMove;
10482                     cm = (ChessMove) Myylex();
10483                 } while (cm == PGNTag || cm == Comment);
10484             }
10485             break;
10486
10487           case WhiteWins:
10488           case BlackWins:
10489           case GameIsDrawn:
10490             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10491                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10492                     != CMAIL_OLD_RESULT) {
10493                     nCmailResults ++ ;
10494                     cmailResult[  CMAIL_MAX_GAMES
10495                                 - gn - 1] = CMAIL_OLD_RESULT;
10496                 }
10497             }
10498             break;
10499
10500           case NormalMove:
10501             /* Only a NormalMove can be at the start of a game
10502              * without a position diagram. */
10503             if (lastLoadGameStart == EndOfFile ) {
10504               gn--;
10505               lastLoadGameStart = MoveNumberOne;
10506             }
10507             break;
10508
10509           default:
10510             break;
10511         }
10512     }
10513
10514     if (appData.debugMode)
10515       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10516
10517     if (cm == XBoardGame) {
10518         /* Skip any header junk before position diagram and/or move 1 */
10519         for (;;) {
10520             yyboardindex = forwardMostMove;
10521             cm = (ChessMove) Myylex();
10522
10523             if (cm == EndOfFile ||
10524                 cm == GNUChessGame || cm == XBoardGame) {
10525                 /* Empty game; pretend end-of-file and handle later */
10526                 cm = EndOfFile;
10527                 break;
10528             }
10529
10530             if (cm == MoveNumberOne || cm == PositionDiagram ||
10531                 cm == PGNTag || cm == Comment)
10532               break;
10533         }
10534     } else if (cm == GNUChessGame) {
10535         if (gameInfo.event != NULL) {
10536             free(gameInfo.event);
10537         }
10538         gameInfo.event = StrSave(yy_text);
10539     }
10540
10541     startedFromSetupPosition = FALSE;
10542     while (cm == PGNTag) {
10543         if (appData.debugMode)
10544           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10545         err = ParsePGNTag(yy_text, &gameInfo);
10546         if (!err) numPGNTags++;
10547
10548         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10549         if(gameInfo.variant != oldVariant) {
10550             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10551             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10552             InitPosition(TRUE);
10553             oldVariant = gameInfo.variant;
10554             if (appData.debugMode)
10555               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10556         }
10557
10558
10559         if (gameInfo.fen != NULL) {
10560           Board initial_position;
10561           startedFromSetupPosition = TRUE;
10562           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10563             Reset(TRUE, TRUE);
10564             DisplayError(_("Bad FEN position in file"), 0);
10565             return FALSE;
10566           }
10567           CopyBoard(boards[0], initial_position);
10568           if (blackPlaysFirst) {
10569             currentMove = forwardMostMove = backwardMostMove = 1;
10570             CopyBoard(boards[1], initial_position);
10571             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10572             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10573             timeRemaining[0][1] = whiteTimeRemaining;
10574             timeRemaining[1][1] = blackTimeRemaining;
10575             if (commentList[0] != NULL) {
10576               commentList[1] = commentList[0];
10577               commentList[0] = NULL;
10578             }
10579           } else {
10580             currentMove = forwardMostMove = backwardMostMove = 0;
10581           }
10582           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10583           {   int i;
10584               initialRulePlies = FENrulePlies;
10585               for( i=0; i< nrCastlingRights; i++ )
10586                   initialRights[i] = initial_position[CASTLING][i];
10587           }
10588           yyboardindex = forwardMostMove;
10589           free(gameInfo.fen);
10590           gameInfo.fen = NULL;
10591         }
10592
10593         yyboardindex = forwardMostMove;
10594         cm = (ChessMove) Myylex();
10595
10596         /* Handle comments interspersed among the tags */
10597         while (cm == Comment) {
10598             char *p;
10599             if (appData.debugMode)
10600               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10601             p = yy_text;
10602             AppendComment(currentMove, p, FALSE);
10603             yyboardindex = forwardMostMove;
10604             cm = (ChessMove) Myylex();
10605         }
10606     }
10607
10608     /* don't rely on existence of Event tag since if game was
10609      * pasted from clipboard the Event tag may not exist
10610      */
10611     if (numPGNTags > 0){
10612         char *tags;
10613         if (gameInfo.variant == VariantNormal) {
10614           VariantClass v = StringToVariant(gameInfo.event);
10615           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10616           if(v < VariantShogi) gameInfo.variant = v;
10617         }
10618         if (!matchMode) {
10619           if( appData.autoDisplayTags ) {
10620             tags = PGNTags(&gameInfo);
10621             TagsPopUp(tags, CmailMsg());
10622             free(tags);
10623           }
10624         }
10625     } else {
10626         /* Make something up, but don't display it now */
10627         SetGameInfo();
10628         TagsPopDown();
10629     }
10630
10631     if (cm == PositionDiagram) {
10632         int i, j;
10633         char *p;
10634         Board initial_position;
10635
10636         if (appData.debugMode)
10637           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10638
10639         if (!startedFromSetupPosition) {
10640             p = yy_text;
10641             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10642               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10643                 switch (*p) {
10644                   case '{':
10645                   case '[':
10646                   case '-':
10647                   case ' ':
10648                   case '\t':
10649                   case '\n':
10650                   case '\r':
10651                     break;
10652                   default:
10653                     initial_position[i][j++] = CharToPiece(*p);
10654                     break;
10655                 }
10656             while (*p == ' ' || *p == '\t' ||
10657                    *p == '\n' || *p == '\r') p++;
10658
10659             if (strncmp(p, "black", strlen("black"))==0)
10660               blackPlaysFirst = TRUE;
10661             else
10662               blackPlaysFirst = FALSE;
10663             startedFromSetupPosition = TRUE;
10664
10665             CopyBoard(boards[0], initial_position);
10666             if (blackPlaysFirst) {
10667                 currentMove = forwardMostMove = backwardMostMove = 1;
10668                 CopyBoard(boards[1], initial_position);
10669                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10670                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10671                 timeRemaining[0][1] = whiteTimeRemaining;
10672                 timeRemaining[1][1] = blackTimeRemaining;
10673                 if (commentList[0] != NULL) {
10674                     commentList[1] = commentList[0];
10675                     commentList[0] = NULL;
10676                 }
10677             } else {
10678                 currentMove = forwardMostMove = backwardMostMove = 0;
10679             }
10680         }
10681         yyboardindex = forwardMostMove;
10682         cm = (ChessMove) Myylex();
10683     }
10684
10685     if (first.pr == NoProc) {
10686         StartChessProgram(&first);
10687     }
10688     InitChessProgram(&first, FALSE);
10689     SendToProgram("force\n", &first);
10690     if (startedFromSetupPosition) {
10691         SendBoard(&first, forwardMostMove);
10692     if (appData.debugMode) {
10693         fprintf(debugFP, "Load Game\n");
10694     }
10695         DisplayBothClocks();
10696     }
10697
10698     /* [HGM] server: flag to write setup moves in broadcast file as one */
10699     loadFlag = appData.suppressLoadMoves;
10700
10701     while (cm == Comment) {
10702         char *p;
10703         if (appData.debugMode)
10704           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10705         p = yy_text;
10706         AppendComment(currentMove, p, FALSE);
10707         yyboardindex = forwardMostMove;
10708         cm = (ChessMove) Myylex();
10709     }
10710
10711     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10712         cm == WhiteWins || cm == BlackWins ||
10713         cm == GameIsDrawn || cm == GameUnfinished) {
10714         DisplayMessage("", _("No moves in game"));
10715         if (cmailMsgLoaded) {
10716             if (appData.debugMode)
10717               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10718             ClearHighlights();
10719             flipView = FALSE;
10720         }
10721         DrawPosition(FALSE, boards[currentMove]);
10722         DisplayBothClocks();
10723         gameMode = EditGame;
10724         ModeHighlight();
10725         gameFileFP = NULL;
10726         cmailOldMove = 0;
10727         return TRUE;
10728     }
10729
10730     // [HGM] PV info: routine tests if comment empty
10731     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10732         DisplayComment(currentMove - 1, commentList[currentMove]);
10733     }
10734     if (!matchMode && appData.timeDelay != 0)
10735       DrawPosition(FALSE, boards[currentMove]);
10736
10737     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10738       programStats.ok_to_send = 1;
10739     }
10740
10741     /* if the first token after the PGN tags is a move
10742      * and not move number 1, retrieve it from the parser
10743      */
10744     if (cm != MoveNumberOne)
10745         LoadGameOneMove(cm);
10746
10747     /* load the remaining moves from the file */
10748     while (LoadGameOneMove(EndOfFile)) {
10749       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10750       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10751     }
10752
10753     /* rewind to the start of the game */
10754     currentMove = backwardMostMove;
10755
10756     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10757
10758     if (oldGameMode == AnalyzeFile ||
10759         oldGameMode == AnalyzeMode) {
10760       AnalyzeFileEvent();
10761     }
10762
10763     if (matchMode || appData.timeDelay == 0) {
10764       ToEndEvent();
10765       gameMode = EditGame;
10766       ModeHighlight();
10767     } else if (appData.timeDelay > 0) {
10768       AutoPlayGameLoop();
10769     }
10770
10771     if (appData.debugMode)
10772         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10773
10774     loadFlag = 0; /* [HGM] true game starts */
10775     return TRUE;
10776 }
10777
10778 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10779 int
10780 ReloadPosition(offset)
10781      int offset;
10782 {
10783     int positionNumber = lastLoadPositionNumber + offset;
10784     if (lastLoadPositionFP == NULL) {
10785         DisplayError(_("No position has been loaded yet"), 0);
10786         return FALSE;
10787     }
10788     if (positionNumber <= 0) {
10789         DisplayError(_("Can't back up any further"), 0);
10790         return FALSE;
10791     }
10792     return LoadPosition(lastLoadPositionFP, positionNumber,
10793                         lastLoadPositionTitle);
10794 }
10795
10796 /* Load the nth position from the given file */
10797 int
10798 LoadPositionFromFile(filename, n, title)
10799      char *filename;
10800      int n;
10801      char *title;
10802 {
10803     FILE *f;
10804     char buf[MSG_SIZ];
10805
10806     if (strcmp(filename, "-") == 0) {
10807         return LoadPosition(stdin, n, "stdin");
10808     } else {
10809         f = fopen(filename, "rb");
10810         if (f == NULL) {
10811             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10812             DisplayError(buf, errno);
10813             return FALSE;
10814         } else {
10815             return LoadPosition(f, n, title);
10816         }
10817     }
10818 }
10819
10820 /* Load the nth position from the given open file, and close it */
10821 int
10822 LoadPosition(f, positionNumber, title)
10823      FILE *f;
10824      int positionNumber;
10825      char *title;
10826 {
10827     char *p, line[MSG_SIZ];
10828     Board initial_position;
10829     int i, j, fenMode, pn;
10830
10831     if (gameMode == Training )
10832         SetTrainingModeOff();
10833
10834     if (gameMode != BeginningOfGame) {
10835         Reset(FALSE, TRUE);
10836     }
10837     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10838         fclose(lastLoadPositionFP);
10839     }
10840     if (positionNumber == 0) positionNumber = 1;
10841     lastLoadPositionFP = f;
10842     lastLoadPositionNumber = positionNumber;
10843     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10844     if (first.pr == NoProc) {
10845       StartChessProgram(&first);
10846       InitChessProgram(&first, FALSE);
10847     }
10848     pn = positionNumber;
10849     if (positionNumber < 0) {
10850         /* Negative position number means to seek to that byte offset */
10851         if (fseek(f, -positionNumber, 0) == -1) {
10852             DisplayError(_("Can't seek on position file"), 0);
10853             return FALSE;
10854         };
10855         pn = 1;
10856     } else {
10857         if (fseek(f, 0, 0) == -1) {
10858             if (f == lastLoadPositionFP ?
10859                 positionNumber == lastLoadPositionNumber + 1 :
10860                 positionNumber == 1) {
10861                 pn = 1;
10862             } else {
10863                 DisplayError(_("Can't seek on position file"), 0);
10864                 return FALSE;
10865             }
10866         }
10867     }
10868     /* See if this file is FEN or old-style xboard */
10869     if (fgets(line, MSG_SIZ, f) == NULL) {
10870         DisplayError(_("Position not found in file"), 0);
10871         return FALSE;
10872     }
10873     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10874     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10875
10876     if (pn >= 2) {
10877         if (fenMode || line[0] == '#') pn--;
10878         while (pn > 0) {
10879             /* skip positions before number pn */
10880             if (fgets(line, MSG_SIZ, f) == NULL) {
10881                 Reset(TRUE, TRUE);
10882                 DisplayError(_("Position not found in file"), 0);
10883                 return FALSE;
10884             }
10885             if (fenMode || line[0] == '#') pn--;
10886         }
10887     }
10888
10889     if (fenMode) {
10890         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10891             DisplayError(_("Bad FEN position in file"), 0);
10892             return FALSE;
10893         }
10894     } else {
10895         (void) fgets(line, MSG_SIZ, f);
10896         (void) fgets(line, MSG_SIZ, f);
10897
10898         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10899             (void) fgets(line, MSG_SIZ, f);
10900             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10901                 if (*p == ' ')
10902                   continue;
10903                 initial_position[i][j++] = CharToPiece(*p);
10904             }
10905         }
10906
10907         blackPlaysFirst = FALSE;
10908         if (!feof(f)) {
10909             (void) fgets(line, MSG_SIZ, f);
10910             if (strncmp(line, "black", strlen("black"))==0)
10911               blackPlaysFirst = TRUE;
10912         }
10913     }
10914     startedFromSetupPosition = TRUE;
10915
10916     SendToProgram("force\n", &first);
10917     CopyBoard(boards[0], initial_position);
10918     if (blackPlaysFirst) {
10919         currentMove = forwardMostMove = backwardMostMove = 1;
10920         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10921         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10922         CopyBoard(boards[1], initial_position);
10923         DisplayMessage("", _("Black to play"));
10924     } else {
10925         currentMove = forwardMostMove = backwardMostMove = 0;
10926         DisplayMessage("", _("White to play"));
10927     }
10928     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10929     SendBoard(&first, forwardMostMove);
10930     if (appData.debugMode) {
10931 int i, j;
10932   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10933   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10934         fprintf(debugFP, "Load Position\n");
10935     }
10936
10937     if (positionNumber > 1) {
10938       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10939         DisplayTitle(line);
10940     } else {
10941         DisplayTitle(title);
10942     }
10943     gameMode = EditGame;
10944     ModeHighlight();
10945     ResetClocks();
10946     timeRemaining[0][1] = whiteTimeRemaining;
10947     timeRemaining[1][1] = blackTimeRemaining;
10948     DrawPosition(FALSE, boards[currentMove]);
10949
10950     return TRUE;
10951 }
10952
10953
10954 void
10955 CopyPlayerNameIntoFileName(dest, src)
10956      char **dest, *src;
10957 {
10958     while (*src != NULLCHAR && *src != ',') {
10959         if (*src == ' ') {
10960             *(*dest)++ = '_';
10961             src++;
10962         } else {
10963             *(*dest)++ = *src++;
10964         }
10965     }
10966 }
10967
10968 char *DefaultFileName(ext)
10969      char *ext;
10970 {
10971     static char def[MSG_SIZ];
10972     char *p;
10973
10974     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10975         p = def;
10976         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10977         *p++ = '-';
10978         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10979         *p++ = '.';
10980         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10981     } else {
10982         def[0] = NULLCHAR;
10983     }
10984     return def;
10985 }
10986
10987 /* Save the current game to the given file */
10988 int
10989 SaveGameToFile(filename, append)
10990      char *filename;
10991      int append;
10992 {
10993     FILE *f;
10994     char buf[MSG_SIZ];
10995     int result;
10996
10997     if (strcmp(filename, "-") == 0) {
10998         return SaveGame(stdout, 0, NULL);
10999     } else {
11000         f = fopen(filename, append ? "a" : "w");
11001         if (f == NULL) {
11002             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11003             DisplayError(buf, errno);
11004             return FALSE;
11005         } else {
11006             safeStrCpy(buf, lastMsg, MSG_SIZ);
11007             DisplayMessage(_("Waiting for access to save file"), "");
11008             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11009             DisplayMessage(_("Saving game"), "");
11010             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11011             result = SaveGame(f, 0, NULL);
11012             DisplayMessage(buf, "");
11013             return result;
11014         }
11015     }
11016 }
11017
11018 char *
11019 SavePart(str)
11020      char *str;
11021 {
11022     static char buf[MSG_SIZ];
11023     char *p;
11024
11025     p = strchr(str, ' ');
11026     if (p == NULL) return str;
11027     strncpy(buf, str, p - str);
11028     buf[p - str] = NULLCHAR;
11029     return buf;
11030 }
11031
11032 #define PGN_MAX_LINE 75
11033
11034 #define PGN_SIDE_WHITE  0
11035 #define PGN_SIDE_BLACK  1
11036
11037 /* [AS] */
11038 static int FindFirstMoveOutOfBook( int side )
11039 {
11040     int result = -1;
11041
11042     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11043         int index = backwardMostMove;
11044         int has_book_hit = 0;
11045
11046         if( (index % 2) != side ) {
11047             index++;
11048         }
11049
11050         while( index < forwardMostMove ) {
11051             /* Check to see if engine is in book */
11052             int depth = pvInfoList[index].depth;
11053             int score = pvInfoList[index].score;
11054             int in_book = 0;
11055
11056             if( depth <= 2 ) {
11057                 in_book = 1;
11058             }
11059             else if( score == 0 && depth == 63 ) {
11060                 in_book = 1; /* Zappa */
11061             }
11062             else if( score == 2 && depth == 99 ) {
11063                 in_book = 1; /* Abrok */
11064             }
11065
11066             has_book_hit += in_book;
11067
11068             if( ! in_book ) {
11069                 result = index;
11070
11071                 break;
11072             }
11073
11074             index += 2;
11075         }
11076     }
11077
11078     return result;
11079 }
11080
11081 /* [AS] */
11082 void GetOutOfBookInfo( char * buf )
11083 {
11084     int oob[2];
11085     int i;
11086     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11087
11088     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11089     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11090
11091     *buf = '\0';
11092
11093     if( oob[0] >= 0 || oob[1] >= 0 ) {
11094         for( i=0; i<2; i++ ) {
11095             int idx = oob[i];
11096
11097             if( idx >= 0 ) {
11098                 if( i > 0 && oob[0] >= 0 ) {
11099                     strcat( buf, "   " );
11100                 }
11101
11102                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11103                 sprintf( buf+strlen(buf), "%s%.2f",
11104                     pvInfoList[idx].score >= 0 ? "+" : "",
11105                     pvInfoList[idx].score / 100.0 );
11106             }
11107         }
11108     }
11109 }
11110
11111 /* Save game in PGN style and close the file */
11112 int
11113 SaveGamePGN(f)
11114      FILE *f;
11115 {
11116     int i, offset, linelen, newblock;
11117     time_t tm;
11118 //    char *movetext;
11119     char numtext[32];
11120     int movelen, numlen, blank;
11121     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11122
11123     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11124
11125     tm = time((time_t *) NULL);
11126
11127     PrintPGNTags(f, &gameInfo);
11128
11129     if (backwardMostMove > 0 || startedFromSetupPosition) {
11130         char *fen = PositionToFEN(backwardMostMove, NULL);
11131         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11132         fprintf(f, "\n{--------------\n");
11133         PrintPosition(f, backwardMostMove);
11134         fprintf(f, "--------------}\n");
11135         free(fen);
11136     }
11137     else {
11138         /* [AS] Out of book annotation */
11139         if( appData.saveOutOfBookInfo ) {
11140             char buf[64];
11141
11142             GetOutOfBookInfo( buf );
11143
11144             if( buf[0] != '\0' ) {
11145                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11146             }
11147         }
11148
11149         fprintf(f, "\n");
11150     }
11151
11152     i = backwardMostMove;
11153     linelen = 0;
11154     newblock = TRUE;
11155
11156     while (i < forwardMostMove) {
11157         /* Print comments preceding this move */
11158         if (commentList[i] != NULL) {
11159             if (linelen > 0) fprintf(f, "\n");
11160             fprintf(f, "%s", commentList[i]);
11161             linelen = 0;
11162             newblock = TRUE;
11163         }
11164
11165         /* Format move number */
11166         if ((i % 2) == 0)
11167           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11168         else
11169           if (newblock)
11170             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11171           else
11172             numtext[0] = NULLCHAR;
11173
11174         numlen = strlen(numtext);
11175         newblock = FALSE;
11176
11177         /* Print move number */
11178         blank = linelen > 0 && numlen > 0;
11179         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11180             fprintf(f, "\n");
11181             linelen = 0;
11182             blank = 0;
11183         }
11184         if (blank) {
11185             fprintf(f, " ");
11186             linelen++;
11187         }
11188         fprintf(f, "%s", numtext);
11189         linelen += numlen;
11190
11191         /* Get move */
11192         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11193         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11194
11195         /* Print move */
11196         blank = linelen > 0 && movelen > 0;
11197         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11198             fprintf(f, "\n");
11199             linelen = 0;
11200             blank = 0;
11201         }
11202         if (blank) {
11203             fprintf(f, " ");
11204             linelen++;
11205         }
11206         fprintf(f, "%s", move_buffer);
11207         linelen += movelen;
11208
11209         /* [AS] Add PV info if present */
11210         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11211             /* [HGM] add time */
11212             char buf[MSG_SIZ]; int seconds;
11213
11214             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11215
11216             if( seconds <= 0)
11217               buf[0] = 0;
11218             else
11219               if( seconds < 30 )
11220                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11221               else
11222                 {
11223                   seconds = (seconds + 4)/10; // round to full seconds
11224                   if( seconds < 60 )
11225                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11226                   else
11227                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11228                 }
11229
11230             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11231                       pvInfoList[i].score >= 0 ? "+" : "",
11232                       pvInfoList[i].score / 100.0,
11233                       pvInfoList[i].depth,
11234                       buf );
11235
11236             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11237
11238             /* Print score/depth */
11239             blank = linelen > 0 && movelen > 0;
11240             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11241                 fprintf(f, "\n");
11242                 linelen = 0;
11243                 blank = 0;
11244             }
11245             if (blank) {
11246                 fprintf(f, " ");
11247                 linelen++;
11248             }
11249             fprintf(f, "%s", move_buffer);
11250             linelen += movelen;
11251         }
11252
11253         i++;
11254     }
11255
11256     /* Start a new line */
11257     if (linelen > 0) fprintf(f, "\n");
11258
11259     /* Print comments after last move */
11260     if (commentList[i] != NULL) {
11261         fprintf(f, "%s\n", commentList[i]);
11262     }
11263
11264     /* Print result */
11265     if (gameInfo.resultDetails != NULL &&
11266         gameInfo.resultDetails[0] != NULLCHAR) {
11267         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11268                 PGNResult(gameInfo.result));
11269     } else {
11270         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11271     }
11272
11273     fclose(f);
11274     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11275     return TRUE;
11276 }
11277
11278 /* Save game in old style and close the file */
11279 int
11280 SaveGameOldStyle(f)
11281      FILE *f;
11282 {
11283     int i, offset;
11284     time_t tm;
11285
11286     tm = time((time_t *) NULL);
11287
11288     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11289     PrintOpponents(f);
11290
11291     if (backwardMostMove > 0 || startedFromSetupPosition) {
11292         fprintf(f, "\n[--------------\n");
11293         PrintPosition(f, backwardMostMove);
11294         fprintf(f, "--------------]\n");
11295     } else {
11296         fprintf(f, "\n");
11297     }
11298
11299     i = backwardMostMove;
11300     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11301
11302     while (i < forwardMostMove) {
11303         if (commentList[i] != NULL) {
11304             fprintf(f, "[%s]\n", commentList[i]);
11305         }
11306
11307         if ((i % 2) == 1) {
11308             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11309             i++;
11310         } else {
11311             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11312             i++;
11313             if (commentList[i] != NULL) {
11314                 fprintf(f, "\n");
11315                 continue;
11316             }
11317             if (i >= forwardMostMove) {
11318                 fprintf(f, "\n");
11319                 break;
11320             }
11321             fprintf(f, "%s\n", parseList[i]);
11322             i++;
11323         }
11324     }
11325
11326     if (commentList[i] != NULL) {
11327         fprintf(f, "[%s]\n", commentList[i]);
11328     }
11329
11330     /* This isn't really the old style, but it's close enough */
11331     if (gameInfo.resultDetails != NULL &&
11332         gameInfo.resultDetails[0] != NULLCHAR) {
11333         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11334                 gameInfo.resultDetails);
11335     } else {
11336         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11337     }
11338
11339     fclose(f);
11340     return TRUE;
11341 }
11342
11343 /* Save the current game to open file f and close the file */
11344 int
11345 SaveGame(f, dummy, dummy2)
11346      FILE *f;
11347      int dummy;
11348      char *dummy2;
11349 {
11350     if (gameMode == EditPosition) EditPositionDone(TRUE);
11351     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11352     if (appData.oldSaveStyle)
11353       return SaveGameOldStyle(f);
11354     else
11355       return SaveGamePGN(f);
11356 }
11357
11358 /* Save the current position to the given file */
11359 int
11360 SavePositionToFile(filename)
11361      char *filename;
11362 {
11363     FILE *f;
11364     char buf[MSG_SIZ];
11365
11366     if (strcmp(filename, "-") == 0) {
11367         return SavePosition(stdout, 0, NULL);
11368     } else {
11369         f = fopen(filename, "a");
11370         if (f == NULL) {
11371             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11372             DisplayError(buf, errno);
11373             return FALSE;
11374         } else {
11375             safeStrCpy(buf, lastMsg, MSG_SIZ);
11376             DisplayMessage(_("Waiting for access to save file"), "");
11377             flock(fileno(f), LOCK_EX); // [HGM] lock
11378             DisplayMessage(_("Saving position"), "");
11379             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11380             SavePosition(f, 0, NULL);
11381             DisplayMessage(buf, "");
11382             return TRUE;
11383         }
11384     }
11385 }
11386
11387 /* Save the current position to the given open file and close the file */
11388 int
11389 SavePosition(f, dummy, dummy2)
11390      FILE *f;
11391      int dummy;
11392      char *dummy2;
11393 {
11394     time_t tm;
11395     char *fen;
11396
11397     if (gameMode == EditPosition) EditPositionDone(TRUE);
11398     if (appData.oldSaveStyle) {
11399         tm = time((time_t *) NULL);
11400
11401         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11402         PrintOpponents(f);
11403         fprintf(f, "[--------------\n");
11404         PrintPosition(f, currentMove);
11405         fprintf(f, "--------------]\n");
11406     } else {
11407         fen = PositionToFEN(currentMove, NULL);
11408         fprintf(f, "%s\n", fen);
11409         free(fen);
11410     }
11411     fclose(f);
11412     return TRUE;
11413 }
11414
11415 void
11416 ReloadCmailMsgEvent(unregister)
11417      int unregister;
11418 {
11419 #if !WIN32
11420     static char *inFilename = NULL;
11421     static char *outFilename;
11422     int i;
11423     struct stat inbuf, outbuf;
11424     int status;
11425
11426     /* Any registered moves are unregistered if unregister is set, */
11427     /* i.e. invoked by the signal handler */
11428     if (unregister) {
11429         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11430             cmailMoveRegistered[i] = FALSE;
11431             if (cmailCommentList[i] != NULL) {
11432                 free(cmailCommentList[i]);
11433                 cmailCommentList[i] = NULL;
11434             }
11435         }
11436         nCmailMovesRegistered = 0;
11437     }
11438
11439     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11440         cmailResult[i] = CMAIL_NOT_RESULT;
11441     }
11442     nCmailResults = 0;
11443
11444     if (inFilename == NULL) {
11445         /* Because the filenames are static they only get malloced once  */
11446         /* and they never get freed                                      */
11447         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11448         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11449
11450         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11451         sprintf(outFilename, "%s.out", appData.cmailGameName);
11452     }
11453
11454     status = stat(outFilename, &outbuf);
11455     if (status < 0) {
11456         cmailMailedMove = FALSE;
11457     } else {
11458         status = stat(inFilename, &inbuf);
11459         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11460     }
11461
11462     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11463        counts the games, notes how each one terminated, etc.
11464
11465        It would be nice to remove this kludge and instead gather all
11466        the information while building the game list.  (And to keep it
11467        in the game list nodes instead of having a bunch of fixed-size
11468        parallel arrays.)  Note this will require getting each game's
11469        termination from the PGN tags, as the game list builder does
11470        not process the game moves.  --mann
11471        */
11472     cmailMsgLoaded = TRUE;
11473     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11474
11475     /* Load first game in the file or popup game menu */
11476     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11477
11478 #endif /* !WIN32 */
11479     return;
11480 }
11481
11482 int
11483 RegisterMove()
11484 {
11485     FILE *f;
11486     char string[MSG_SIZ];
11487
11488     if (   cmailMailedMove
11489         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11490         return TRUE;            /* Allow free viewing  */
11491     }
11492
11493     /* Unregister move to ensure that we don't leave RegisterMove        */
11494     /* with the move registered when the conditions for registering no   */
11495     /* longer hold                                                       */
11496     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11497         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11498         nCmailMovesRegistered --;
11499
11500         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11501           {
11502               free(cmailCommentList[lastLoadGameNumber - 1]);
11503               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11504           }
11505     }
11506
11507     if (cmailOldMove == -1) {
11508         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11509         return FALSE;
11510     }
11511
11512     if (currentMove > cmailOldMove + 1) {
11513         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11514         return FALSE;
11515     }
11516
11517     if (currentMove < cmailOldMove) {
11518         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11519         return FALSE;
11520     }
11521
11522     if (forwardMostMove > currentMove) {
11523         /* Silently truncate extra moves */
11524         TruncateGame();
11525     }
11526
11527     if (   (currentMove == cmailOldMove + 1)
11528         || (   (currentMove == cmailOldMove)
11529             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11530                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11531         if (gameInfo.result != GameUnfinished) {
11532             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11533         }
11534
11535         if (commentList[currentMove] != NULL) {
11536             cmailCommentList[lastLoadGameNumber - 1]
11537               = StrSave(commentList[currentMove]);
11538         }
11539         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11540
11541         if (appData.debugMode)
11542           fprintf(debugFP, "Saving %s for game %d\n",
11543                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11544
11545         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11546
11547         f = fopen(string, "w");
11548         if (appData.oldSaveStyle) {
11549             SaveGameOldStyle(f); /* also closes the file */
11550
11551             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11552             f = fopen(string, "w");
11553             SavePosition(f, 0, NULL); /* also closes the file */
11554         } else {
11555             fprintf(f, "{--------------\n");
11556             PrintPosition(f, currentMove);
11557             fprintf(f, "--------------}\n\n");
11558
11559             SaveGame(f, 0, NULL); /* also closes the file*/
11560         }
11561
11562         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11563         nCmailMovesRegistered ++;
11564     } else if (nCmailGames == 1) {
11565         DisplayError(_("You have not made a move yet"), 0);
11566         return FALSE;
11567     }
11568
11569     return TRUE;
11570 }
11571
11572 void
11573 MailMoveEvent()
11574 {
11575 #if !WIN32
11576     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11577     FILE *commandOutput;
11578     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11579     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11580     int nBuffers;
11581     int i;
11582     int archived;
11583     char *arcDir;
11584
11585     if (! cmailMsgLoaded) {
11586         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11587         return;
11588     }
11589
11590     if (nCmailGames == nCmailResults) {
11591         DisplayError(_("No unfinished games"), 0);
11592         return;
11593     }
11594
11595 #if CMAIL_PROHIBIT_REMAIL
11596     if (cmailMailedMove) {
11597       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);
11598         DisplayError(msg, 0);
11599         return;
11600     }
11601 #endif
11602
11603     if (! (cmailMailedMove || RegisterMove())) return;
11604
11605     if (   cmailMailedMove
11606         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11607       snprintf(string, MSG_SIZ, partCommandString,
11608                appData.debugMode ? " -v" : "", appData.cmailGameName);
11609         commandOutput = popen(string, "r");
11610
11611         if (commandOutput == NULL) {
11612             DisplayError(_("Failed to invoke cmail"), 0);
11613         } else {
11614             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11615                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11616             }
11617             if (nBuffers > 1) {
11618                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11619                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11620                 nBytes = MSG_SIZ - 1;
11621             } else {
11622                 (void) memcpy(msg, buffer, nBytes);
11623             }
11624             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11625
11626             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11627                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11628
11629                 archived = TRUE;
11630                 for (i = 0; i < nCmailGames; i ++) {
11631                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11632                         archived = FALSE;
11633                     }
11634                 }
11635                 if (   archived
11636                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11637                         != NULL)) {
11638                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11639                            arcDir,
11640                            appData.cmailGameName,
11641                            gameInfo.date);
11642                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11643                     cmailMsgLoaded = FALSE;
11644                 }
11645             }
11646
11647             DisplayInformation(msg);
11648             pclose(commandOutput);
11649         }
11650     } else {
11651         if ((*cmailMsg) != '\0') {
11652             DisplayInformation(cmailMsg);
11653         }
11654     }
11655
11656     return;
11657 #endif /* !WIN32 */
11658 }
11659
11660 char *
11661 CmailMsg()
11662 {
11663 #if WIN32
11664     return NULL;
11665 #else
11666     int  prependComma = 0;
11667     char number[5];
11668     char string[MSG_SIZ];       /* Space for game-list */
11669     int  i;
11670
11671     if (!cmailMsgLoaded) return "";
11672
11673     if (cmailMailedMove) {
11674       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11675     } else {
11676         /* Create a list of games left */
11677       snprintf(string, MSG_SIZ, "[");
11678         for (i = 0; i < nCmailGames; i ++) {
11679             if (! (   cmailMoveRegistered[i]
11680                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11681                 if (prependComma) {
11682                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11683                 } else {
11684                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11685                     prependComma = 1;
11686                 }
11687
11688                 strcat(string, number);
11689             }
11690         }
11691         strcat(string, "]");
11692
11693         if (nCmailMovesRegistered + nCmailResults == 0) {
11694             switch (nCmailGames) {
11695               case 1:
11696                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11697                 break;
11698
11699               case 2:
11700                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11701                 break;
11702
11703               default:
11704                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11705                          nCmailGames);
11706                 break;
11707             }
11708         } else {
11709             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11710               case 1:
11711                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11712                          string);
11713                 break;
11714
11715               case 0:
11716                 if (nCmailResults == nCmailGames) {
11717                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11718                 } else {
11719                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11720                 }
11721                 break;
11722
11723               default:
11724                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11725                          string);
11726             }
11727         }
11728     }
11729     return cmailMsg;
11730 #endif /* WIN32 */
11731 }
11732
11733 void
11734 ResetGameEvent()
11735 {
11736     if (gameMode == Training)
11737       SetTrainingModeOff();
11738
11739     Reset(TRUE, TRUE);
11740     cmailMsgLoaded = FALSE;
11741     if (appData.icsActive) {
11742       SendToICS(ics_prefix);
11743       SendToICS("refresh\n");
11744     }
11745 }
11746
11747 void
11748 ExitEvent(status)
11749      int status;
11750 {
11751     exiting++;
11752     if (exiting > 2) {
11753       /* Give up on clean exit */
11754       exit(status);
11755     }
11756     if (exiting > 1) {
11757       /* Keep trying for clean exit */
11758       return;
11759     }
11760
11761     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11762
11763     if (telnetISR != NULL) {
11764       RemoveInputSource(telnetISR);
11765     }
11766     if (icsPR != NoProc) {
11767       DestroyChildProcess(icsPR, TRUE);
11768     }
11769
11770     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11771     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11772
11773     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11774     /* make sure this other one finishes before killing it!                  */
11775     if(endingGame) { int count = 0;
11776         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11777         while(endingGame && count++ < 10) DoSleep(1);
11778         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11779     }
11780
11781     /* Kill off chess programs */
11782     if (first.pr != NoProc) {
11783         ExitAnalyzeMode();
11784
11785         DoSleep( appData.delayBeforeQuit );
11786         SendToProgram("quit\n", &first);
11787         DoSleep( appData.delayAfterQuit );
11788         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11789     }
11790     if (second.pr != NoProc) {
11791         DoSleep( appData.delayBeforeQuit );
11792         SendToProgram("quit\n", &second);
11793         DoSleep( appData.delayAfterQuit );
11794         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11795     }
11796     if (first.isr != NULL) {
11797         RemoveInputSource(first.isr);
11798     }
11799     if (second.isr != NULL) {
11800         RemoveInputSource(second.isr);
11801     }
11802
11803     ShutDownFrontEnd();
11804     exit(status);
11805 }
11806
11807 void
11808 PauseEvent()
11809 {
11810     if (appData.debugMode)
11811         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11812     if (pausing) {
11813         pausing = FALSE;
11814         ModeHighlight();
11815         if (gameMode == MachinePlaysWhite ||
11816             gameMode == MachinePlaysBlack) {
11817             StartClocks();
11818         } else {
11819             DisplayBothClocks();
11820         }
11821         if (gameMode == PlayFromGameFile) {
11822             if (appData.timeDelay >= 0)
11823                 AutoPlayGameLoop();
11824         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11825             Reset(FALSE, TRUE);
11826             SendToICS(ics_prefix);
11827             SendToICS("refresh\n");
11828         } else if (currentMove < forwardMostMove) {
11829             ForwardInner(forwardMostMove);
11830         }
11831         pauseExamInvalid = FALSE;
11832     } else {
11833         switch (gameMode) {
11834           default:
11835             return;
11836           case IcsExamining:
11837             pauseExamForwardMostMove = forwardMostMove;
11838             pauseExamInvalid = FALSE;
11839             /* fall through */
11840           case IcsObserving:
11841           case IcsPlayingWhite:
11842           case IcsPlayingBlack:
11843             pausing = TRUE;
11844             ModeHighlight();
11845             return;
11846           case PlayFromGameFile:
11847             (void) StopLoadGameTimer();
11848             pausing = TRUE;
11849             ModeHighlight();
11850             break;
11851           case BeginningOfGame:
11852             if (appData.icsActive) return;
11853             /* else fall through */
11854           case MachinePlaysWhite:
11855           case MachinePlaysBlack:
11856           case TwoMachinesPlay:
11857             if (forwardMostMove == 0)
11858               return;           /* don't pause if no one has moved */
11859             if ((gameMode == MachinePlaysWhite &&
11860                  !WhiteOnMove(forwardMostMove)) ||
11861                 (gameMode == MachinePlaysBlack &&
11862                  WhiteOnMove(forwardMostMove))) {
11863                 StopClocks();
11864             }
11865             pausing = TRUE;
11866             ModeHighlight();
11867             break;
11868         }
11869     }
11870 }
11871
11872 void
11873 EditCommentEvent()
11874 {
11875     char title[MSG_SIZ];
11876
11877     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11878       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11879     } else {
11880       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11881                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11882                parseList[currentMove - 1]);
11883     }
11884
11885     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11886 }
11887
11888
11889 void
11890 EditTagsEvent()
11891 {
11892     char *tags = PGNTags(&gameInfo);
11893     EditTagsPopUp(tags, NULL);
11894     free(tags);
11895 }
11896
11897 void
11898 AnalyzeModeEvent()
11899 {
11900     if (appData.noChessProgram || gameMode == AnalyzeMode)
11901       return;
11902
11903     if (gameMode != AnalyzeFile) {
11904         if (!appData.icsEngineAnalyze) {
11905                EditGameEvent();
11906                if (gameMode != EditGame) return;
11907         }
11908         ResurrectChessProgram();
11909         SendToProgram("analyze\n", &first);
11910         first.analyzing = TRUE;
11911         /*first.maybeThinking = TRUE;*/
11912         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11913         EngineOutputPopUp();
11914     }
11915     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11916     pausing = FALSE;
11917     ModeHighlight();
11918     SetGameInfo();
11919
11920     StartAnalysisClock();
11921     GetTimeMark(&lastNodeCountTime);
11922     lastNodeCount = 0;
11923 }
11924
11925 void
11926 AnalyzeFileEvent()
11927 {
11928     if (appData.noChessProgram || gameMode == AnalyzeFile)
11929       return;
11930
11931     if (gameMode != AnalyzeMode) {
11932         EditGameEvent();
11933         if (gameMode != EditGame) return;
11934         ResurrectChessProgram();
11935         SendToProgram("analyze\n", &first);
11936         first.analyzing = TRUE;
11937         /*first.maybeThinking = TRUE;*/
11938         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11939         EngineOutputPopUp();
11940     }
11941     gameMode = AnalyzeFile;
11942     pausing = FALSE;
11943     ModeHighlight();
11944     SetGameInfo();
11945
11946     StartAnalysisClock();
11947     GetTimeMark(&lastNodeCountTime);
11948     lastNodeCount = 0;
11949 }
11950
11951 void
11952 MachineWhiteEvent()
11953 {
11954     char buf[MSG_SIZ];
11955     char *bookHit = NULL;
11956
11957     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11958       return;
11959
11960
11961     if (gameMode == PlayFromGameFile ||
11962         gameMode == TwoMachinesPlay  ||
11963         gameMode == Training         ||
11964         gameMode == AnalyzeMode      ||
11965         gameMode == EndOfGame)
11966         EditGameEvent();
11967
11968     if (gameMode == EditPosition)
11969         EditPositionDone(TRUE);
11970
11971     if (!WhiteOnMove(currentMove)) {
11972         DisplayError(_("It is not White's turn"), 0);
11973         return;
11974     }
11975
11976     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11977       ExitAnalyzeMode();
11978
11979     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11980         gameMode == AnalyzeFile)
11981         TruncateGame();
11982
11983     ResurrectChessProgram();    /* in case it isn't running */
11984     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11985         gameMode = MachinePlaysWhite;
11986         ResetClocks();
11987     } else
11988     gameMode = MachinePlaysWhite;
11989     pausing = FALSE;
11990     ModeHighlight();
11991     SetGameInfo();
11992     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11993     DisplayTitle(buf);
11994     if (first.sendName) {
11995       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11996       SendToProgram(buf, &first);
11997     }
11998     if (first.sendTime) {
11999       if (first.useColors) {
12000         SendToProgram("black\n", &first); /*gnu kludge*/
12001       }
12002       SendTimeRemaining(&first, TRUE);
12003     }
12004     if (first.useColors) {
12005       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12006     }
12007     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12008     SetMachineThinkingEnables();
12009     first.maybeThinking = TRUE;
12010     StartClocks();
12011     firstMove = FALSE;
12012
12013     if (appData.autoFlipView && !flipView) {
12014       flipView = !flipView;
12015       DrawPosition(FALSE, NULL);
12016       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12017     }
12018
12019     if(bookHit) { // [HGM] book: simulate book reply
12020         static char bookMove[MSG_SIZ]; // a bit generous?
12021
12022         programStats.nodes = programStats.depth = programStats.time =
12023         programStats.score = programStats.got_only_move = 0;
12024         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12025
12026         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12027         strcat(bookMove, bookHit);
12028         HandleMachineMove(bookMove, &first);
12029     }
12030 }
12031
12032 void
12033 MachineBlackEvent()
12034 {
12035   char buf[MSG_SIZ];
12036   char *bookHit = NULL;
12037
12038     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12039         return;
12040
12041
12042     if (gameMode == PlayFromGameFile ||
12043         gameMode == TwoMachinesPlay  ||
12044         gameMode == Training         ||
12045         gameMode == AnalyzeMode      ||
12046         gameMode == EndOfGame)
12047         EditGameEvent();
12048
12049     if (gameMode == EditPosition)
12050         EditPositionDone(TRUE);
12051
12052     if (WhiteOnMove(currentMove)) {
12053         DisplayError(_("It is not Black's turn"), 0);
12054         return;
12055     }
12056
12057     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12058       ExitAnalyzeMode();
12059
12060     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12061         gameMode == AnalyzeFile)
12062         TruncateGame();
12063
12064     ResurrectChessProgram();    /* in case it isn't running */
12065     gameMode = MachinePlaysBlack;
12066     pausing = FALSE;
12067     ModeHighlight();
12068     SetGameInfo();
12069     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12070     DisplayTitle(buf);
12071     if (first.sendName) {
12072       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12073       SendToProgram(buf, &first);
12074     }
12075     if (first.sendTime) {
12076       if (first.useColors) {
12077         SendToProgram("white\n", &first); /*gnu kludge*/
12078       }
12079       SendTimeRemaining(&first, FALSE);
12080     }
12081     if (first.useColors) {
12082       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12083     }
12084     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12085     SetMachineThinkingEnables();
12086     first.maybeThinking = TRUE;
12087     StartClocks();
12088
12089     if (appData.autoFlipView && flipView) {
12090       flipView = !flipView;
12091       DrawPosition(FALSE, NULL);
12092       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12093     }
12094     if(bookHit) { // [HGM] book: simulate book reply
12095         static char bookMove[MSG_SIZ]; // a bit generous?
12096
12097         programStats.nodes = programStats.depth = programStats.time =
12098         programStats.score = programStats.got_only_move = 0;
12099         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12100
12101         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12102         strcat(bookMove, bookHit);
12103         HandleMachineMove(bookMove, &first);
12104     }
12105 }
12106
12107
12108 void
12109 DisplayTwoMachinesTitle()
12110 {
12111     char buf[MSG_SIZ];
12112     if (appData.matchGames > 0) {
12113         if (first.twoMachinesColor[0] == 'w') {
12114           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12115                    gameInfo.white, gameInfo.black,
12116                    first.matchWins, second.matchWins,
12117                    matchGame - 1 - (first.matchWins + second.matchWins));
12118         } else {
12119           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12120                    gameInfo.white, gameInfo.black,
12121                    second.matchWins, first.matchWins,
12122                    matchGame - 1 - (first.matchWins + second.matchWins));
12123         }
12124     } else {
12125       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12126     }
12127     DisplayTitle(buf);
12128 }
12129
12130 void
12131 SettingsMenuIfReady()
12132 {
12133   if (second.lastPing != second.lastPong) {
12134     DisplayMessage("", _("Waiting for second chess program"));
12135     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12136     return;
12137   }
12138   ThawUI();
12139   DisplayMessage("", "");
12140   SettingsPopUp(&second);
12141 }
12142
12143 int
12144 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12145 {
12146     char buf[MSG_SIZ];
12147     if (cps->pr == NULL) {
12148         StartChessProgram(cps);
12149         if (cps->protocolVersion == 1) {
12150           retry();
12151         } else {
12152           /* kludge: allow timeout for initial "feature" command */
12153           FreezeUI();
12154           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12155           DisplayMessage("", buf);
12156           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12157         }
12158         return 1;
12159     }
12160     return 0;
12161 }
12162
12163 void
12164 TwoMachinesEvent P((void))
12165 {
12166     int i;
12167     char buf[MSG_SIZ];
12168     ChessProgramState *onmove;
12169     char *bookHit = NULL;
12170     static int stalling = 0;
12171
12172     if (appData.noChessProgram) return;
12173
12174     switch (gameMode) {
12175       case TwoMachinesPlay:
12176         return;
12177       case MachinePlaysWhite:
12178       case MachinePlaysBlack:
12179         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12180             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12181             return;
12182         }
12183         /* fall through */
12184       case BeginningOfGame:
12185       case PlayFromGameFile:
12186       case EndOfGame:
12187         EditGameEvent();
12188         if (gameMode != EditGame) return;
12189         break;
12190       case EditPosition:
12191         EditPositionDone(TRUE);
12192         break;
12193       case AnalyzeMode:
12194       case AnalyzeFile:
12195         ExitAnalyzeMode();
12196         break;
12197       case EditGame:
12198       default:
12199         break;
12200     }
12201
12202 //    forwardMostMove = currentMove;
12203     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12204     ResurrectChessProgram();    /* in case first program isn't running */
12205
12206     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
12207     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12208       DisplayMessage("", _("Waiting for first chess program"));
12209       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12210       return;
12211     }
12212     if(!stalling) {
12213       InitChessProgram(&second, FALSE);
12214       SendToProgram("force\n", &second);
12215     }
12216     if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12217       if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12218       stalling = 1;
12219       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12220       return;
12221     }
12222     stalling = 0;
12223     DisplayMessage("", "");
12224     if (startedFromSetupPosition) {
12225         SendBoard(&second, backwardMostMove);
12226     if (appData.debugMode) {
12227         fprintf(debugFP, "Two Machines\n");
12228     }
12229     }
12230     for (i = backwardMostMove; i < forwardMostMove; i++) {
12231         SendMoveToProgram(i, &second);
12232     }
12233
12234     gameMode = TwoMachinesPlay;
12235     pausing = FALSE;
12236     ModeHighlight();
12237     SetGameInfo();
12238     DisplayTwoMachinesTitle();
12239     firstMove = TRUE;
12240     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12241         onmove = &first;
12242     } else {
12243         onmove = &second;
12244     }
12245
12246     SendToProgram(first.computerString, &first);
12247     if (first.sendName) {
12248       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12249       SendToProgram(buf, &first);
12250     }
12251     SendToProgram(second.computerString, &second);
12252     if (second.sendName) {
12253       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12254       SendToProgram(buf, &second);
12255     }
12256
12257     ResetClocks();
12258     if (!first.sendTime || !second.sendTime) {
12259         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12260         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12261     }
12262     if (onmove->sendTime) {
12263       if (onmove->useColors) {
12264         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12265       }
12266       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12267     }
12268     if (onmove->useColors) {
12269       SendToProgram(onmove->twoMachinesColor, onmove);
12270     }
12271     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12272 //    SendToProgram("go\n", onmove);
12273     onmove->maybeThinking = TRUE;
12274     SetMachineThinkingEnables();
12275
12276     StartClocks();
12277
12278     if(bookHit) { // [HGM] book: simulate book reply
12279         static char bookMove[MSG_SIZ]; // a bit generous?
12280
12281         programStats.nodes = programStats.depth = programStats.time =
12282         programStats.score = programStats.got_only_move = 0;
12283         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12284
12285         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12286         strcat(bookMove, bookHit);
12287         savedMessage = bookMove; // args for deferred call
12288         savedState = onmove;
12289         ScheduleDelayedEvent(DeferredBookMove, 1);
12290     }
12291 }
12292
12293 void
12294 TrainingEvent()
12295 {
12296     if (gameMode == Training) {
12297       SetTrainingModeOff();
12298       gameMode = PlayFromGameFile;
12299       DisplayMessage("", _("Training mode off"));
12300     } else {
12301       gameMode = Training;
12302       animateTraining = appData.animate;
12303
12304       /* make sure we are not already at the end of the game */
12305       if (currentMove < forwardMostMove) {
12306         SetTrainingModeOn();
12307         DisplayMessage("", _("Training mode on"));
12308       } else {
12309         gameMode = PlayFromGameFile;
12310         DisplayError(_("Already at end of game"), 0);
12311       }
12312     }
12313     ModeHighlight();
12314 }
12315
12316 void
12317 IcsClientEvent()
12318 {
12319     if (!appData.icsActive) return;
12320     switch (gameMode) {
12321       case IcsPlayingWhite:
12322       case IcsPlayingBlack:
12323       case IcsObserving:
12324       case IcsIdle:
12325       case BeginningOfGame:
12326       case IcsExamining:
12327         return;
12328
12329       case EditGame:
12330         break;
12331
12332       case EditPosition:
12333         EditPositionDone(TRUE);
12334         break;
12335
12336       case AnalyzeMode:
12337       case AnalyzeFile:
12338         ExitAnalyzeMode();
12339         break;
12340
12341       default:
12342         EditGameEvent();
12343         break;
12344     }
12345
12346     gameMode = IcsIdle;
12347     ModeHighlight();
12348     return;
12349 }
12350
12351
12352 void
12353 EditGameEvent()
12354 {
12355     int i;
12356
12357     switch (gameMode) {
12358       case Training:
12359         SetTrainingModeOff();
12360         break;
12361       case MachinePlaysWhite:
12362       case MachinePlaysBlack:
12363       case BeginningOfGame:
12364         SendToProgram("force\n", &first);
12365         SetUserThinkingEnables();
12366         break;
12367       case PlayFromGameFile:
12368         (void) StopLoadGameTimer();
12369         if (gameFileFP != NULL) {
12370             gameFileFP = NULL;
12371         }
12372         break;
12373       case EditPosition:
12374         EditPositionDone(TRUE);
12375         break;
12376       case AnalyzeMode:
12377       case AnalyzeFile:
12378         ExitAnalyzeMode();
12379         SendToProgram("force\n", &first);
12380         break;
12381       case TwoMachinesPlay:
12382         GameEnds(EndOfFile, NULL, GE_PLAYER);
12383         ResurrectChessProgram();
12384         SetUserThinkingEnables();
12385         break;
12386       case EndOfGame:
12387         ResurrectChessProgram();
12388         break;
12389       case IcsPlayingBlack:
12390       case IcsPlayingWhite:
12391         DisplayError(_("Warning: You are still playing a game"), 0);
12392         break;
12393       case IcsObserving:
12394         DisplayError(_("Warning: You are still observing a game"), 0);
12395         break;
12396       case IcsExamining:
12397         DisplayError(_("Warning: You are still examining a game"), 0);
12398         break;
12399       case IcsIdle:
12400         break;
12401       case EditGame:
12402       default:
12403         return;
12404     }
12405
12406     pausing = FALSE;
12407     StopClocks();
12408     first.offeredDraw = second.offeredDraw = 0;
12409
12410     if (gameMode == PlayFromGameFile) {
12411         whiteTimeRemaining = timeRemaining[0][currentMove];
12412         blackTimeRemaining = timeRemaining[1][currentMove];
12413         DisplayTitle("");
12414     }
12415
12416     if (gameMode == MachinePlaysWhite ||
12417         gameMode == MachinePlaysBlack ||
12418         gameMode == TwoMachinesPlay ||
12419         gameMode == EndOfGame) {
12420         i = forwardMostMove;
12421         while (i > currentMove) {
12422             SendToProgram("undo\n", &first);
12423             i--;
12424         }
12425         whiteTimeRemaining = timeRemaining[0][currentMove];
12426         blackTimeRemaining = timeRemaining[1][currentMove];
12427         DisplayBothClocks();
12428         if (whiteFlag || blackFlag) {
12429             whiteFlag = blackFlag = 0;
12430         }
12431         DisplayTitle("");
12432     }
12433
12434     gameMode = EditGame;
12435     ModeHighlight();
12436     SetGameInfo();
12437 }
12438
12439
12440 void
12441 EditPositionEvent()
12442 {
12443     if (gameMode == EditPosition) {
12444         EditGameEvent();
12445         return;
12446     }
12447
12448     EditGameEvent();
12449     if (gameMode != EditGame) return;
12450
12451     gameMode = EditPosition;
12452     ModeHighlight();
12453     SetGameInfo();
12454     if (currentMove > 0)
12455       CopyBoard(boards[0], boards[currentMove]);
12456
12457     blackPlaysFirst = !WhiteOnMove(currentMove);
12458     ResetClocks();
12459     currentMove = forwardMostMove = backwardMostMove = 0;
12460     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12461     DisplayMove(-1);
12462 }
12463
12464 void
12465 ExitAnalyzeMode()
12466 {
12467     /* [DM] icsEngineAnalyze - possible call from other functions */
12468     if (appData.icsEngineAnalyze) {
12469         appData.icsEngineAnalyze = FALSE;
12470
12471         DisplayMessage("",_("Close ICS engine analyze..."));
12472     }
12473     if (first.analysisSupport && first.analyzing) {
12474       SendToProgram("exit\n", &first);
12475       first.analyzing = FALSE;
12476     }
12477     thinkOutput[0] = NULLCHAR;
12478 }
12479
12480 void
12481 EditPositionDone(Boolean fakeRights)
12482 {
12483     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12484
12485     startedFromSetupPosition = TRUE;
12486     InitChessProgram(&first, FALSE);
12487     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12488       boards[0][EP_STATUS] = EP_NONE;
12489       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12490     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12491         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12492         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12493       } else boards[0][CASTLING][2] = NoRights;
12494     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12495         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12496         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12497       } else boards[0][CASTLING][5] = NoRights;
12498     }
12499     SendToProgram("force\n", &first);
12500     if (blackPlaysFirst) {
12501         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12502         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12503         currentMove = forwardMostMove = backwardMostMove = 1;
12504         CopyBoard(boards[1], boards[0]);
12505     } else {
12506         currentMove = forwardMostMove = backwardMostMove = 0;
12507     }
12508     SendBoard(&first, forwardMostMove);
12509     if (appData.debugMode) {
12510         fprintf(debugFP, "EditPosDone\n");
12511     }
12512     DisplayTitle("");
12513     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12514     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12515     gameMode = EditGame;
12516     ModeHighlight();
12517     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12518     ClearHighlights(); /* [AS] */
12519 }
12520
12521 /* Pause for `ms' milliseconds */
12522 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12523 void
12524 TimeDelay(ms)
12525      long ms;
12526 {
12527     TimeMark m1, m2;
12528
12529     GetTimeMark(&m1);
12530     do {
12531         GetTimeMark(&m2);
12532     } while (SubtractTimeMarks(&m2, &m1) < ms);
12533 }
12534
12535 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12536 void
12537 SendMultiLineToICS(buf)
12538      char *buf;
12539 {
12540     char temp[MSG_SIZ+1], *p;
12541     int len;
12542
12543     len = strlen(buf);
12544     if (len > MSG_SIZ)
12545       len = MSG_SIZ;
12546
12547     strncpy(temp, buf, len);
12548     temp[len] = 0;
12549
12550     p = temp;
12551     while (*p) {
12552         if (*p == '\n' || *p == '\r')
12553           *p = ' ';
12554         ++p;
12555     }
12556
12557     strcat(temp, "\n");
12558     SendToICS(temp);
12559     SendToPlayer(temp, strlen(temp));
12560 }
12561
12562 void
12563 SetWhiteToPlayEvent()
12564 {
12565     if (gameMode == EditPosition) {
12566         blackPlaysFirst = FALSE;
12567         DisplayBothClocks();    /* works because currentMove is 0 */
12568     } else if (gameMode == IcsExamining) {
12569         SendToICS(ics_prefix);
12570         SendToICS("tomove white\n");
12571     }
12572 }
12573
12574 void
12575 SetBlackToPlayEvent()
12576 {
12577     if (gameMode == EditPosition) {
12578         blackPlaysFirst = TRUE;
12579         currentMove = 1;        /* kludge */
12580         DisplayBothClocks();
12581         currentMove = 0;
12582     } else if (gameMode == IcsExamining) {
12583         SendToICS(ics_prefix);
12584         SendToICS("tomove black\n");
12585     }
12586 }
12587
12588 void
12589 EditPositionMenuEvent(selection, x, y)
12590      ChessSquare selection;
12591      int x, y;
12592 {
12593     char buf[MSG_SIZ];
12594     ChessSquare piece = boards[0][y][x];
12595
12596     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12597
12598     switch (selection) {
12599       case ClearBoard:
12600         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12601             SendToICS(ics_prefix);
12602             SendToICS("bsetup clear\n");
12603         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12604             SendToICS(ics_prefix);
12605             SendToICS("clearboard\n");
12606         } else {
12607             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12608                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12609                 for (y = 0; y < BOARD_HEIGHT; y++) {
12610                     if (gameMode == IcsExamining) {
12611                         if (boards[currentMove][y][x] != EmptySquare) {
12612                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12613                                     AAA + x, ONE + y);
12614                             SendToICS(buf);
12615                         }
12616                     } else {
12617                         boards[0][y][x] = p;
12618                     }
12619                 }
12620             }
12621         }
12622         if (gameMode == EditPosition) {
12623             DrawPosition(FALSE, boards[0]);
12624         }
12625         break;
12626
12627       case WhitePlay:
12628         SetWhiteToPlayEvent();
12629         break;
12630
12631       case BlackPlay:
12632         SetBlackToPlayEvent();
12633         break;
12634
12635       case EmptySquare:
12636         if (gameMode == IcsExamining) {
12637             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12638             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12639             SendToICS(buf);
12640         } else {
12641             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12642                 if(x == BOARD_LEFT-2) {
12643                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12644                     boards[0][y][1] = 0;
12645                 } else
12646                 if(x == BOARD_RGHT+1) {
12647                     if(y >= gameInfo.holdingsSize) break;
12648                     boards[0][y][BOARD_WIDTH-2] = 0;
12649                 } else break;
12650             }
12651             boards[0][y][x] = EmptySquare;
12652             DrawPosition(FALSE, boards[0]);
12653         }
12654         break;
12655
12656       case PromotePiece:
12657         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12658            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12659             selection = (ChessSquare) (PROMOTED piece);
12660         } else if(piece == EmptySquare) selection = WhiteSilver;
12661         else selection = (ChessSquare)((int)piece - 1);
12662         goto defaultlabel;
12663
12664       case DemotePiece:
12665         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12666            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12667             selection = (ChessSquare) (DEMOTED piece);
12668         } else if(piece == EmptySquare) selection = BlackSilver;
12669         else selection = (ChessSquare)((int)piece + 1);
12670         goto defaultlabel;
12671
12672       case WhiteQueen:
12673       case BlackQueen:
12674         if(gameInfo.variant == VariantShatranj ||
12675            gameInfo.variant == VariantXiangqi  ||
12676            gameInfo.variant == VariantCourier  ||
12677            gameInfo.variant == VariantMakruk     )
12678             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12679         goto defaultlabel;
12680
12681       case WhiteKing:
12682       case BlackKing:
12683         if(gameInfo.variant == VariantXiangqi)
12684             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12685         if(gameInfo.variant == VariantKnightmate)
12686             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12687       default:
12688         defaultlabel:
12689         if (gameMode == IcsExamining) {
12690             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12691             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12692                      PieceToChar(selection), AAA + x, ONE + y);
12693             SendToICS(buf);
12694         } else {
12695             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12696                 int n;
12697                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12698                     n = PieceToNumber(selection - BlackPawn);
12699                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12700                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12701                     boards[0][BOARD_HEIGHT-1-n][1]++;
12702                 } else
12703                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12704                     n = PieceToNumber(selection);
12705                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12706                     boards[0][n][BOARD_WIDTH-1] = selection;
12707                     boards[0][n][BOARD_WIDTH-2]++;
12708                 }
12709             } else
12710             boards[0][y][x] = selection;
12711             DrawPosition(TRUE, boards[0]);
12712         }
12713         break;
12714     }
12715 }
12716
12717
12718 void
12719 DropMenuEvent(selection, x, y)
12720      ChessSquare selection;
12721      int x, y;
12722 {
12723     ChessMove moveType;
12724
12725     switch (gameMode) {
12726       case IcsPlayingWhite:
12727       case MachinePlaysBlack:
12728         if (!WhiteOnMove(currentMove)) {
12729             DisplayMoveError(_("It is Black's turn"));
12730             return;
12731         }
12732         moveType = WhiteDrop;
12733         break;
12734       case IcsPlayingBlack:
12735       case MachinePlaysWhite:
12736         if (WhiteOnMove(currentMove)) {
12737             DisplayMoveError(_("It is White's turn"));
12738             return;
12739         }
12740         moveType = BlackDrop;
12741         break;
12742       case EditGame:
12743         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12744         break;
12745       default:
12746         return;
12747     }
12748
12749     if (moveType == BlackDrop && selection < BlackPawn) {
12750       selection = (ChessSquare) ((int) selection
12751                                  + (int) BlackPawn - (int) WhitePawn);
12752     }
12753     if (boards[currentMove][y][x] != EmptySquare) {
12754         DisplayMoveError(_("That square is occupied"));
12755         return;
12756     }
12757
12758     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12759 }
12760
12761 void
12762 AcceptEvent()
12763 {
12764     /* Accept a pending offer of any kind from opponent */
12765
12766     if (appData.icsActive) {
12767         SendToICS(ics_prefix);
12768         SendToICS("accept\n");
12769     } else if (cmailMsgLoaded) {
12770         if (currentMove == cmailOldMove &&
12771             commentList[cmailOldMove] != NULL &&
12772             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12773                    "Black offers a draw" : "White offers a draw")) {
12774             TruncateGame();
12775             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12776             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12777         } else {
12778             DisplayError(_("There is no pending offer on this move"), 0);
12779             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12780         }
12781     } else {
12782         /* Not used for offers from chess program */
12783     }
12784 }
12785
12786 void
12787 DeclineEvent()
12788 {
12789     /* Decline a pending offer of any kind from opponent */
12790
12791     if (appData.icsActive) {
12792         SendToICS(ics_prefix);
12793         SendToICS("decline\n");
12794     } else if (cmailMsgLoaded) {
12795         if (currentMove == cmailOldMove &&
12796             commentList[cmailOldMove] != NULL &&
12797             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12798                    "Black offers a draw" : "White offers a draw")) {
12799 #ifdef NOTDEF
12800             AppendComment(cmailOldMove, "Draw declined", TRUE);
12801             DisplayComment(cmailOldMove - 1, "Draw declined");
12802 #endif /*NOTDEF*/
12803         } else {
12804             DisplayError(_("There is no pending offer on this move"), 0);
12805         }
12806     } else {
12807         /* Not used for offers from chess program */
12808     }
12809 }
12810
12811 void
12812 RematchEvent()
12813 {
12814     /* Issue ICS rematch command */
12815     if (appData.icsActive) {
12816         SendToICS(ics_prefix);
12817         SendToICS("rematch\n");
12818     }
12819 }
12820
12821 void
12822 CallFlagEvent()
12823 {
12824     /* Call your opponent's flag (claim a win on time) */
12825     if (appData.icsActive) {
12826         SendToICS(ics_prefix);
12827         SendToICS("flag\n");
12828     } else {
12829         switch (gameMode) {
12830           default:
12831             return;
12832           case MachinePlaysWhite:
12833             if (whiteFlag) {
12834                 if (blackFlag)
12835                   GameEnds(GameIsDrawn, "Both players ran out of time",
12836                            GE_PLAYER);
12837                 else
12838                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12839             } else {
12840                 DisplayError(_("Your opponent is not out of time"), 0);
12841             }
12842             break;
12843           case MachinePlaysBlack:
12844             if (blackFlag) {
12845                 if (whiteFlag)
12846                   GameEnds(GameIsDrawn, "Both players ran out of time",
12847                            GE_PLAYER);
12848                 else
12849                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12850             } else {
12851                 DisplayError(_("Your opponent is not out of time"), 0);
12852             }
12853             break;
12854         }
12855     }
12856 }
12857
12858 void
12859 ClockClick(int which)
12860 {       // [HGM] code moved to back-end from winboard.c
12861         if(which) { // black clock
12862           if (gameMode == EditPosition || gameMode == IcsExamining) {
12863             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12864             SetBlackToPlayEvent();
12865           } else if (gameMode == EditGame || shiftKey) {
12866             AdjustClock(which, -1);
12867           } else if (gameMode == IcsPlayingWhite ||
12868                      gameMode == MachinePlaysBlack) {
12869             CallFlagEvent();
12870           }
12871         } else { // white clock
12872           if (gameMode == EditPosition || gameMode == IcsExamining) {
12873             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12874             SetWhiteToPlayEvent();
12875           } else if (gameMode == EditGame || shiftKey) {
12876             AdjustClock(which, -1);
12877           } else if (gameMode == IcsPlayingBlack ||
12878                    gameMode == MachinePlaysWhite) {
12879             CallFlagEvent();
12880           }
12881         }
12882 }
12883
12884 void
12885 DrawEvent()
12886 {
12887     /* Offer draw or accept pending draw offer from opponent */
12888
12889     if (appData.icsActive) {
12890         /* Note: tournament rules require draw offers to be
12891            made after you make your move but before you punch
12892            your clock.  Currently ICS doesn't let you do that;
12893            instead, you immediately punch your clock after making
12894            a move, but you can offer a draw at any time. */
12895
12896         SendToICS(ics_prefix);
12897         SendToICS("draw\n");
12898         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12899     } else if (cmailMsgLoaded) {
12900         if (currentMove == cmailOldMove &&
12901             commentList[cmailOldMove] != NULL &&
12902             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12903                    "Black offers a draw" : "White offers a draw")) {
12904             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12905             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12906         } else if (currentMove == cmailOldMove + 1) {
12907             char *offer = WhiteOnMove(cmailOldMove) ?
12908               "White offers a draw" : "Black offers a draw";
12909             AppendComment(currentMove, offer, TRUE);
12910             DisplayComment(currentMove - 1, offer);
12911             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12912         } else {
12913             DisplayError(_("You must make your move before offering a draw"), 0);
12914             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12915         }
12916     } else if (first.offeredDraw) {
12917         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12918     } else {
12919         if (first.sendDrawOffers) {
12920             SendToProgram("draw\n", &first);
12921             userOfferedDraw = TRUE;
12922         }
12923     }
12924 }
12925
12926 void
12927 AdjournEvent()
12928 {
12929     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12930
12931     if (appData.icsActive) {
12932         SendToICS(ics_prefix);
12933         SendToICS("adjourn\n");
12934     } else {
12935         /* Currently GNU Chess doesn't offer or accept Adjourns */
12936     }
12937 }
12938
12939
12940 void
12941 AbortEvent()
12942 {
12943     /* Offer Abort or accept pending Abort offer from opponent */
12944
12945     if (appData.icsActive) {
12946         SendToICS(ics_prefix);
12947         SendToICS("abort\n");
12948     } else {
12949         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12950     }
12951 }
12952
12953 void
12954 ResignEvent()
12955 {
12956     /* Resign.  You can do this even if it's not your turn. */
12957
12958     if (appData.icsActive) {
12959         SendToICS(ics_prefix);
12960         SendToICS("resign\n");
12961     } else {
12962         switch (gameMode) {
12963           case MachinePlaysWhite:
12964             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12965             break;
12966           case MachinePlaysBlack:
12967             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12968             break;
12969           case EditGame:
12970             if (cmailMsgLoaded) {
12971                 TruncateGame();
12972                 if (WhiteOnMove(cmailOldMove)) {
12973                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12974                 } else {
12975                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12976                 }
12977                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12978             }
12979             break;
12980           default:
12981             break;
12982         }
12983     }
12984 }
12985
12986
12987 void
12988 StopObservingEvent()
12989 {
12990     /* Stop observing current games */
12991     SendToICS(ics_prefix);
12992     SendToICS("unobserve\n");
12993 }
12994
12995 void
12996 StopExaminingEvent()
12997 {
12998     /* Stop observing current game */
12999     SendToICS(ics_prefix);
13000     SendToICS("unexamine\n");
13001 }
13002
13003 void
13004 ForwardInner(target)
13005      int target;
13006 {
13007     int limit;
13008
13009     if (appData.debugMode)
13010         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13011                 target, currentMove, forwardMostMove);
13012
13013     if (gameMode == EditPosition)
13014       return;
13015
13016     if (gameMode == PlayFromGameFile && !pausing)
13017       PauseEvent();
13018
13019     if (gameMode == IcsExamining && pausing)
13020       limit = pauseExamForwardMostMove;
13021     else
13022       limit = forwardMostMove;
13023
13024     if (target > limit) target = limit;
13025
13026     if (target > 0 && moveList[target - 1][0]) {
13027         int fromX, fromY, toX, toY;
13028         toX = moveList[target - 1][2] - AAA;
13029         toY = moveList[target - 1][3] - ONE;
13030         if (moveList[target - 1][1] == '@') {
13031             if (appData.highlightLastMove) {
13032                 SetHighlights(-1, -1, toX, toY);
13033             }
13034         } else {
13035             fromX = moveList[target - 1][0] - AAA;
13036             fromY = moveList[target - 1][1] - ONE;
13037             if (target == currentMove + 1) {
13038                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13039             }
13040             if (appData.highlightLastMove) {
13041                 SetHighlights(fromX, fromY, toX, toY);
13042             }
13043         }
13044     }
13045     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13046         gameMode == Training || gameMode == PlayFromGameFile ||
13047         gameMode == AnalyzeFile) {
13048         while (currentMove < target) {
13049             SendMoveToProgram(currentMove++, &first);
13050         }
13051     } else {
13052         currentMove = target;
13053     }
13054
13055     if (gameMode == EditGame || gameMode == EndOfGame) {
13056         whiteTimeRemaining = timeRemaining[0][currentMove];
13057         blackTimeRemaining = timeRemaining[1][currentMove];
13058     }
13059     DisplayBothClocks();
13060     DisplayMove(currentMove - 1);
13061     DrawPosition(FALSE, boards[currentMove]);
13062     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13063     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13064         DisplayComment(currentMove - 1, commentList[currentMove]);
13065     }
13066 }
13067
13068
13069 void
13070 ForwardEvent()
13071 {
13072     if (gameMode == IcsExamining && !pausing) {
13073         SendToICS(ics_prefix);
13074         SendToICS("forward\n");
13075     } else {
13076         ForwardInner(currentMove + 1);
13077     }
13078 }
13079
13080 void
13081 ToEndEvent()
13082 {
13083     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13084         /* to optimze, we temporarily turn off analysis mode while we feed
13085          * the remaining moves to the engine. Otherwise we get analysis output
13086          * after each move.
13087          */
13088         if (first.analysisSupport) {
13089           SendToProgram("exit\nforce\n", &first);
13090           first.analyzing = FALSE;
13091         }
13092     }
13093
13094     if (gameMode == IcsExamining && !pausing) {
13095         SendToICS(ics_prefix);
13096         SendToICS("forward 999999\n");
13097     } else {
13098         ForwardInner(forwardMostMove);
13099     }
13100
13101     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13102         /* we have fed all the moves, so reactivate analysis mode */
13103         SendToProgram("analyze\n", &first);
13104         first.analyzing = TRUE;
13105         /*first.maybeThinking = TRUE;*/
13106         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13107     }
13108 }
13109
13110 void
13111 BackwardInner(target)
13112      int target;
13113 {
13114     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13115
13116     if (appData.debugMode)
13117         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13118                 target, currentMove, forwardMostMove);
13119
13120     if (gameMode == EditPosition) return;
13121     if (currentMove <= backwardMostMove) {
13122         ClearHighlights();
13123         DrawPosition(full_redraw, boards[currentMove]);
13124         return;
13125     }
13126     if (gameMode == PlayFromGameFile && !pausing)
13127       PauseEvent();
13128
13129     if (moveList[target][0]) {
13130         int fromX, fromY, toX, toY;
13131         toX = moveList[target][2] - AAA;
13132         toY = moveList[target][3] - ONE;
13133         if (moveList[target][1] == '@') {
13134             if (appData.highlightLastMove) {
13135                 SetHighlights(-1, -1, toX, toY);
13136             }
13137         } else {
13138             fromX = moveList[target][0] - AAA;
13139             fromY = moveList[target][1] - ONE;
13140             if (target == currentMove - 1) {
13141                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13142             }
13143             if (appData.highlightLastMove) {
13144                 SetHighlights(fromX, fromY, toX, toY);
13145             }
13146         }
13147     }
13148     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13149         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13150         while (currentMove > target) {
13151             SendToProgram("undo\n", &first);
13152             currentMove--;
13153         }
13154     } else {
13155         currentMove = target;
13156     }
13157
13158     if (gameMode == EditGame || gameMode == EndOfGame) {
13159         whiteTimeRemaining = timeRemaining[0][currentMove];
13160         blackTimeRemaining = timeRemaining[1][currentMove];
13161     }
13162     DisplayBothClocks();
13163     DisplayMove(currentMove - 1);
13164     DrawPosition(full_redraw, boards[currentMove]);
13165     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13166     // [HGM] PV info: routine tests if comment empty
13167     DisplayComment(currentMove - 1, commentList[currentMove]);
13168 }
13169
13170 void
13171 BackwardEvent()
13172 {
13173     if (gameMode == IcsExamining && !pausing) {
13174         SendToICS(ics_prefix);
13175         SendToICS("backward\n");
13176     } else {
13177         BackwardInner(currentMove - 1);
13178     }
13179 }
13180
13181 void
13182 ToStartEvent()
13183 {
13184     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13185         /* to optimize, we temporarily turn off analysis mode while we undo
13186          * all the moves. Otherwise we get analysis output after each undo.
13187          */
13188         if (first.analysisSupport) {
13189           SendToProgram("exit\nforce\n", &first);
13190           first.analyzing = FALSE;
13191         }
13192     }
13193
13194     if (gameMode == IcsExamining && !pausing) {
13195         SendToICS(ics_prefix);
13196         SendToICS("backward 999999\n");
13197     } else {
13198         BackwardInner(backwardMostMove);
13199     }
13200
13201     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13202         /* we have fed all the moves, so reactivate analysis mode */
13203         SendToProgram("analyze\n", &first);
13204         first.analyzing = TRUE;
13205         /*first.maybeThinking = TRUE;*/
13206         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13207     }
13208 }
13209
13210 void
13211 ToNrEvent(int to)
13212 {
13213   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13214   if (to >= forwardMostMove) to = forwardMostMove;
13215   if (to <= backwardMostMove) to = backwardMostMove;
13216   if (to < currentMove) {
13217     BackwardInner(to);
13218   } else {
13219     ForwardInner(to);
13220   }
13221 }
13222
13223 void
13224 RevertEvent(Boolean annotate)
13225 {
13226     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13227         return;
13228     }
13229     if (gameMode != IcsExamining) {
13230         DisplayError(_("You are not examining a game"), 0);
13231         return;
13232     }
13233     if (pausing) {
13234         DisplayError(_("You can't revert while pausing"), 0);
13235         return;
13236     }
13237     SendToICS(ics_prefix);
13238     SendToICS("revert\n");
13239 }
13240
13241 void
13242 RetractMoveEvent()
13243 {
13244     switch (gameMode) {
13245       case MachinePlaysWhite:
13246       case MachinePlaysBlack:
13247         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13248             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13249             return;
13250         }
13251         if (forwardMostMove < 2) return;
13252         currentMove = forwardMostMove = forwardMostMove - 2;
13253         whiteTimeRemaining = timeRemaining[0][currentMove];
13254         blackTimeRemaining = timeRemaining[1][currentMove];
13255         DisplayBothClocks();
13256         DisplayMove(currentMove - 1);
13257         ClearHighlights();/*!! could figure this out*/
13258         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13259         SendToProgram("remove\n", &first);
13260         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13261         break;
13262
13263       case BeginningOfGame:
13264       default:
13265         break;
13266
13267       case IcsPlayingWhite:
13268       case IcsPlayingBlack:
13269         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13270             SendToICS(ics_prefix);
13271             SendToICS("takeback 2\n");
13272         } else {
13273             SendToICS(ics_prefix);
13274             SendToICS("takeback 1\n");
13275         }
13276         break;
13277     }
13278 }
13279
13280 void
13281 MoveNowEvent()
13282 {
13283     ChessProgramState *cps;
13284
13285     switch (gameMode) {
13286       case MachinePlaysWhite:
13287         if (!WhiteOnMove(forwardMostMove)) {
13288             DisplayError(_("It is your turn"), 0);
13289             return;
13290         }
13291         cps = &first;
13292         break;
13293       case MachinePlaysBlack:
13294         if (WhiteOnMove(forwardMostMove)) {
13295             DisplayError(_("It is your turn"), 0);
13296             return;
13297         }
13298         cps = &first;
13299         break;
13300       case TwoMachinesPlay:
13301         if (WhiteOnMove(forwardMostMove) ==
13302             (first.twoMachinesColor[0] == 'w')) {
13303             cps = &first;
13304         } else {
13305             cps = &second;
13306         }
13307         break;
13308       case BeginningOfGame:
13309       default:
13310         return;
13311     }
13312     SendToProgram("?\n", cps);
13313 }
13314
13315 void
13316 TruncateGameEvent()
13317 {
13318     EditGameEvent();
13319     if (gameMode != EditGame) return;
13320     TruncateGame();
13321 }
13322
13323 void
13324 TruncateGame()
13325 {
13326     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13327     if (forwardMostMove > currentMove) {
13328         if (gameInfo.resultDetails != NULL) {
13329             free(gameInfo.resultDetails);
13330             gameInfo.resultDetails = NULL;
13331             gameInfo.result = GameUnfinished;
13332         }
13333         forwardMostMove = currentMove;
13334         HistorySet(parseList, backwardMostMove, forwardMostMove,
13335                    currentMove-1);
13336     }
13337 }
13338
13339 void
13340 HintEvent()
13341 {
13342     if (appData.noChessProgram) return;
13343     switch (gameMode) {
13344       case MachinePlaysWhite:
13345         if (WhiteOnMove(forwardMostMove)) {
13346             DisplayError(_("Wait until your turn"), 0);
13347             return;
13348         }
13349         break;
13350       case BeginningOfGame:
13351       case MachinePlaysBlack:
13352         if (!WhiteOnMove(forwardMostMove)) {
13353             DisplayError(_("Wait until your turn"), 0);
13354             return;
13355         }
13356         break;
13357       default:
13358         DisplayError(_("No hint available"), 0);
13359         return;
13360     }
13361     SendToProgram("hint\n", &first);
13362     hintRequested = TRUE;
13363 }
13364
13365 void
13366 BookEvent()
13367 {
13368     if (appData.noChessProgram) return;
13369     switch (gameMode) {
13370       case MachinePlaysWhite:
13371         if (WhiteOnMove(forwardMostMove)) {
13372             DisplayError(_("Wait until your turn"), 0);
13373             return;
13374         }
13375         break;
13376       case BeginningOfGame:
13377       case MachinePlaysBlack:
13378         if (!WhiteOnMove(forwardMostMove)) {
13379             DisplayError(_("Wait until your turn"), 0);
13380             return;
13381         }
13382         break;
13383       case EditPosition:
13384         EditPositionDone(TRUE);
13385         break;
13386       case TwoMachinesPlay:
13387         return;
13388       default:
13389         break;
13390     }
13391     SendToProgram("bk\n", &first);
13392     bookOutput[0] = NULLCHAR;
13393     bookRequested = TRUE;
13394 }
13395
13396 void
13397 AboutGameEvent()
13398 {
13399     char *tags = PGNTags(&gameInfo);
13400     TagsPopUp(tags, CmailMsg());
13401     free(tags);
13402 }
13403
13404 /* end button procedures */
13405
13406 void
13407 PrintPosition(fp, move)
13408      FILE *fp;
13409      int move;
13410 {
13411     int i, j;
13412
13413     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13414         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13415             char c = PieceToChar(boards[move][i][j]);
13416             fputc(c == 'x' ? '.' : c, fp);
13417             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13418         }
13419     }
13420     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13421       fprintf(fp, "white to play\n");
13422     else
13423       fprintf(fp, "black to play\n");
13424 }
13425
13426 void
13427 PrintOpponents(fp)
13428      FILE *fp;
13429 {
13430     if (gameInfo.white != NULL) {
13431         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13432     } else {
13433         fprintf(fp, "\n");
13434     }
13435 }
13436
13437 /* Find last component of program's own name, using some heuristics */
13438 void
13439 TidyProgramName(prog, host, buf)
13440      char *prog, *host, buf[MSG_SIZ];
13441 {
13442     char *p, *q;
13443     int local = (strcmp(host, "localhost") == 0);
13444     while (!local && (p = strchr(prog, ';')) != NULL) {
13445         p++;
13446         while (*p == ' ') p++;
13447         prog = p;
13448     }
13449     if (*prog == '"' || *prog == '\'') {
13450         q = strchr(prog + 1, *prog);
13451     } else {
13452         q = strchr(prog, ' ');
13453     }
13454     if (q == NULL) q = prog + strlen(prog);
13455     p = q;
13456     while (p >= prog && *p != '/' && *p != '\\') p--;
13457     p++;
13458     if(p == prog && *p == '"') p++;
13459     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13460     memcpy(buf, p, q - p);
13461     buf[q - p] = NULLCHAR;
13462     if (!local) {
13463         strcat(buf, "@");
13464         strcat(buf, host);
13465     }
13466 }
13467
13468 char *
13469 TimeControlTagValue()
13470 {
13471     char buf[MSG_SIZ];
13472     if (!appData.clockMode) {
13473       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13474     } else if (movesPerSession > 0) {
13475       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13476     } else if (timeIncrement == 0) {
13477       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13478     } else {
13479       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13480     }
13481     return StrSave(buf);
13482 }
13483
13484 void
13485 SetGameInfo()
13486 {
13487     /* This routine is used only for certain modes */
13488     VariantClass v = gameInfo.variant;
13489     ChessMove r = GameUnfinished;
13490     char *p = NULL;
13491
13492     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13493         r = gameInfo.result;
13494         p = gameInfo.resultDetails;
13495         gameInfo.resultDetails = NULL;
13496     }
13497     ClearGameInfo(&gameInfo);
13498     gameInfo.variant = v;
13499
13500     switch (gameMode) {
13501       case MachinePlaysWhite:
13502         gameInfo.event = StrSave( appData.pgnEventHeader );
13503         gameInfo.site = StrSave(HostName());
13504         gameInfo.date = PGNDate();
13505         gameInfo.round = StrSave("-");
13506         gameInfo.white = StrSave(first.tidy);
13507         gameInfo.black = StrSave(UserName());
13508         gameInfo.timeControl = TimeControlTagValue();
13509         break;
13510
13511       case MachinePlaysBlack:
13512         gameInfo.event = StrSave( appData.pgnEventHeader );
13513         gameInfo.site = StrSave(HostName());
13514         gameInfo.date = PGNDate();
13515         gameInfo.round = StrSave("-");
13516         gameInfo.white = StrSave(UserName());
13517         gameInfo.black = StrSave(first.tidy);
13518         gameInfo.timeControl = TimeControlTagValue();
13519         break;
13520
13521       case TwoMachinesPlay:
13522         gameInfo.event = StrSave( appData.pgnEventHeader );
13523         gameInfo.site = StrSave(HostName());
13524         gameInfo.date = PGNDate();
13525         if (matchGame > 0) {
13526             char buf[MSG_SIZ];
13527             snprintf(buf, MSG_SIZ, "%d", matchGame);
13528             gameInfo.round = StrSave(buf);
13529         } else {
13530             gameInfo.round = StrSave("-");
13531         }
13532         if (first.twoMachinesColor[0] == 'w') {
13533             gameInfo.white = StrSave(first.tidy);
13534             gameInfo.black = StrSave(second.tidy);
13535         } else {
13536             gameInfo.white = StrSave(second.tidy);
13537             gameInfo.black = StrSave(first.tidy);
13538         }
13539         gameInfo.timeControl = TimeControlTagValue();
13540         break;
13541
13542       case EditGame:
13543         gameInfo.event = StrSave("Edited game");
13544         gameInfo.site = StrSave(HostName());
13545         gameInfo.date = PGNDate();
13546         gameInfo.round = StrSave("-");
13547         gameInfo.white = StrSave("-");
13548         gameInfo.black = StrSave("-");
13549         gameInfo.result = r;
13550         gameInfo.resultDetails = p;
13551         break;
13552
13553       case EditPosition:
13554         gameInfo.event = StrSave("Edited position");
13555         gameInfo.site = StrSave(HostName());
13556         gameInfo.date = PGNDate();
13557         gameInfo.round = StrSave("-");
13558         gameInfo.white = StrSave("-");
13559         gameInfo.black = StrSave("-");
13560         break;
13561
13562       case IcsPlayingWhite:
13563       case IcsPlayingBlack:
13564       case IcsObserving:
13565       case IcsExamining:
13566         break;
13567
13568       case PlayFromGameFile:
13569         gameInfo.event = StrSave("Game from non-PGN file");
13570         gameInfo.site = StrSave(HostName());
13571         gameInfo.date = PGNDate();
13572         gameInfo.round = StrSave("-");
13573         gameInfo.white = StrSave("?");
13574         gameInfo.black = StrSave("?");
13575         break;
13576
13577       default:
13578         break;
13579     }
13580 }
13581
13582 void
13583 ReplaceComment(index, text)
13584      int index;
13585      char *text;
13586 {
13587     int len;
13588     char *p;
13589     float score;
13590
13591     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13592        pvInfoList[index-1].depth == len &&
13593        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13594        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13595     while (*text == '\n') text++;
13596     len = strlen(text);
13597     while (len > 0 && text[len - 1] == '\n') len--;
13598
13599     if (commentList[index] != NULL)
13600       free(commentList[index]);
13601
13602     if (len == 0) {
13603         commentList[index] = NULL;
13604         return;
13605     }
13606   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13607       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13608       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13609     commentList[index] = (char *) malloc(len + 2);
13610     strncpy(commentList[index], text, len);
13611     commentList[index][len] = '\n';
13612     commentList[index][len + 1] = NULLCHAR;
13613   } else {
13614     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13615     char *p;
13616     commentList[index] = (char *) malloc(len + 7);
13617     safeStrCpy(commentList[index], "{\n", 3);
13618     safeStrCpy(commentList[index]+2, text, len+1);
13619     commentList[index][len+2] = NULLCHAR;
13620     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13621     strcat(commentList[index], "\n}\n");
13622   }
13623 }
13624
13625 void
13626 CrushCRs(text)
13627      char *text;
13628 {
13629   char *p = text;
13630   char *q = text;
13631   char ch;
13632
13633   do {
13634     ch = *p++;
13635     if (ch == '\r') continue;
13636     *q++ = ch;
13637   } while (ch != '\0');
13638 }
13639
13640 void
13641 AppendComment(index, text, addBraces)
13642      int index;
13643      char *text;
13644      Boolean addBraces; // [HGM] braces: tells if we should add {}
13645 {
13646     int oldlen, len;
13647     char *old;
13648
13649 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13650     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13651
13652     CrushCRs(text);
13653     while (*text == '\n') text++;
13654     len = strlen(text);
13655     while (len > 0 && text[len - 1] == '\n') len--;
13656
13657     if (len == 0) return;
13658
13659     if (commentList[index] != NULL) {
13660         old = commentList[index];
13661         oldlen = strlen(old);
13662         while(commentList[index][oldlen-1] ==  '\n')
13663           commentList[index][--oldlen] = NULLCHAR;
13664         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13665         safeStrCpy(commentList[index], old, oldlen + len + 6);
13666         free(old);
13667         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13668         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13669           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13670           while (*text == '\n') { text++; len--; }
13671           commentList[index][--oldlen] = NULLCHAR;
13672       }
13673         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13674         else          strcat(commentList[index], "\n");
13675         strcat(commentList[index], text);
13676         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13677         else          strcat(commentList[index], "\n");
13678     } else {
13679         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13680         if(addBraces)
13681           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13682         else commentList[index][0] = NULLCHAR;
13683         strcat(commentList[index], text);
13684         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13685         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13686     }
13687 }
13688
13689 static char * FindStr( char * text, char * sub_text )
13690 {
13691     char * result = strstr( text, sub_text );
13692
13693     if( result != NULL ) {
13694         result += strlen( sub_text );
13695     }
13696
13697     return result;
13698 }
13699
13700 /* [AS] Try to extract PV info from PGN comment */
13701 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13702 char *GetInfoFromComment( int index, char * text )
13703 {
13704     char * sep = text, *p;
13705
13706     if( text != NULL && index > 0 ) {
13707         int score = 0;
13708         int depth = 0;
13709         int time = -1, sec = 0, deci;
13710         char * s_eval = FindStr( text, "[%eval " );
13711         char * s_emt = FindStr( text, "[%emt " );
13712
13713         if( s_eval != NULL || s_emt != NULL ) {
13714             /* New style */
13715             char delim;
13716
13717             if( s_eval != NULL ) {
13718                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13719                     return text;
13720                 }
13721
13722                 if( delim != ']' ) {
13723                     return text;
13724                 }
13725             }
13726
13727             if( s_emt != NULL ) {
13728             }
13729                 return text;
13730         }
13731         else {
13732             /* We expect something like: [+|-]nnn.nn/dd */
13733             int score_lo = 0;
13734
13735             if(*text != '{') return text; // [HGM] braces: must be normal comment
13736
13737             sep = strchr( text, '/' );
13738             if( sep == NULL || sep < (text+4) ) {
13739                 return text;
13740             }
13741
13742             p = text;
13743             if(p[1] == '(') { // comment starts with PV
13744                p = strchr(p, ')'); // locate end of PV
13745                if(p == NULL || sep < p+5) return text;
13746                // at this point we have something like "{(.*) +0.23/6 ..."
13747                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13748                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13749                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13750             }
13751             time = -1; sec = -1; deci = -1;
13752             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13753                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13754                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13755                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13756                 return text;
13757             }
13758
13759             if( score_lo < 0 || score_lo >= 100 ) {
13760                 return text;
13761             }
13762
13763             if(sec >= 0) time = 600*time + 10*sec; else
13764             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13765
13766             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13767
13768             /* [HGM] PV time: now locate end of PV info */
13769             while( *++sep >= '0' && *sep <= '9'); // strip depth
13770             if(time >= 0)
13771             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13772             if(sec >= 0)
13773             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13774             if(deci >= 0)
13775             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13776             while(*sep == ' ') sep++;
13777         }
13778
13779         if( depth <= 0 ) {
13780             return text;
13781         }
13782
13783         if( time < 0 ) {
13784             time = -1;
13785         }
13786
13787         pvInfoList[index-1].depth = depth;
13788         pvInfoList[index-1].score = score;
13789         pvInfoList[index-1].time  = 10*time; // centi-sec
13790         if(*sep == '}') *sep = 0; else *--sep = '{';
13791         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13792     }
13793     return sep;
13794 }
13795
13796 void
13797 SendToProgram(message, cps)
13798      char *message;
13799      ChessProgramState *cps;
13800 {
13801     int count, outCount, error;
13802     char buf[MSG_SIZ];
13803
13804     if (cps->pr == NULL) return;
13805     Attention(cps);
13806
13807     if (appData.debugMode) {
13808         TimeMark now;
13809         GetTimeMark(&now);
13810         fprintf(debugFP, "%ld >%-6s: %s",
13811                 SubtractTimeMarks(&now, &programStartTime),
13812                 cps->which, message);
13813     }
13814
13815     count = strlen(message);
13816     outCount = OutputToProcess(cps->pr, message, count, &error);
13817     if (outCount < count && !exiting
13818                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13819       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
13820       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13821         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13822             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13823                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13824                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13825             } else {
13826                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13827             }
13828             gameInfo.resultDetails = StrSave(buf);
13829         }
13830         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13831     }
13832 }
13833
13834 void
13835 ReceiveFromProgram(isr, closure, message, count, error)
13836      InputSourceRef isr;
13837      VOIDSTAR closure;
13838      char *message;
13839      int count;
13840      int error;
13841 {
13842     char *end_str;
13843     char buf[MSG_SIZ];
13844     ChessProgramState *cps = (ChessProgramState *)closure;
13845
13846     if (isr != cps->isr) return; /* Killed intentionally */
13847     if (count <= 0) {
13848         if (count == 0) {
13849             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13850                     _(cps->which), cps->program);
13851         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13852                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13853                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13854                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13855                 } else {
13856                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13857                 }
13858                 gameInfo.resultDetails = StrSave(buf);
13859             }
13860             RemoveInputSource(cps->isr);
13861             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13862         } else {
13863             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13864                     _(cps->which), cps->program);
13865             RemoveInputSource(cps->isr);
13866
13867             /* [AS] Program is misbehaving badly... kill it */
13868             if( count == -2 ) {
13869                 DestroyChildProcess( cps->pr, 9 );
13870                 cps->pr = NoProc;
13871             }
13872
13873             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13874         }
13875         return;
13876     }
13877
13878     if ((end_str = strchr(message, '\r')) != NULL)
13879       *end_str = NULLCHAR;
13880     if ((end_str = strchr(message, '\n')) != NULL)
13881       *end_str = NULLCHAR;
13882
13883     if (appData.debugMode) {
13884         TimeMark now; int print = 1;
13885         char *quote = ""; char c; int i;
13886
13887         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13888                 char start = message[0];
13889                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13890                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13891                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13892                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13893                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13894                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13895                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13896                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13897                    sscanf(message, "hint: %c", &c)!=1 && 
13898                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13899                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13900                     print = (appData.engineComments >= 2);
13901                 }
13902                 message[0] = start; // restore original message
13903         }
13904         if(print) {
13905                 GetTimeMark(&now);
13906                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13907                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13908                         quote,
13909                         message);
13910         }
13911     }
13912
13913     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13914     if (appData.icsEngineAnalyze) {
13915         if (strstr(message, "whisper") != NULL ||
13916              strstr(message, "kibitz") != NULL ||
13917             strstr(message, "tellics") != NULL) return;
13918     }
13919
13920     HandleMachineMove(message, cps);
13921 }
13922
13923
13924 void
13925 SendTimeControl(cps, mps, tc, inc, sd, st)
13926      ChessProgramState *cps;
13927      int mps, inc, sd, st;
13928      long tc;
13929 {
13930     char buf[MSG_SIZ];
13931     int seconds;
13932
13933     if( timeControl_2 > 0 ) {
13934         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13935             tc = timeControl_2;
13936         }
13937     }
13938     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13939     inc /= cps->timeOdds;
13940     st  /= cps->timeOdds;
13941
13942     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13943
13944     if (st > 0) {
13945       /* Set exact time per move, normally using st command */
13946       if (cps->stKludge) {
13947         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13948         seconds = st % 60;
13949         if (seconds == 0) {
13950           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13951         } else {
13952           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13953         }
13954       } else {
13955         snprintf(buf, MSG_SIZ, "st %d\n", st);
13956       }
13957     } else {
13958       /* Set conventional or incremental time control, using level command */
13959       if (seconds == 0) {
13960         /* Note old gnuchess bug -- minutes:seconds used to not work.
13961            Fixed in later versions, but still avoid :seconds
13962            when seconds is 0. */
13963         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13964       } else {
13965         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13966                  seconds, inc/1000.);
13967       }
13968     }
13969     SendToProgram(buf, cps);
13970
13971     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13972     /* Orthogonally, limit search to given depth */
13973     if (sd > 0) {
13974       if (cps->sdKludge) {
13975         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13976       } else {
13977         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13978       }
13979       SendToProgram(buf, cps);
13980     }
13981
13982     if(cps->nps >= 0) { /* [HGM] nps */
13983         if(cps->supportsNPS == FALSE)
13984           cps->nps = -1; // don't use if engine explicitly says not supported!
13985         else {
13986           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13987           SendToProgram(buf, cps);
13988         }
13989     }
13990 }
13991
13992 ChessProgramState *WhitePlayer()
13993 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13994 {
13995     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13996        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13997         return &second;
13998     return &first;
13999 }
14000
14001 void
14002 SendTimeRemaining(cps, machineWhite)
14003      ChessProgramState *cps;
14004      int /*boolean*/ machineWhite;
14005 {
14006     char message[MSG_SIZ];
14007     long time, otime;
14008
14009     /* Note: this routine must be called when the clocks are stopped
14010        or when they have *just* been set or switched; otherwise
14011        it will be off by the time since the current tick started.
14012     */
14013     if (machineWhite) {
14014         time = whiteTimeRemaining / 10;
14015         otime = blackTimeRemaining / 10;
14016     } else {
14017         time = blackTimeRemaining / 10;
14018         otime = whiteTimeRemaining / 10;
14019     }
14020     /* [HGM] translate opponent's time by time-odds factor */
14021     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14022     if (appData.debugMode) {
14023         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14024     }
14025
14026     if (time <= 0) time = 1;
14027     if (otime <= 0) otime = 1;
14028
14029     snprintf(message, MSG_SIZ, "time %ld\n", time);
14030     SendToProgram(message, cps);
14031
14032     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14033     SendToProgram(message, cps);
14034 }
14035
14036 int
14037 BoolFeature(p, name, loc, cps)
14038      char **p;
14039      char *name;
14040      int *loc;
14041      ChessProgramState *cps;
14042 {
14043   char buf[MSG_SIZ];
14044   int len = strlen(name);
14045   int val;
14046
14047   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14048     (*p) += len + 1;
14049     sscanf(*p, "%d", &val);
14050     *loc = (val != 0);
14051     while (**p && **p != ' ')
14052       (*p)++;
14053     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14054     SendToProgram(buf, cps);
14055     return TRUE;
14056   }
14057   return FALSE;
14058 }
14059
14060 int
14061 IntFeature(p, name, loc, cps)
14062      char **p;
14063      char *name;
14064      int *loc;
14065      ChessProgramState *cps;
14066 {
14067   char buf[MSG_SIZ];
14068   int len = strlen(name);
14069   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14070     (*p) += len + 1;
14071     sscanf(*p, "%d", loc);
14072     while (**p && **p != ' ') (*p)++;
14073     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14074     SendToProgram(buf, cps);
14075     return TRUE;
14076   }
14077   return FALSE;
14078 }
14079
14080 int
14081 StringFeature(p, name, loc, cps)
14082      char **p;
14083      char *name;
14084      char loc[];
14085      ChessProgramState *cps;
14086 {
14087   char buf[MSG_SIZ];
14088   int len = strlen(name);
14089   if (strncmp((*p), name, len) == 0
14090       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14091     (*p) += len + 2;
14092     sscanf(*p, "%[^\"]", loc);
14093     while (**p && **p != '\"') (*p)++;
14094     if (**p == '\"') (*p)++;
14095     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14096     SendToProgram(buf, cps);
14097     return TRUE;
14098   }
14099   return FALSE;
14100 }
14101
14102 int
14103 ParseOption(Option *opt, ChessProgramState *cps)
14104 // [HGM] options: process the string that defines an engine option, and determine
14105 // name, type, default value, and allowed value range
14106 {
14107         char *p, *q, buf[MSG_SIZ];
14108         int n, min = (-1)<<31, max = 1<<31, def;
14109
14110         if(p = strstr(opt->name, " -spin ")) {
14111             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14112             if(max < min) max = min; // enforce consistency
14113             if(def < min) def = min;
14114             if(def > max) def = max;
14115             opt->value = def;
14116             opt->min = min;
14117             opt->max = max;
14118             opt->type = Spin;
14119         } else if((p = strstr(opt->name, " -slider "))) {
14120             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14121             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14122             if(max < min) max = min; // enforce consistency
14123             if(def < min) def = min;
14124             if(def > max) def = max;
14125             opt->value = def;
14126             opt->min = min;
14127             opt->max = max;
14128             opt->type = Spin; // Slider;
14129         } else if((p = strstr(opt->name, " -string "))) {
14130             opt->textValue = p+9;
14131             opt->type = TextBox;
14132         } else if((p = strstr(opt->name, " -file "))) {
14133             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14134             opt->textValue = p+7;
14135             opt->type = FileName; // FileName;
14136         } else if((p = strstr(opt->name, " -path "))) {
14137             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14138             opt->textValue = p+7;
14139             opt->type = PathName; // PathName;
14140         } else if(p = strstr(opt->name, " -check ")) {
14141             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14142             opt->value = (def != 0);
14143             opt->type = CheckBox;
14144         } else if(p = strstr(opt->name, " -combo ")) {
14145             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14146             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14147             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14148             opt->value = n = 0;
14149             while(q = StrStr(q, " /// ")) {
14150                 n++; *q = 0;    // count choices, and null-terminate each of them
14151                 q += 5;
14152                 if(*q == '*') { // remember default, which is marked with * prefix
14153                     q++;
14154                     opt->value = n;
14155                 }
14156                 cps->comboList[cps->comboCnt++] = q;
14157             }
14158             cps->comboList[cps->comboCnt++] = NULL;
14159             opt->max = n + 1;
14160             opt->type = ComboBox;
14161         } else if(p = strstr(opt->name, " -button")) {
14162             opt->type = Button;
14163         } else if(p = strstr(opt->name, " -save")) {
14164             opt->type = SaveButton;
14165         } else return FALSE;
14166         *p = 0; // terminate option name
14167         // now look if the command-line options define a setting for this engine option.
14168         if(cps->optionSettings && cps->optionSettings[0])
14169             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14170         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14171           snprintf(buf, MSG_SIZ, "option %s", p);
14172                 if(p = strstr(buf, ",")) *p = 0;
14173                 if(q = strchr(buf, '=')) switch(opt->type) {
14174                     case ComboBox:
14175                         for(n=0; n<opt->max; n++)
14176                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14177                         break;
14178                     case TextBox:
14179                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14180                         break;
14181                     case Spin:
14182                     case CheckBox:
14183                         opt->value = atoi(q+1);
14184                     default:
14185                         break;
14186                 }
14187                 strcat(buf, "\n");
14188                 SendToProgram(buf, cps);
14189         }
14190         return TRUE;
14191 }
14192
14193 void
14194 FeatureDone(cps, val)
14195      ChessProgramState* cps;
14196      int val;
14197 {
14198   DelayedEventCallback cb = GetDelayedEvent();
14199   if ((cb == InitBackEnd3 && cps == &first) ||
14200       (cb == SettingsMenuIfReady && cps == &second) ||
14201       (cb == LoadEngine) ||
14202       (cb == TwoMachinesEventIfReady && cps == &second)) {
14203     CancelDelayedEvent();
14204     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14205   }
14206   cps->initDone = val;
14207 }
14208
14209 /* Parse feature command from engine */
14210 void
14211 ParseFeatures(args, cps)
14212      char* args;
14213      ChessProgramState *cps;
14214 {
14215   char *p = args;
14216   char *q;
14217   int val;
14218   char buf[MSG_SIZ];
14219
14220   for (;;) {
14221     while (*p == ' ') p++;
14222     if (*p == NULLCHAR) return;
14223
14224     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14225     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14226     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14227     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14228     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14229     if (BoolFeature(&p, "reuse", &val, cps)) {
14230       /* Engine can disable reuse, but can't enable it if user said no */
14231       if (!val) cps->reuse = FALSE;
14232       continue;
14233     }
14234     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14235     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14236       if (gameMode == TwoMachinesPlay) {
14237         DisplayTwoMachinesTitle();
14238       } else {
14239         DisplayTitle("");
14240       }
14241       continue;
14242     }
14243     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14244     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14245     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14246     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14247     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14248     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14249     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14250     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14251     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14252     if (IntFeature(&p, "done", &val, cps)) {
14253       FeatureDone(cps, val);
14254       continue;
14255     }
14256     /* Added by Tord: */
14257     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14258     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14259     /* End of additions by Tord */
14260
14261     /* [HGM] added features: */
14262     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14263     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14264     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14265     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14266     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14267     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14268     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14269         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14270           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14271             SendToProgram(buf, cps);
14272             continue;
14273         }
14274         if(cps->nrOptions >= MAX_OPTIONS) {
14275             cps->nrOptions--;
14276             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14277             DisplayError(buf, 0);
14278         }
14279         continue;
14280     }
14281     /* End of additions by HGM */
14282
14283     /* unknown feature: complain and skip */
14284     q = p;
14285     while (*q && *q != '=') q++;
14286     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14287     SendToProgram(buf, cps);
14288     p = q;
14289     if (*p == '=') {
14290       p++;
14291       if (*p == '\"') {
14292         p++;
14293         while (*p && *p != '\"') p++;
14294         if (*p == '\"') p++;
14295       } else {
14296         while (*p && *p != ' ') p++;
14297       }
14298     }
14299   }
14300
14301 }
14302
14303 void
14304 PeriodicUpdatesEvent(newState)
14305      int newState;
14306 {
14307     if (newState == appData.periodicUpdates)
14308       return;
14309
14310     appData.periodicUpdates=newState;
14311
14312     /* Display type changes, so update it now */
14313 //    DisplayAnalysis();
14314
14315     /* Get the ball rolling again... */
14316     if (newState) {
14317         AnalysisPeriodicEvent(1);
14318         StartAnalysisClock();
14319     }
14320 }
14321
14322 void
14323 PonderNextMoveEvent(newState)
14324      int newState;
14325 {
14326     if (newState == appData.ponderNextMove) return;
14327     if (gameMode == EditPosition) EditPositionDone(TRUE);
14328     if (newState) {
14329         SendToProgram("hard\n", &first);
14330         if (gameMode == TwoMachinesPlay) {
14331             SendToProgram("hard\n", &second);
14332         }
14333     } else {
14334         SendToProgram("easy\n", &first);
14335         thinkOutput[0] = NULLCHAR;
14336         if (gameMode == TwoMachinesPlay) {
14337             SendToProgram("easy\n", &second);
14338         }
14339     }
14340     appData.ponderNextMove = newState;
14341 }
14342
14343 void
14344 NewSettingEvent(option, feature, command, value)
14345      char *command;
14346      int option, value, *feature;
14347 {
14348     char buf[MSG_SIZ];
14349
14350     if (gameMode == EditPosition) EditPositionDone(TRUE);
14351     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14352     if(feature == NULL || *feature) SendToProgram(buf, &first);
14353     if (gameMode == TwoMachinesPlay) {
14354         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14355     }
14356 }
14357
14358 void
14359 ShowThinkingEvent()
14360 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14361 {
14362     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14363     int newState = appData.showThinking
14364         // [HGM] thinking: other features now need thinking output as well
14365         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14366
14367     if (oldState == newState) return;
14368     oldState = newState;
14369     if (gameMode == EditPosition) EditPositionDone(TRUE);
14370     if (oldState) {
14371         SendToProgram("post\n", &first);
14372         if (gameMode == TwoMachinesPlay) {
14373             SendToProgram("post\n", &second);
14374         }
14375     } else {
14376         SendToProgram("nopost\n", &first);
14377         thinkOutput[0] = NULLCHAR;
14378         if (gameMode == TwoMachinesPlay) {
14379             SendToProgram("nopost\n", &second);
14380         }
14381     }
14382 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14383 }
14384
14385 void
14386 AskQuestionEvent(title, question, replyPrefix, which)
14387      char *title; char *question; char *replyPrefix; char *which;
14388 {
14389   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14390   if (pr == NoProc) return;
14391   AskQuestion(title, question, replyPrefix, pr);
14392 }
14393
14394 void
14395 TypeInEvent(char firstChar)
14396 {
14397     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14398         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14399         gameMode == AnalyzeMode || gameMode == EditGame || \r
14400         gameMode == EditPosition || gameMode == IcsExamining ||\r
14401         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14402         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14403                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14404                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14405         gameMode == Training) PopUpMoveDialog(firstChar);
14406 }
14407
14408 void
14409 TypeInDoneEvent(char *move)
14410 {
14411         Board board;
14412         int n, fromX, fromY, toX, toY;
14413         char promoChar;
14414         ChessMove moveType;\r
14415
14416         // [HGM] FENedit\r
14417         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14418                 EditPositionPasteFEN(move);\r
14419                 return;\r
14420         }\r
14421         // [HGM] movenum: allow move number to be typed in any mode\r
14422         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14423           ToNrEvent(2*n-1);\r
14424           return;\r
14425         }\r
14426
14427       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14428         gameMode != Training) {\r
14429         DisplayMoveError(_("Displayed move is not current"));\r
14430       } else {\r
14431         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14432           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14433         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14434         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14435           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14436           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14437         } else {\r
14438           DisplayMoveError(_("Could not parse move"));\r
14439         }
14440       }\r
14441 }\r
14442
14443 void
14444 DisplayMove(moveNumber)
14445      int moveNumber;
14446 {
14447     char message[MSG_SIZ];
14448     char res[MSG_SIZ];
14449     char cpThinkOutput[MSG_SIZ];
14450
14451     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14452
14453     if (moveNumber == forwardMostMove - 1 ||
14454         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14455
14456         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14457
14458         if (strchr(cpThinkOutput, '\n')) {
14459             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14460         }
14461     } else {
14462         *cpThinkOutput = NULLCHAR;
14463     }
14464
14465     /* [AS] Hide thinking from human user */
14466     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14467         *cpThinkOutput = NULLCHAR;
14468         if( thinkOutput[0] != NULLCHAR ) {
14469             int i;
14470
14471             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14472                 cpThinkOutput[i] = '.';
14473             }
14474             cpThinkOutput[i] = NULLCHAR;
14475             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14476         }
14477     }
14478
14479     if (moveNumber == forwardMostMove - 1 &&
14480         gameInfo.resultDetails != NULL) {
14481         if (gameInfo.resultDetails[0] == NULLCHAR) {
14482           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14483         } else {
14484           snprintf(res, MSG_SIZ, " {%s} %s",
14485                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14486         }
14487     } else {
14488         res[0] = NULLCHAR;
14489     }
14490
14491     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14492         DisplayMessage(res, cpThinkOutput);
14493     } else {
14494       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14495                 WhiteOnMove(moveNumber) ? " " : ".. ",
14496                 parseList[moveNumber], res);
14497         DisplayMessage(message, cpThinkOutput);
14498     }
14499 }
14500
14501 void
14502 DisplayComment(moveNumber, text)
14503      int moveNumber;
14504      char *text;
14505 {
14506     char title[MSG_SIZ];
14507     char buf[8000]; // comment can be long!
14508     int score, depth;
14509
14510     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14511       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14512     } else {
14513       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14514               WhiteOnMove(moveNumber) ? " " : ".. ",
14515               parseList[moveNumber]);
14516     }
14517     // [HGM] PV info: display PV info together with (or as) comment
14518     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14519       if(text == NULL) text = "";
14520       score = pvInfoList[moveNumber].score;
14521       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14522               depth, (pvInfoList[moveNumber].time+50)/100, text);
14523       text = buf;
14524     }
14525     if (text != NULL && (appData.autoDisplayComment || commentUp))
14526         CommentPopUp(title, text);
14527 }
14528
14529 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14530  * might be busy thinking or pondering.  It can be omitted if your
14531  * gnuchess is configured to stop thinking immediately on any user
14532  * input.  However, that gnuchess feature depends on the FIONREAD
14533  * ioctl, which does not work properly on some flavors of Unix.
14534  */
14535 void
14536 Attention(cps)
14537      ChessProgramState *cps;
14538 {
14539 #if ATTENTION
14540     if (!cps->useSigint) return;
14541     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14542     switch (gameMode) {
14543       case MachinePlaysWhite:
14544       case MachinePlaysBlack:
14545       case TwoMachinesPlay:
14546       case IcsPlayingWhite:
14547       case IcsPlayingBlack:
14548       case AnalyzeMode:
14549       case AnalyzeFile:
14550         /* Skip if we know it isn't thinking */
14551         if (!cps->maybeThinking) return;
14552         if (appData.debugMode)
14553           fprintf(debugFP, "Interrupting %s\n", cps->which);
14554         InterruptChildProcess(cps->pr);
14555         cps->maybeThinking = FALSE;
14556         break;
14557       default:
14558         break;
14559     }
14560 #endif /*ATTENTION*/
14561 }
14562
14563 int
14564 CheckFlags()
14565 {
14566     if (whiteTimeRemaining <= 0) {
14567         if (!whiteFlag) {
14568             whiteFlag = TRUE;
14569             if (appData.icsActive) {
14570                 if (appData.autoCallFlag &&
14571                     gameMode == IcsPlayingBlack && !blackFlag) {
14572                   SendToICS(ics_prefix);
14573                   SendToICS("flag\n");
14574                 }
14575             } else {
14576                 if (blackFlag) {
14577                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14578                 } else {
14579                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14580                     if (appData.autoCallFlag) {
14581                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14582                         return TRUE;
14583                     }
14584                 }
14585             }
14586         }
14587     }
14588     if (blackTimeRemaining <= 0) {
14589         if (!blackFlag) {
14590             blackFlag = TRUE;
14591             if (appData.icsActive) {
14592                 if (appData.autoCallFlag &&
14593                     gameMode == IcsPlayingWhite && !whiteFlag) {
14594                   SendToICS(ics_prefix);
14595                   SendToICS("flag\n");
14596                 }
14597             } else {
14598                 if (whiteFlag) {
14599                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14600                 } else {
14601                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14602                     if (appData.autoCallFlag) {
14603                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14604                         return TRUE;
14605                     }
14606                 }
14607             }
14608         }
14609     }
14610     return FALSE;
14611 }
14612
14613 void
14614 CheckTimeControl()
14615 {
14616     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14617         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14618
14619     /*
14620      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14621      */
14622     if ( !WhiteOnMove(forwardMostMove) ) {
14623         /* White made time control */
14624         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14625         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14626         /* [HGM] time odds: correct new time quota for time odds! */
14627                                             / WhitePlayer()->timeOdds;
14628         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14629     } else {
14630         lastBlack -= blackTimeRemaining;
14631         /* Black made time control */
14632         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14633                                             / WhitePlayer()->other->timeOdds;
14634         lastWhite = whiteTimeRemaining;
14635     }
14636 }
14637
14638 void
14639 DisplayBothClocks()
14640 {
14641     int wom = gameMode == EditPosition ?
14642       !blackPlaysFirst : WhiteOnMove(currentMove);
14643     DisplayWhiteClock(whiteTimeRemaining, wom);
14644     DisplayBlackClock(blackTimeRemaining, !wom);
14645 }
14646
14647
14648 /* Timekeeping seems to be a portability nightmare.  I think everyone
14649    has ftime(), but I'm really not sure, so I'm including some ifdefs
14650    to use other calls if you don't.  Clocks will be less accurate if
14651    you have neither ftime nor gettimeofday.
14652 */
14653
14654 /* VS 2008 requires the #include outside of the function */
14655 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14656 #include <sys/timeb.h>
14657 #endif
14658
14659 /* Get the current time as a TimeMark */
14660 void
14661 GetTimeMark(tm)
14662      TimeMark *tm;
14663 {
14664 #if HAVE_GETTIMEOFDAY
14665
14666     struct timeval timeVal;
14667     struct timezone timeZone;
14668
14669     gettimeofday(&timeVal, &timeZone);
14670     tm->sec = (long) timeVal.tv_sec;
14671     tm->ms = (int) (timeVal.tv_usec / 1000L);
14672
14673 #else /*!HAVE_GETTIMEOFDAY*/
14674 #if HAVE_FTIME
14675
14676 // include <sys/timeb.h> / moved to just above start of function
14677     struct timeb timeB;
14678
14679     ftime(&timeB);
14680     tm->sec = (long) timeB.time;
14681     tm->ms = (int) timeB.millitm;
14682
14683 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14684     tm->sec = (long) time(NULL);
14685     tm->ms = 0;
14686 #endif
14687 #endif
14688 }
14689
14690 /* Return the difference in milliseconds between two
14691    time marks.  We assume the difference will fit in a long!
14692 */
14693 long
14694 SubtractTimeMarks(tm2, tm1)
14695      TimeMark *tm2, *tm1;
14696 {
14697     return 1000L*(tm2->sec - tm1->sec) +
14698            (long) (tm2->ms - tm1->ms);
14699 }
14700
14701
14702 /*
14703  * Code to manage the game clocks.
14704  *
14705  * In tournament play, black starts the clock and then white makes a move.
14706  * We give the human user a slight advantage if he is playing white---the
14707  * clocks don't run until he makes his first move, so it takes zero time.
14708  * Also, we don't account for network lag, so we could get out of sync
14709  * with GNU Chess's clock -- but then, referees are always right.
14710  */
14711
14712 static TimeMark tickStartTM;
14713 static long intendedTickLength;
14714
14715 long
14716 NextTickLength(timeRemaining)
14717      long timeRemaining;
14718 {
14719     long nominalTickLength, nextTickLength;
14720
14721     if (timeRemaining > 0L && timeRemaining <= 10000L)
14722       nominalTickLength = 100L;
14723     else
14724       nominalTickLength = 1000L;
14725     nextTickLength = timeRemaining % nominalTickLength;
14726     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14727
14728     return nextTickLength;
14729 }
14730
14731 /* Adjust clock one minute up or down */
14732 void
14733 AdjustClock(Boolean which, int dir)
14734 {
14735     if(which) blackTimeRemaining += 60000*dir;
14736     else      whiteTimeRemaining += 60000*dir;
14737     DisplayBothClocks();
14738 }
14739
14740 /* Stop clocks and reset to a fresh time control */
14741 void
14742 ResetClocks()
14743 {
14744     (void) StopClockTimer();
14745     if (appData.icsActive) {
14746         whiteTimeRemaining = blackTimeRemaining = 0;
14747     } else if (searchTime) {
14748         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14749         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14750     } else { /* [HGM] correct new time quote for time odds */
14751         whiteTC = blackTC = fullTimeControlString;
14752         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14753         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14754     }
14755     if (whiteFlag || blackFlag) {
14756         DisplayTitle("");
14757         whiteFlag = blackFlag = FALSE;
14758     }
14759     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14760     DisplayBothClocks();
14761 }
14762
14763 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14764
14765 /* Decrement running clock by amount of time that has passed */
14766 void
14767 DecrementClocks()
14768 {
14769     long timeRemaining;
14770     long lastTickLength, fudge;
14771     TimeMark now;
14772
14773     if (!appData.clockMode) return;
14774     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14775
14776     GetTimeMark(&now);
14777
14778     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14779
14780     /* Fudge if we woke up a little too soon */
14781     fudge = intendedTickLength - lastTickLength;
14782     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14783
14784     if (WhiteOnMove(forwardMostMove)) {
14785         if(whiteNPS >= 0) lastTickLength = 0;
14786         timeRemaining = whiteTimeRemaining -= lastTickLength;
14787         if(timeRemaining < 0 && !appData.icsActive) {
14788             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14789             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14790                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14791                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14792             }
14793         }
14794         DisplayWhiteClock(whiteTimeRemaining - fudge,
14795                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14796     } else {
14797         if(blackNPS >= 0) lastTickLength = 0;
14798         timeRemaining = blackTimeRemaining -= lastTickLength;
14799         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14800             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14801             if(suddenDeath) {
14802                 blackStartMove = forwardMostMove;
14803                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14804             }
14805         }
14806         DisplayBlackClock(blackTimeRemaining - fudge,
14807                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14808     }
14809     if (CheckFlags()) return;
14810
14811     tickStartTM = now;
14812     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14813     StartClockTimer(intendedTickLength);
14814
14815     /* if the time remaining has fallen below the alarm threshold, sound the
14816      * alarm. if the alarm has sounded and (due to a takeback or time control
14817      * with increment) the time remaining has increased to a level above the
14818      * threshold, reset the alarm so it can sound again.
14819      */
14820
14821     if (appData.icsActive && appData.icsAlarm) {
14822
14823         /* make sure we are dealing with the user's clock */
14824         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14825                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14826            )) return;
14827
14828         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14829             alarmSounded = FALSE;
14830         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14831             PlayAlarmSound();
14832             alarmSounded = TRUE;
14833         }
14834     }
14835 }
14836
14837
14838 /* A player has just moved, so stop the previously running
14839    clock and (if in clock mode) start the other one.
14840    We redisplay both clocks in case we're in ICS mode, because
14841    ICS gives us an update to both clocks after every move.
14842    Note that this routine is called *after* forwardMostMove
14843    is updated, so the last fractional tick must be subtracted
14844    from the color that is *not* on move now.
14845 */
14846 void
14847 SwitchClocks(int newMoveNr)
14848 {
14849     long lastTickLength;
14850     TimeMark now;
14851     int flagged = FALSE;
14852
14853     GetTimeMark(&now);
14854
14855     if (StopClockTimer() && appData.clockMode) {
14856         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14857         if (!WhiteOnMove(forwardMostMove)) {
14858             if(blackNPS >= 0) lastTickLength = 0;
14859             blackTimeRemaining -= lastTickLength;
14860            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14861 //         if(pvInfoList[forwardMostMove].time == -1)
14862                  pvInfoList[forwardMostMove].time =               // use GUI time
14863                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14864         } else {
14865            if(whiteNPS >= 0) lastTickLength = 0;
14866            whiteTimeRemaining -= lastTickLength;
14867            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14868 //         if(pvInfoList[forwardMostMove].time == -1)
14869                  pvInfoList[forwardMostMove].time =
14870                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14871         }
14872         flagged = CheckFlags();
14873     }
14874     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14875     CheckTimeControl();
14876
14877     if (flagged || !appData.clockMode) return;
14878
14879     switch (gameMode) {
14880       case MachinePlaysBlack:
14881       case MachinePlaysWhite:
14882       case BeginningOfGame:
14883         if (pausing) return;
14884         break;
14885
14886       case EditGame:
14887       case PlayFromGameFile:
14888       case IcsExamining:
14889         return;
14890
14891       default:
14892         break;
14893     }
14894
14895     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14896         if(WhiteOnMove(forwardMostMove))
14897              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14898         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14899     }
14900
14901     tickStartTM = now;
14902     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14903       whiteTimeRemaining : blackTimeRemaining);
14904     StartClockTimer(intendedTickLength);
14905 }
14906
14907
14908 /* Stop both clocks */
14909 void
14910 StopClocks()
14911 {
14912     long lastTickLength;
14913     TimeMark now;
14914
14915     if (!StopClockTimer()) return;
14916     if (!appData.clockMode) return;
14917
14918     GetTimeMark(&now);
14919
14920     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14921     if (WhiteOnMove(forwardMostMove)) {
14922         if(whiteNPS >= 0) lastTickLength = 0;
14923         whiteTimeRemaining -= lastTickLength;
14924         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14925     } else {
14926         if(blackNPS >= 0) lastTickLength = 0;
14927         blackTimeRemaining -= lastTickLength;
14928         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14929     }
14930     CheckFlags();
14931 }
14932
14933 /* Start clock of player on move.  Time may have been reset, so
14934    if clock is already running, stop and restart it. */
14935 void
14936 StartClocks()
14937 {
14938     (void) StopClockTimer(); /* in case it was running already */
14939     DisplayBothClocks();
14940     if (CheckFlags()) return;
14941
14942     if (!appData.clockMode) return;
14943     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14944
14945     GetTimeMark(&tickStartTM);
14946     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14947       whiteTimeRemaining : blackTimeRemaining);
14948
14949    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14950     whiteNPS = blackNPS = -1;
14951     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14952        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14953         whiteNPS = first.nps;
14954     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14955        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14956         blackNPS = first.nps;
14957     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14958         whiteNPS = second.nps;
14959     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14960         blackNPS = second.nps;
14961     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14962
14963     StartClockTimer(intendedTickLength);
14964 }
14965
14966 char *
14967 TimeString(ms)
14968      long ms;
14969 {
14970     long second, minute, hour, day;
14971     char *sign = "";
14972     static char buf[32];
14973
14974     if (ms > 0 && ms <= 9900) {
14975       /* convert milliseconds to tenths, rounding up */
14976       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14977
14978       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14979       return buf;
14980     }
14981
14982     /* convert milliseconds to seconds, rounding up */
14983     /* use floating point to avoid strangeness of integer division
14984        with negative dividends on many machines */
14985     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14986
14987     if (second < 0) {
14988         sign = "-";
14989         second = -second;
14990     }
14991
14992     day = second / (60 * 60 * 24);
14993     second = second % (60 * 60 * 24);
14994     hour = second / (60 * 60);
14995     second = second % (60 * 60);
14996     minute = second / 60;
14997     second = second % 60;
14998
14999     if (day > 0)
15000       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15001               sign, day, hour, minute, second);
15002     else if (hour > 0)
15003       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15004     else
15005       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15006
15007     return buf;
15008 }
15009
15010
15011 /*
15012  * This is necessary because some C libraries aren't ANSI C compliant yet.
15013  */
15014 char *
15015 StrStr(string, match)
15016      char *string, *match;
15017 {
15018     int i, length;
15019
15020     length = strlen(match);
15021
15022     for (i = strlen(string) - length; i >= 0; i--, string++)
15023       if (!strncmp(match, string, length))
15024         return string;
15025
15026     return NULL;
15027 }
15028
15029 char *
15030 StrCaseStr(string, match)
15031      char *string, *match;
15032 {
15033     int i, j, length;
15034
15035     length = strlen(match);
15036
15037     for (i = strlen(string) - length; i >= 0; i--, string++) {
15038         for (j = 0; j < length; j++) {
15039             if (ToLower(match[j]) != ToLower(string[j]))
15040               break;
15041         }
15042         if (j == length) return string;
15043     }
15044
15045     return NULL;
15046 }
15047
15048 #ifndef _amigados
15049 int
15050 StrCaseCmp(s1, s2)
15051      char *s1, *s2;
15052 {
15053     char c1, c2;
15054
15055     for (;;) {
15056         c1 = ToLower(*s1++);
15057         c2 = ToLower(*s2++);
15058         if (c1 > c2) return 1;
15059         if (c1 < c2) return -1;
15060         if (c1 == NULLCHAR) return 0;
15061     }
15062 }
15063
15064
15065 int
15066 ToLower(c)
15067      int c;
15068 {
15069     return isupper(c) ? tolower(c) : c;
15070 }
15071
15072
15073 int
15074 ToUpper(c)
15075      int c;
15076 {
15077     return islower(c) ? toupper(c) : c;
15078 }
15079 #endif /* !_amigados    */
15080
15081 char *
15082 StrSave(s)
15083      char *s;
15084 {
15085   char *ret;
15086
15087   if ((ret = (char *) malloc(strlen(s) + 1)))
15088     {
15089       safeStrCpy(ret, s, strlen(s)+1);
15090     }
15091   return ret;
15092 }
15093
15094 char *
15095 StrSavePtr(s, savePtr)
15096      char *s, **savePtr;
15097 {
15098     if (*savePtr) {
15099         free(*savePtr);
15100     }
15101     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15102       safeStrCpy(*savePtr, s, strlen(s)+1);
15103     }
15104     return(*savePtr);
15105 }
15106
15107 char *
15108 PGNDate()
15109 {
15110     time_t clock;
15111     struct tm *tm;
15112     char buf[MSG_SIZ];
15113
15114     clock = time((time_t *)NULL);
15115     tm = localtime(&clock);
15116     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15117             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15118     return StrSave(buf);
15119 }
15120
15121
15122 char *
15123 PositionToFEN(move, overrideCastling)
15124      int move;
15125      char *overrideCastling;
15126 {
15127     int i, j, fromX, fromY, toX, toY;
15128     int whiteToPlay;
15129     char buf[128];
15130     char *p, *q;
15131     int emptycount;
15132     ChessSquare piece;
15133
15134     whiteToPlay = (gameMode == EditPosition) ?
15135       !blackPlaysFirst : (move % 2 == 0);
15136     p = buf;
15137
15138     /* Piece placement data */
15139     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15140         emptycount = 0;
15141         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15142             if (boards[move][i][j] == EmptySquare) {
15143                 emptycount++;
15144             } else { ChessSquare piece = boards[move][i][j];
15145                 if (emptycount > 0) {
15146                     if(emptycount<10) /* [HGM] can be >= 10 */
15147                         *p++ = '0' + emptycount;
15148                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15149                     emptycount = 0;
15150                 }
15151                 if(PieceToChar(piece) == '+') {
15152                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15153                     *p++ = '+';
15154                     piece = (ChessSquare)(DEMOTED piece);
15155                 }
15156                 *p++ = PieceToChar(piece);
15157                 if(p[-1] == '~') {
15158                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15159                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15160                     *p++ = '~';
15161                 }
15162             }
15163         }
15164         if (emptycount > 0) {
15165             if(emptycount<10) /* [HGM] can be >= 10 */
15166                 *p++ = '0' + emptycount;
15167             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15168             emptycount = 0;
15169         }
15170         *p++ = '/';
15171     }
15172     *(p - 1) = ' ';
15173
15174     /* [HGM] print Crazyhouse or Shogi holdings */
15175     if( gameInfo.holdingsWidth ) {
15176         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15177         q = p;
15178         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15179             piece = boards[move][i][BOARD_WIDTH-1];
15180             if( piece != EmptySquare )
15181               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15182                   *p++ = PieceToChar(piece);
15183         }
15184         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15185             piece = boards[move][BOARD_HEIGHT-i-1][0];
15186             if( piece != EmptySquare )
15187               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15188                   *p++ = PieceToChar(piece);
15189         }
15190
15191         if( q == p ) *p++ = '-';
15192         *p++ = ']';
15193         *p++ = ' ';
15194     }
15195
15196     /* Active color */
15197     *p++ = whiteToPlay ? 'w' : 'b';
15198     *p++ = ' ';
15199
15200   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15201     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15202   } else {
15203   if(nrCastlingRights) {
15204      q = p;
15205      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15206        /* [HGM] write directly from rights */
15207            if(boards[move][CASTLING][2] != NoRights &&
15208               boards[move][CASTLING][0] != NoRights   )
15209                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15210            if(boards[move][CASTLING][2] != NoRights &&
15211               boards[move][CASTLING][1] != NoRights   )
15212                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15213            if(boards[move][CASTLING][5] != NoRights &&
15214               boards[move][CASTLING][3] != NoRights   )
15215                 *p++ = boards[move][CASTLING][3] + AAA;
15216            if(boards[move][CASTLING][5] != NoRights &&
15217               boards[move][CASTLING][4] != NoRights   )
15218                 *p++ = boards[move][CASTLING][4] + AAA;
15219      } else {
15220
15221         /* [HGM] write true castling rights */
15222         if( nrCastlingRights == 6 ) {
15223             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15224                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15225             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15226                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15227             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15228                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15229             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15230                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15231         }
15232      }
15233      if (q == p) *p++ = '-'; /* No castling rights */
15234      *p++ = ' ';
15235   }
15236
15237   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15238      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15239     /* En passant target square */
15240     if (move > backwardMostMove) {
15241         fromX = moveList[move - 1][0] - AAA;
15242         fromY = moveList[move - 1][1] - ONE;
15243         toX = moveList[move - 1][2] - AAA;
15244         toY = moveList[move - 1][3] - ONE;
15245         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15246             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15247             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15248             fromX == toX) {
15249             /* 2-square pawn move just happened */
15250             *p++ = toX + AAA;
15251             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15252         } else {
15253             *p++ = '-';
15254         }
15255     } else if(move == backwardMostMove) {
15256         // [HGM] perhaps we should always do it like this, and forget the above?
15257         if((signed char)boards[move][EP_STATUS] >= 0) {
15258             *p++ = boards[move][EP_STATUS] + AAA;
15259             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15260         } else {
15261             *p++ = '-';
15262         }
15263     } else {
15264         *p++ = '-';
15265     }
15266     *p++ = ' ';
15267   }
15268   }
15269
15270     /* [HGM] find reversible plies */
15271     {   int i = 0, j=move;
15272
15273         if (appData.debugMode) { int k;
15274             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15275             for(k=backwardMostMove; k<=forwardMostMove; k++)
15276                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15277
15278         }
15279
15280         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15281         if( j == backwardMostMove ) i += initialRulePlies;
15282         sprintf(p, "%d ", i);
15283         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15284     }
15285     /* Fullmove number */
15286     sprintf(p, "%d", (move / 2) + 1);
15287
15288     return StrSave(buf);
15289 }
15290
15291 Boolean
15292 ParseFEN(board, blackPlaysFirst, fen)
15293     Board board;
15294      int *blackPlaysFirst;
15295      char *fen;
15296 {
15297     int i, j;
15298     char *p, c;
15299     int emptycount;
15300     ChessSquare piece;
15301
15302     p = fen;
15303
15304     /* [HGM] by default clear Crazyhouse holdings, if present */
15305     if(gameInfo.holdingsWidth) {
15306        for(i=0; i<BOARD_HEIGHT; i++) {
15307            board[i][0]             = EmptySquare; /* black holdings */
15308            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15309            board[i][1]             = (ChessSquare) 0; /* black counts */
15310            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15311        }
15312     }
15313
15314     /* Piece placement data */
15315     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15316         j = 0;
15317         for (;;) {
15318             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15319                 if (*p == '/') p++;
15320                 emptycount = gameInfo.boardWidth - j;
15321                 while (emptycount--)
15322                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15323                 break;
15324 #if(BOARD_FILES >= 10)
15325             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15326                 p++; emptycount=10;
15327                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15328                 while (emptycount--)
15329                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15330 #endif
15331             } else if (isdigit(*p)) {
15332                 emptycount = *p++ - '0';
15333                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15334                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15335                 while (emptycount--)
15336                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15337             } else if (*p == '+' || isalpha(*p)) {
15338                 if (j >= gameInfo.boardWidth) return FALSE;
15339                 if(*p=='+') {
15340                     piece = CharToPiece(*++p);
15341                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15342                     piece = (ChessSquare) (PROMOTED piece ); p++;
15343                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15344                 } else piece = CharToPiece(*p++);
15345
15346                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15347                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15348                     piece = (ChessSquare) (PROMOTED piece);
15349                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15350                     p++;
15351                 }
15352                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15353             } else {
15354                 return FALSE;
15355             }
15356         }
15357     }
15358     while (*p == '/' || *p == ' ') p++;
15359
15360     /* [HGM] look for Crazyhouse holdings here */
15361     while(*p==' ') p++;
15362     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15363         if(*p == '[') p++;
15364         if(*p == '-' ) p++; /* empty holdings */ else {
15365             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15366             /* if we would allow FEN reading to set board size, we would   */
15367             /* have to add holdings and shift the board read so far here   */
15368             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15369                 p++;
15370                 if((int) piece >= (int) BlackPawn ) {
15371                     i = (int)piece - (int)BlackPawn;
15372                     i = PieceToNumber((ChessSquare)i);
15373                     if( i >= gameInfo.holdingsSize ) return FALSE;
15374                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15375                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15376                 } else {
15377                     i = (int)piece - (int)WhitePawn;
15378                     i = PieceToNumber((ChessSquare)i);
15379                     if( i >= gameInfo.holdingsSize ) return FALSE;
15380                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15381                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15382                 }
15383             }
15384         }
15385         if(*p == ']') p++;
15386     }
15387
15388     while(*p == ' ') p++;
15389
15390     /* Active color */
15391     c = *p++;
15392     if(appData.colorNickNames) {
15393       if( c == appData.colorNickNames[0] ) c = 'w'; else
15394       if( c == appData.colorNickNames[1] ) c = 'b';
15395     }
15396     switch (c) {
15397       case 'w':
15398         *blackPlaysFirst = FALSE;
15399         break;
15400       case 'b':
15401         *blackPlaysFirst = TRUE;
15402         break;
15403       default:
15404         return FALSE;
15405     }
15406
15407     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15408     /* return the extra info in global variiables             */
15409
15410     /* set defaults in case FEN is incomplete */
15411     board[EP_STATUS] = EP_UNKNOWN;
15412     for(i=0; i<nrCastlingRights; i++ ) {
15413         board[CASTLING][i] =
15414             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15415     }   /* assume possible unless obviously impossible */
15416     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15417     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15418     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15419                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15420     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15421     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15422     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15423                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15424     FENrulePlies = 0;
15425
15426     while(*p==' ') p++;
15427     if(nrCastlingRights) {
15428       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15429           /* castling indicator present, so default becomes no castlings */
15430           for(i=0; i<nrCastlingRights; i++ ) {
15431                  board[CASTLING][i] = NoRights;
15432           }
15433       }
15434       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15435              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15436              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15437              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15438         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15439
15440         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15441             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15442             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15443         }
15444         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15445             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15446         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15447                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15448         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15449                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15450         switch(c) {
15451           case'K':
15452               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15453               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15454               board[CASTLING][2] = whiteKingFile;
15455               break;
15456           case'Q':
15457               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15458               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15459               board[CASTLING][2] = whiteKingFile;
15460               break;
15461           case'k':
15462               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15463               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15464               board[CASTLING][5] = blackKingFile;
15465               break;
15466           case'q':
15467               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15468               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15469               board[CASTLING][5] = blackKingFile;
15470           case '-':
15471               break;
15472           default: /* FRC castlings */
15473               if(c >= 'a') { /* black rights */
15474                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15475                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15476                   if(i == BOARD_RGHT) break;
15477                   board[CASTLING][5] = i;
15478                   c -= AAA;
15479                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15480                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15481                   if(c > i)
15482                       board[CASTLING][3] = c;
15483                   else
15484                       board[CASTLING][4] = c;
15485               } else { /* white rights */
15486                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15487                     if(board[0][i] == WhiteKing) break;
15488                   if(i == BOARD_RGHT) break;
15489                   board[CASTLING][2] = i;
15490                   c -= AAA - 'a' + 'A';
15491                   if(board[0][c] >= WhiteKing) break;
15492                   if(c > i)
15493                       board[CASTLING][0] = c;
15494                   else
15495                       board[CASTLING][1] = c;
15496               }
15497         }
15498       }
15499       for(i=0; i<nrCastlingRights; i++)
15500         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15501     if (appData.debugMode) {
15502         fprintf(debugFP, "FEN castling rights:");
15503         for(i=0; i<nrCastlingRights; i++)
15504         fprintf(debugFP, " %d", board[CASTLING][i]);
15505         fprintf(debugFP, "\n");
15506     }
15507
15508       while(*p==' ') p++;
15509     }
15510
15511     /* read e.p. field in games that know e.p. capture */
15512     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15513        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15514       if(*p=='-') {
15515         p++; board[EP_STATUS] = EP_NONE;
15516       } else {
15517          char c = *p++ - AAA;
15518
15519          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15520          if(*p >= '0' && *p <='9') p++;
15521          board[EP_STATUS] = c;
15522       }
15523     }
15524
15525
15526     if(sscanf(p, "%d", &i) == 1) {
15527         FENrulePlies = i; /* 50-move ply counter */
15528         /* (The move number is still ignored)    */
15529     }
15530
15531     return TRUE;
15532 }
15533
15534 void
15535 EditPositionPasteFEN(char *fen)
15536 {
15537   if (fen != NULL) {
15538     Board initial_position;
15539
15540     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15541       DisplayError(_("Bad FEN position in clipboard"), 0);
15542       return ;
15543     } else {
15544       int savedBlackPlaysFirst = blackPlaysFirst;
15545       EditPositionEvent();
15546       blackPlaysFirst = savedBlackPlaysFirst;
15547       CopyBoard(boards[0], initial_position);
15548       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15549       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15550       DisplayBothClocks();
15551       DrawPosition(FALSE, boards[currentMove]);
15552     }
15553   }
15554 }
15555
15556 static char cseq[12] = "\\   ";
15557
15558 Boolean set_cont_sequence(char *new_seq)
15559 {
15560     int len;
15561     Boolean ret;
15562
15563     // handle bad attempts to set the sequence
15564         if (!new_seq)
15565                 return 0; // acceptable error - no debug
15566
15567     len = strlen(new_seq);
15568     ret = (len > 0) && (len < sizeof(cseq));
15569     if (ret)
15570       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15571     else if (appData.debugMode)
15572       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15573     return ret;
15574 }
15575
15576 /*
15577     reformat a source message so words don't cross the width boundary.  internal
15578     newlines are not removed.  returns the wrapped size (no null character unless
15579     included in source message).  If dest is NULL, only calculate the size required
15580     for the dest buffer.  lp argument indicats line position upon entry, and it's
15581     passed back upon exit.
15582 */
15583 int wrap(char *dest, char *src, int count, int width, int *lp)
15584 {
15585     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15586
15587     cseq_len = strlen(cseq);
15588     old_line = line = *lp;
15589     ansi = len = clen = 0;
15590
15591     for (i=0; i < count; i++)
15592     {
15593         if (src[i] == '\033')
15594             ansi = 1;
15595
15596         // if we hit the width, back up
15597         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15598         {
15599             // store i & len in case the word is too long
15600             old_i = i, old_len = len;
15601
15602             // find the end of the last word
15603             while (i && src[i] != ' ' && src[i] != '\n')
15604             {
15605                 i--;
15606                 len--;
15607             }
15608
15609             // word too long?  restore i & len before splitting it
15610             if ((old_i-i+clen) >= width)
15611             {
15612                 i = old_i;
15613                 len = old_len;
15614             }
15615
15616             // extra space?
15617             if (i && src[i-1] == ' ')
15618                 len--;
15619
15620             if (src[i] != ' ' && src[i] != '\n')
15621             {
15622                 i--;
15623                 if (len)
15624                     len--;
15625             }
15626
15627             // now append the newline and continuation sequence
15628             if (dest)
15629                 dest[len] = '\n';
15630             len++;
15631             if (dest)
15632                 strncpy(dest+len, cseq, cseq_len);
15633             len += cseq_len;
15634             line = cseq_len;
15635             clen = cseq_len;
15636             continue;
15637         }
15638
15639         if (dest)
15640             dest[len] = src[i];
15641         len++;
15642         if (!ansi)
15643             line++;
15644         if (src[i] == '\n')
15645             line = 0;
15646         if (src[i] == 'm')
15647             ansi = 0;
15648     }
15649     if (dest && appData.debugMode)
15650     {
15651         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15652             count, width, line, len, *lp);
15653         show_bytes(debugFP, src, count);
15654         fprintf(debugFP, "\ndest: ");
15655         show_bytes(debugFP, dest, len);
15656         fprintf(debugFP, "\n");
15657     }
15658     *lp = dest ? line : old_line;
15659
15660     return len;
15661 }
15662
15663 // [HGM] vari: routines for shelving variations
15664
15665 void
15666 PushTail(int firstMove, int lastMove)
15667 {
15668         int i, j, nrMoves = lastMove - firstMove;
15669
15670         if(appData.icsActive) { // only in local mode
15671                 forwardMostMove = currentMove; // mimic old ICS behavior
15672                 return;
15673         }
15674         if(storedGames >= MAX_VARIATIONS-1) return;
15675
15676         // push current tail of game on stack
15677         savedResult[storedGames] = gameInfo.result;
15678         savedDetails[storedGames] = gameInfo.resultDetails;
15679         gameInfo.resultDetails = NULL;
15680         savedFirst[storedGames] = firstMove;
15681         savedLast [storedGames] = lastMove;
15682         savedFramePtr[storedGames] = framePtr;
15683         framePtr -= nrMoves; // reserve space for the boards
15684         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15685             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15686             for(j=0; j<MOVE_LEN; j++)
15687                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15688             for(j=0; j<2*MOVE_LEN; j++)
15689                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15690             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15691             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15692             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15693             pvInfoList[firstMove+i-1].depth = 0;
15694             commentList[framePtr+i] = commentList[firstMove+i];
15695             commentList[firstMove+i] = NULL;
15696         }
15697
15698         storedGames++;
15699         forwardMostMove = firstMove; // truncate game so we can start variation
15700         if(storedGames == 1) GreyRevert(FALSE);
15701 }
15702
15703 Boolean
15704 PopTail(Boolean annotate)
15705 {
15706         int i, j, nrMoves;
15707         char buf[8000], moveBuf[20];
15708
15709         if(appData.icsActive) return FALSE; // only in local mode
15710         if(!storedGames) return FALSE; // sanity
15711         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15712
15713         storedGames--;
15714         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15715         nrMoves = savedLast[storedGames] - currentMove;
15716         if(annotate) {
15717                 int cnt = 10;
15718                 if(!WhiteOnMove(currentMove))
15719                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15720                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15721                 for(i=currentMove; i<forwardMostMove; i++) {
15722                         if(WhiteOnMove(i))
15723                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15724                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15725                         strcat(buf, moveBuf);
15726                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15727                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15728                 }
15729                 strcat(buf, ")");
15730         }
15731         for(i=1; i<=nrMoves; i++) { // copy last variation back
15732             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15733             for(j=0; j<MOVE_LEN; j++)
15734                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15735             for(j=0; j<2*MOVE_LEN; j++)
15736                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15737             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15738             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15739             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15740             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15741             commentList[currentMove+i] = commentList[framePtr+i];
15742             commentList[framePtr+i] = NULL;
15743         }
15744         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15745         framePtr = savedFramePtr[storedGames];
15746         gameInfo.result = savedResult[storedGames];
15747         if(gameInfo.resultDetails != NULL) {
15748             free(gameInfo.resultDetails);
15749       }
15750         gameInfo.resultDetails = savedDetails[storedGames];
15751         forwardMostMove = currentMove + nrMoves;
15752         if(storedGames == 0) GreyRevert(TRUE);
15753         return TRUE;
15754 }
15755
15756 void
15757 CleanupTail()
15758 {       // remove all shelved variations
15759         int i;
15760         for(i=0; i<storedGames; i++) {
15761             if(savedDetails[i])
15762                 free(savedDetails[i]);
15763             savedDetails[i] = NULL;
15764         }
15765         for(i=framePtr; i<MAX_MOVES; i++) {
15766                 if(commentList[i]) free(commentList[i]);
15767                 commentList[i] = NULL;
15768         }
15769         framePtr = MAX_MOVES-1;
15770         storedGames = 0;
15771 }
15772
15773 void
15774 LoadVariation(int index, char *text)
15775 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15776         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15777         int level = 0, move;
15778
15779         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15780         // first find outermost bracketing variation
15781         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15782             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15783                 if(*p == '{') wait = '}'; else
15784                 if(*p == '[') wait = ']'; else
15785                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15786                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15787             }
15788             if(*p == wait) wait = NULLCHAR; // closing ]} found
15789             p++;
15790         }
15791         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15792         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15793         end[1] = NULLCHAR; // clip off comment beyond variation
15794         ToNrEvent(currentMove-1);
15795         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15796         // kludge: use ParsePV() to append variation to game
15797         move = currentMove;
15798         ParsePV(start, TRUE);
15799         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15800         ClearPremoveHighlights();
15801         CommentPopDown();
15802         ToNrEvent(currentMove+1);
15803 }
15804