f6e75c49f0e69aa380491ecdccc2ddb9739b3116
[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 ChessSquare pieceSweep = EmptySquare;
274 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
275 int promoDefaultAltered;
276
277 /* States for ics_getting_history */
278 #define H_FALSE 0
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
284
285 /* whosays values for GameEnds */
286 #define GE_ICS 0
287 #define GE_ENGINE 1
288 #define GE_PLAYER 2
289 #define GE_FILE 3
290 #define GE_XBOARD 4
291 #define GE_ENGINE1 5
292 #define GE_ENGINE2 6
293
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
296
297 /* Different types of move when calling RegisterMove */
298 #define CMAIL_MOVE   0
299 #define CMAIL_RESIGN 1
300 #define CMAIL_DRAW   2
301 #define CMAIL_ACCEPT 3
302
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
307
308 /* Telnet protocol constants */
309 #define TN_WILL 0373
310 #define TN_WONT 0374
311 #define TN_DO   0375
312 #define TN_DONT 0376
313 #define TN_IAC  0377
314 #define TN_ECHO 0001
315 #define TN_SGA  0003
316 #define TN_PORT 23
317
318 char*
319 safeStrCpy( char *dst, const char *src, size_t count )
320 { // [HGM] made safe
321   int i;
322   assert( dst != NULL );
323   assert( src != NULL );
324   assert( count > 0 );
325
326   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327   if(  i == count && dst[count-1] != NULLCHAR)
328     {
329       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330       if(appData.debugMode)
331       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
332     }
333
334   return dst;
335 }
336
337 /* Some compiler can't cast u64 to double
338  * This function do the job for us:
339
340  * We use the highest bit for cast, this only
341  * works if the highest bit is not
342  * in use (This should not happen)
343  *
344  * We used this for all compiler
345  */
346 double
347 u64ToDouble(u64 value)
348 {
349   double r;
350   u64 tmp = value & u64Const(0x7fffffffffffffff);
351   r = (double)(s64)tmp;
352   if (value & u64Const(0x8000000000000000))
353        r +=  9.2233720368547758080e18; /* 2^63 */
354  return r;
355 }
356
357 /* Fake up flags for now, as we aren't keeping track of castling
358    availability yet. [HGM] Change of logic: the flag now only
359    indicates the type of castlings allowed by the rule of the game.
360    The actual rights themselves are maintained in the array
361    castlingRights, as part of the game history, and are not probed
362    by this function.
363  */
364 int
365 PosFlags(index)
366 {
367   int flags = F_ALL_CASTLE_OK;
368   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369   switch (gameInfo.variant) {
370   case VariantSuicide:
371     flags &= ~F_ALL_CASTLE_OK;
372   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373     flags |= F_IGNORE_CHECK;
374   case VariantLosers:
375     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
376     break;
377   case VariantAtomic:
378     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
379     break;
380   case VariantKriegspiel:
381     flags |= F_KRIEGSPIEL_CAPTURE;
382     break;
383   case VariantCapaRandom:
384   case VariantFischeRandom:
385     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386   case VariantNoCastle:
387   case VariantShatranj:
388   case VariantCourier:
389   case VariantMakruk:
390     flags &= ~F_ALL_CASTLE_OK;
391     break;
392   default:
393     break;
394   }
395   return flags;
396 }
397
398 FILE *gameFileFP, *debugFP;
399
400 /*
401     [AS] Note: sometimes, the sscanf() function is used to parse the input
402     into a fixed-size buffer. Because of this, we must be prepared to
403     receive strings as long as the size of the input buffer, which is currently
404     set to 4K for Windows and 8K for the rest.
405     So, we must either allocate sufficiently large buffers here, or
406     reduce the size of the input buffer in the input reading part.
407 */
408
409 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
410 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
411 char thinkOutput1[MSG_SIZ*10];
412
413 ChessProgramState first, second;
414
415 /* premove variables */
416 int premoveToX = 0;
417 int premoveToY = 0;
418 int premoveFromX = 0;
419 int premoveFromY = 0;
420 int premovePromoChar = 0;
421 int gotPremove = 0;
422 Boolean alarmSounded;
423 /* end premove variables */
424
425 char *ics_prefix = "$";
426 int ics_type = ICS_GENERIC;
427
428 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
429 int pauseExamForwardMostMove = 0;
430 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
431 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
432 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
433 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
434 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
435 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
436 int whiteFlag = FALSE, blackFlag = FALSE;
437 int userOfferedDraw = FALSE;
438 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
439 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
440 int cmailMoveType[CMAIL_MAX_GAMES];
441 long ics_clock_paused = 0;
442 ProcRef icsPR = NoProc, cmailPR = NoProc;
443 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
444 GameMode gameMode = BeginningOfGame;
445 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
446 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
447 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
448 int hiddenThinkOutputState = 0; /* [AS] */
449 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
450 int adjudicateLossPlies = 6;
451 char white_holding[64], black_holding[64];
452 TimeMark lastNodeCountTime;
453 long lastNodeCount=0;
454 int shiftKey; // [HGM] set by mouse handler
455
456 int have_sent_ICS_logon = 0;
457 int movesPerSession;
458 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
459 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0;
464 TimeMark programStartTime;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 int shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void CleanupTail P((void));
503
504 ChessSquare  FIDEArray[2][BOARD_FILES] = {
505     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
506         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
508         BlackKing, BlackBishop, BlackKnight, BlackRook }
509 };
510
511 ChessSquare twoKingsArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515         BlackKing, BlackKing, BlackKnight, BlackRook }
516 };
517
518 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
520         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
521     { BlackRook, BlackMan, BlackBishop, BlackQueen,
522         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
523 };
524
525 ChessSquare SpartanArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
527         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
528     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
529         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
530 };
531
532 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
533     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
536         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
537 };
538
539 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
540     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
541         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
543         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
544 };
545
546 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
547     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
548         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
549     { BlackRook, BlackKnight, BlackMan, BlackFerz,
550         BlackKing, BlackMan, BlackKnight, BlackRook }
551 };
552
553
554 #if (BOARD_FILES>=10)
555 ChessSquare ShogiArray[2][BOARD_FILES] = {
556     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
557         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
558     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
559         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
560 };
561
562 ChessSquare XiangqiArray[2][BOARD_FILES] = {
563     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
564         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
566         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
567 };
568
569 ChessSquare CapablancaArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
571         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
573         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
574 };
575
576 ChessSquare GreatArray[2][BOARD_FILES] = {
577     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
578         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
579     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
580         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
581 };
582
583 ChessSquare JanusArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
585         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
586     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
587         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
588 };
589
590 #ifdef GOTHIC
591 ChessSquare GothicArray[2][BOARD_FILES] = {
592     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
593         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
595         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
596 };
597 #else // !GOTHIC
598 #define GothicArray CapablancaArray
599 #endif // !GOTHIC
600
601 #ifdef FALCON
602 ChessSquare FalconArray[2][BOARD_FILES] = {
603     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
604         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
605     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
606         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
607 };
608 #else // !FALCON
609 #define FalconArray CapablancaArray
610 #endif // !FALCON
611
612 #else // !(BOARD_FILES>=10)
613 #define XiangqiPosition FIDEArray
614 #define CapablancaArray FIDEArray
615 #define GothicArray FIDEArray
616 #define GreatArray FIDEArray
617 #endif // !(BOARD_FILES>=10)
618
619 #if (BOARD_FILES>=12)
620 ChessSquare CourierArray[2][BOARD_FILES] = {
621     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
622         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
623     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
624         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
625 };
626 #else // !(BOARD_FILES>=12)
627 #define CourierArray CapablancaArray
628 #endif // !(BOARD_FILES>=12)
629
630
631 Board initialPosition;
632
633
634 /* Convert str to a rating. Checks for special cases of "----",
635
636    "++++", etc. Also strips ()'s */
637 int
638 string_to_rating(str)
639   char *str;
640 {
641   while(*str && !isdigit(*str)) ++str;
642   if (!*str)
643     return 0;   /* One of the special "no rating" cases */
644   else
645     return atoi(str);
646 }
647
648 void
649 ClearProgramStats()
650 {
651     /* Init programStats */
652     programStats.movelist[0] = 0;
653     programStats.depth = 0;
654     programStats.nr_moves = 0;
655     programStats.moves_left = 0;
656     programStats.nodes = 0;
657     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
658     programStats.score = 0;
659     programStats.got_only_move = 0;
660     programStats.got_fail = 0;
661     programStats.line_is_book = 0;
662 }
663
664 void
665 CommonEngineInit()
666 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
667     if (appData.firstPlaysBlack) {
668         first.twoMachinesColor = "black\n";
669         second.twoMachinesColor = "white\n";
670     } else {
671         first.twoMachinesColor = "white\n";
672         second.twoMachinesColor = "black\n";
673     }
674
675     first.other = &second;
676     second.other = &first;
677
678     { float norm = 1;
679         if(appData.timeOddsMode) {
680             norm = appData.timeOdds[0];
681             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
682         }
683         first.timeOdds  = appData.timeOdds[0]/norm;
684         second.timeOdds = appData.timeOdds[1]/norm;
685     }
686
687     if(programVersion) free(programVersion);
688     if (appData.noChessProgram) {
689         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
690         sprintf(programVersion, "%s", PACKAGE_STRING);
691     } else {
692       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
693       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
694       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
695     }
696 }
697
698 void
699 UnloadEngine(ChessProgramState *cps)
700 {
701         /* Kill off first chess program */
702         if (cps->isr != NULL)
703           RemoveInputSource(cps->isr);
704         cps->isr = NULL;
705
706         if (cps->pr != NoProc) {
707             ExitAnalyzeMode();
708             DoSleep( appData.delayBeforeQuit );
709             SendToProgram("quit\n", cps);
710             DoSleep( appData.delayAfterQuit );
711             DestroyChildProcess(cps->pr, cps->useSigterm);
712         }
713         cps->pr = NoProc;
714 }
715
716 void
717 ClearOptions(ChessProgramState *cps)
718 {
719     int i;
720     cps->nrOptions = cps->comboCnt = 0;
721     for(i=0; i<MAX_OPTIONS; i++) {
722         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
723         cps->option[i].textValue = 0;
724     }
725 }
726
727 char *engineNames[] = {
728 "first",
729 "second"
730 };
731
732 InitEngine(ChessProgramState *cps, int n)
733 {   // [HGM] all engine initialiation put in a function that does one engine
734
735     ClearOptions(cps);
736
737     cps->which = engineNames[n];
738     cps->maybeThinking = FALSE;
739     cps->pr = NoProc;
740     cps->isr = NULL;
741     cps->sendTime = 2;
742     cps->sendDrawOffers = 1;
743
744     cps->program = appData.chessProgram[n];
745     cps->host = appData.host[n];
746     cps->dir = appData.directory[n];
747     cps->initString = appData.engInitString[n];
748     cps->computerString = appData.computerString[n];
749     cps->useSigint  = TRUE;
750     cps->useSigterm = TRUE;
751     cps->reuse = appData.reuse[n];
752     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
753     cps->useSetboard = FALSE;
754     cps->useSAN = FALSE;
755     cps->usePing = FALSE;
756     cps->lastPing = 0;
757     cps->lastPong = 0;
758     cps->usePlayother = FALSE;
759     cps->useColors = TRUE;
760     cps->useUsermove = FALSE;
761     cps->sendICS = FALSE;
762     cps->sendName = appData.icsActive;
763     cps->sdKludge = FALSE;
764     cps->stKludge = FALSE;
765     TidyProgramName(cps->program, cps->host, cps->tidy);
766     cps->matchWins = 0;
767     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
768     cps->analysisSupport = 2; /* detect */
769     cps->analyzing = FALSE;
770     cps->initDone = FALSE;
771
772     /* New features added by Tord: */
773     cps->useFEN960 = FALSE;
774     cps->useOOCastle = TRUE;
775     /* End of new features added by Tord. */
776     cps->fenOverride  = appData.fenOverride[n];
777
778     /* [HGM] time odds: set factor for each machine */
779     cps->timeOdds  = appData.timeOdds[n];
780
781     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
782     cps->accumulateTC = appData.accumulateTC[n];
783     cps->maxNrOfSessions = 1;
784
785     /* [HGM] debug */
786     cps->debug = FALSE;
787     cps->supportsNPS = UNKNOWN;
788
789     /* [HGM] options */
790     cps->optionSettings  = appData.engOptions[n];
791
792     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
793     cps->isUCI = appData.isUCI[n]; /* [AS] */
794     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
795
796     if (appData.protocolVersion[n] > PROTOVER
797         || appData.protocolVersion[n] < 1)
798       {
799         char buf[MSG_SIZ];
800         int len;
801
802         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
803                        appData.protocolVersion[n]);
804         if( (len > MSG_SIZ) && appData.debugMode )
805           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
806
807         DisplayFatalError(buf, 0, 2);
808       }
809     else
810       {
811         cps->protocolVersion = appData.protocolVersion[n];
812       }
813
814     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
815 }
816
817 ChessProgramState *savCps;
818
819 void
820 LoadEngine()
821 {
822     int i;
823     if(WaitForEngine(savCps, LoadEngine)) return;
824     CommonEngineInit(); // recalculate time odds
825     if(gameInfo.variant != StringToVariant(appData.variant)) {
826         // we changed variant when loading the engine; this forces us to reset
827         Reset(TRUE, savCps != &first);
828         EditGameEvent(); // for consistency with other path, as Reset changes mode
829     }
830     InitChessProgram(savCps, FALSE);
831     SendToProgram("force\n", savCps);
832     DisplayMessage("", "");
833     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
834     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
835     ThawUI();
836     SetGNUMode();
837 }
838
839 void
840 ReplaceEngine(ChessProgramState *cps, int n)
841 {
842     EditGameEvent();
843     UnloadEngine(cps);
844     appData.noChessProgram = False;
845     appData.clockMode = True;
846     InitEngine(cps, n);
847     if(n) return; // only startup first engine immediately; second can wait
848     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
849     LoadEngine();
850 }
851
852 void
853 InitBackEnd1()
854 {
855     int matched, min, sec;
856
857     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
858     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
859
860     GetTimeMark(&programStartTime);
861     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
862
863     ClearProgramStats();
864     programStats.ok_to_send = 1;
865     programStats.seen_stat = 0;
866
867     /*
868      * Initialize game list
869      */
870     ListNew(&gameList);
871
872
873     /*
874      * Internet chess server status
875      */
876     if (appData.icsActive) {
877         appData.matchMode = FALSE;
878         appData.matchGames = 0;
879 #if ZIPPY
880         appData.noChessProgram = !appData.zippyPlay;
881 #else
882         appData.zippyPlay = FALSE;
883         appData.zippyTalk = FALSE;
884         appData.noChessProgram = TRUE;
885 #endif
886         if (*appData.icsHelper != NULLCHAR) {
887             appData.useTelnet = TRUE;
888             appData.telnetProgram = appData.icsHelper;
889         }
890     } else {
891         appData.zippyTalk = appData.zippyPlay = FALSE;
892     }
893
894     /* [AS] Initialize pv info list [HGM] and game state */
895     {
896         int i, j;
897
898         for( i=0; i<=framePtr; i++ ) {
899             pvInfoList[i].depth = -1;
900             boards[i][EP_STATUS] = EP_NONE;
901             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
902         }
903     }
904
905     /*
906      * Parse timeControl resource
907      */
908     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
909                           appData.movesPerSession)) {
910         char buf[MSG_SIZ];
911         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
912         DisplayFatalError(buf, 0, 2);
913     }
914
915     /*
916      * Parse searchTime resource
917      */
918     if (*appData.searchTime != NULLCHAR) {
919         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
920         if (matched == 1) {
921             searchTime = min * 60;
922         } else if (matched == 2) {
923             searchTime = min * 60 + sec;
924         } else {
925             char buf[MSG_SIZ];
926             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
927             DisplayFatalError(buf, 0, 2);
928         }
929     }
930
931     /* [AS] Adjudication threshold */
932     adjudicateLossThreshold = appData.adjudicateLossThreshold;
933
934     InitEngine(&first, 0);
935     InitEngine(&second, 1);
936     CommonEngineInit();
937
938     if (appData.icsActive) {
939         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
940     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
941         appData.clockMode = FALSE;
942         first.sendTime = second.sendTime = 0;
943     }
944
945 #if ZIPPY
946     /* Override some settings from environment variables, for backward
947        compatibility.  Unfortunately it's not feasible to have the env
948        vars just set defaults, at least in xboard.  Ugh.
949     */
950     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
951       ZippyInit();
952     }
953 #endif
954
955     if (!appData.icsActive) {
956       char buf[MSG_SIZ];
957       int len;
958
959       /* Check for variants that are supported only in ICS mode,
960          or not at all.  Some that are accepted here nevertheless
961          have bugs; see comments below.
962       */
963       VariantClass variant = StringToVariant(appData.variant);
964       switch (variant) {
965       case VariantBughouse:     /* need four players and two boards */
966       case VariantKriegspiel:   /* need to hide pieces and move details */
967         /* case VariantFischeRandom: (Fabien: moved below) */
968         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
969         if( (len > MSG_SIZ) && appData.debugMode )
970           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
971
972         DisplayFatalError(buf, 0, 2);
973         return;
974
975       case VariantUnknown:
976       case VariantLoadable:
977       case Variant29:
978       case Variant30:
979       case Variant31:
980       case Variant32:
981       case Variant33:
982       case Variant34:
983       case Variant35:
984       case Variant36:
985       default:
986         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
987         if( (len > MSG_SIZ) && appData.debugMode )
988           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
989
990         DisplayFatalError(buf, 0, 2);
991         return;
992
993       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
994       case VariantFairy:      /* [HGM] TestLegality definitely off! */
995       case VariantGothic:     /* [HGM] should work */
996       case VariantCapablanca: /* [HGM] should work */
997       case VariantCourier:    /* [HGM] initial forced moves not implemented */
998       case VariantShogi:      /* [HGM] could still mate with pawn drop */
999       case VariantKnightmate: /* [HGM] should work */
1000       case VariantCylinder:   /* [HGM] untested */
1001       case VariantFalcon:     /* [HGM] untested */
1002       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1003                                  offboard interposition not understood */
1004       case VariantNormal:     /* definitely works! */
1005       case VariantWildCastle: /* pieces not automatically shuffled */
1006       case VariantNoCastle:   /* pieces not automatically shuffled */
1007       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1008       case VariantLosers:     /* should work except for win condition,
1009                                  and doesn't know captures are mandatory */
1010       case VariantSuicide:    /* should work except for win condition,
1011                                  and doesn't know captures are mandatory */
1012       case VariantGiveaway:   /* should work except for win condition,
1013                                  and doesn't know captures are mandatory */
1014       case VariantTwoKings:   /* should work */
1015       case VariantAtomic:     /* should work except for win condition */
1016       case Variant3Check:     /* should work except for win condition */
1017       case VariantShatranj:   /* should work except for all win conditions */
1018       case VariantMakruk:     /* should work except for daw countdown */
1019       case VariantBerolina:   /* might work if TestLegality is off */
1020       case VariantCapaRandom: /* should work */
1021       case VariantJanus:      /* should work */
1022       case VariantSuper:      /* experimental */
1023       case VariantGreat:      /* experimental, requires legality testing to be off */
1024       case VariantSChess:     /* S-Chess, should work */
1025       case VariantSpartan:    /* should work */
1026         break;
1027       }
1028     }
1029
1030 }
1031
1032 int NextIntegerFromString( char ** str, long * value )
1033 {
1034     int result = -1;
1035     char * s = *str;
1036
1037     while( *s == ' ' || *s == '\t' ) {
1038         s++;
1039     }
1040
1041     *value = 0;
1042
1043     if( *s >= '0' && *s <= '9' ) {
1044         while( *s >= '0' && *s <= '9' ) {
1045             *value = *value * 10 + (*s - '0');
1046             s++;
1047         }
1048
1049         result = 0;
1050     }
1051
1052     *str = s;
1053
1054     return result;
1055 }
1056
1057 int NextTimeControlFromString( char ** str, long * value )
1058 {
1059     long temp;
1060     int result = NextIntegerFromString( str, &temp );
1061
1062     if( result == 0 ) {
1063         *value = temp * 60; /* Minutes */
1064         if( **str == ':' ) {
1065             (*str)++;
1066             result = NextIntegerFromString( str, &temp );
1067             *value += temp; /* Seconds */
1068         }
1069     }
1070
1071     return result;
1072 }
1073
1074 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1075 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1076     int result = -1, type = 0; long temp, temp2;
1077
1078     if(**str != ':') return -1; // old params remain in force!
1079     (*str)++;
1080     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1081     if( NextIntegerFromString( str, &temp ) ) return -1;
1082     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1083
1084     if(**str != '/') {
1085         /* time only: incremental or sudden-death time control */
1086         if(**str == '+') { /* increment follows; read it */
1087             (*str)++;
1088             if(**str == '!') type = *(*str)++; // Bronstein TC
1089             if(result = NextIntegerFromString( str, &temp2)) return -1;
1090             *inc = temp2 * 1000;
1091             if(**str == '.') { // read fraction of increment
1092                 char *start = ++(*str);
1093                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1094                 temp2 *= 1000;
1095                 while(start++ < *str) temp2 /= 10;
1096                 *inc += temp2;
1097             }
1098         } else *inc = 0;
1099         *moves = 0; *tc = temp * 1000; *incType = type;
1100         return 0;
1101     }
1102
1103     (*str)++; /* classical time control */
1104     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1105
1106     if(result == 0) {
1107         *moves = temp;
1108         *tc    = temp2 * 1000;
1109         *inc   = 0;
1110         *incType = type;
1111     }
1112     return result;
1113 }
1114
1115 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1116 {   /* [HGM] get time to add from the multi-session time-control string */
1117     int incType, moves=1; /* kludge to force reading of first session */
1118     long time, increment;
1119     char *s = tcString;
1120
1121     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1122     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1123     do {
1124         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1125         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1126         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1127         if(movenr == -1) return time;    /* last move before new session     */
1128         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1129         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1130         if(!moves) return increment;     /* current session is incremental   */
1131         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1132     } while(movenr >= -1);               /* try again for next session       */
1133
1134     return 0; // no new time quota on this move
1135 }
1136
1137 int
1138 ParseTimeControl(tc, ti, mps)
1139      char *tc;
1140      float ti;
1141      int mps;
1142 {
1143   long tc1;
1144   long tc2;
1145   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1146   int min, sec=0;
1147
1148   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1149   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1150       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1151   if(ti > 0) {
1152
1153     if(mps)
1154       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1155     else 
1156       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1157   } else {
1158     if(mps)
1159       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1160     else 
1161       snprintf(buf, MSG_SIZ, ":%s", mytc);
1162   }
1163   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1164   
1165   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1166     return FALSE;
1167   }
1168
1169   if( *tc == '/' ) {
1170     /* Parse second time control */
1171     tc++;
1172
1173     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1174       return FALSE;
1175     }
1176
1177     if( tc2 == 0 ) {
1178       return FALSE;
1179     }
1180
1181     timeControl_2 = tc2 * 1000;
1182   }
1183   else {
1184     timeControl_2 = 0;
1185   }
1186
1187   if( tc1 == 0 ) {
1188     return FALSE;
1189   }
1190
1191   timeControl = tc1 * 1000;
1192
1193   if (ti >= 0) {
1194     timeIncrement = ti * 1000;  /* convert to ms */
1195     movesPerSession = 0;
1196   } else {
1197     timeIncrement = 0;
1198     movesPerSession = mps;
1199   }
1200   return TRUE;
1201 }
1202
1203 void
1204 InitBackEnd2()
1205 {
1206     if (appData.debugMode) {
1207         fprintf(debugFP, "%s\n", programVersion);
1208     }
1209
1210     set_cont_sequence(appData.wrapContSeq);
1211     if (appData.matchGames > 0) {
1212         appData.matchMode = TRUE;
1213     } else if (appData.matchMode) {
1214         appData.matchGames = 1;
1215     }
1216     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1217         appData.matchGames = appData.sameColorGames;
1218     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1219         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1220         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1221     }
1222     Reset(TRUE, FALSE);
1223     if (appData.noChessProgram || first.protocolVersion == 1) {
1224       InitBackEnd3();
1225     } else {
1226       /* kludge: allow timeout for initial "feature" commands */
1227       FreezeUI();
1228       DisplayMessage("", _("Starting chess program"));
1229       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1230     }
1231 }
1232
1233 void
1234 MatchEvent(int mode)
1235 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1236         /* Set up machine vs. machine match */
1237         if (appData.noChessProgram) {
1238             DisplayFatalError(_("Can't have a match with no chess programs"),
1239                               0, 2);
1240             return;
1241         }
1242         matchMode = mode;
1243         matchGame = 1;
1244         if (*appData.loadGameFile != NULLCHAR) {
1245             int index = appData.loadGameIndex; // [HGM] autoinc
1246             if(index<0) lastIndex = index = 1;
1247             if (!LoadGameFromFile(appData.loadGameFile,
1248                                   index,
1249                                   appData.loadGameFile, FALSE)) {
1250                 DisplayFatalError(_("Bad game file"), 0, 1);
1251                 return;
1252             }
1253         } else if (*appData.loadPositionFile != NULLCHAR) {
1254             int index = appData.loadPositionIndex; // [HGM] autoinc
1255             if(index<0) lastIndex = index = 1;
1256             if (!LoadPositionFromFile(appData.loadPositionFile,
1257                                       index,
1258                                       appData.loadPositionFile)) {
1259                 DisplayFatalError(_("Bad position file"), 0, 1);
1260                 return;
1261             }
1262         }
1263         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1264         TwoMachinesEvent();
1265 }
1266
1267 void
1268 InitBackEnd3 P((void))
1269 {
1270     GameMode initialMode;
1271     char buf[MSG_SIZ];
1272     int err, len;
1273
1274     InitChessProgram(&first, startedFromSetupPosition);
1275
1276     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1277         free(programVersion);
1278         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1279         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1280     }
1281
1282     if (appData.icsActive) {
1283 #ifdef WIN32
1284         /* [DM] Make a console window if needed [HGM] merged ifs */
1285         ConsoleCreate();
1286 #endif
1287         err = establish();
1288         if (err != 0)
1289           {
1290             if (*appData.icsCommPort != NULLCHAR)
1291               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1292                              appData.icsCommPort);
1293             else
1294               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1295                         appData.icsHost, appData.icsPort);
1296
1297             if( (len > MSG_SIZ) && appData.debugMode )
1298               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1299
1300             DisplayFatalError(buf, err, 1);
1301             return;
1302         }
1303         SetICSMode();
1304         telnetISR =
1305           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1306         fromUserISR =
1307           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1308         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1309             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1310     } else if (appData.noChessProgram) {
1311         SetNCPMode();
1312     } else {
1313         SetGNUMode();
1314     }
1315
1316     if (*appData.cmailGameName != NULLCHAR) {
1317         SetCmailMode();
1318         OpenLoopback(&cmailPR);
1319         cmailISR =
1320           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1321     }
1322
1323     ThawUI();
1324     DisplayMessage("", "");
1325     if (StrCaseCmp(appData.initialMode, "") == 0) {
1326       initialMode = BeginningOfGame;
1327       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1328         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1329         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1330         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1331         ModeHighlight();
1332       }
1333     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1334       initialMode = TwoMachinesPlay;
1335     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1336       initialMode = AnalyzeFile;
1337     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1338       initialMode = AnalyzeMode;
1339     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1340       initialMode = MachinePlaysWhite;
1341     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1342       initialMode = MachinePlaysBlack;
1343     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1344       initialMode = EditGame;
1345     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1346       initialMode = EditPosition;
1347     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1348       initialMode = Training;
1349     } else {
1350       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1351       if( (len > MSG_SIZ) && appData.debugMode )
1352         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1353
1354       DisplayFatalError(buf, 0, 2);
1355       return;
1356     }
1357
1358     if (appData.matchMode) {
1359         MatchEvent(TRUE);
1360     } else if (*appData.cmailGameName != NULLCHAR) {
1361         /* Set up cmail mode */
1362         ReloadCmailMsgEvent(TRUE);
1363     } else {
1364         /* Set up other modes */
1365         if (initialMode == AnalyzeFile) {
1366           if (*appData.loadGameFile == NULLCHAR) {
1367             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1368             return;
1369           }
1370         }
1371         if (*appData.loadGameFile != NULLCHAR) {
1372             (void) LoadGameFromFile(appData.loadGameFile,
1373                                     appData.loadGameIndex,
1374                                     appData.loadGameFile, TRUE);
1375         } else if (*appData.loadPositionFile != NULLCHAR) {
1376             (void) LoadPositionFromFile(appData.loadPositionFile,
1377                                         appData.loadPositionIndex,
1378                                         appData.loadPositionFile);
1379             /* [HGM] try to make self-starting even after FEN load */
1380             /* to allow automatic setup of fairy variants with wtm */
1381             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1382                 gameMode = BeginningOfGame;
1383                 setboardSpoiledMachineBlack = 1;
1384             }
1385             /* [HGM] loadPos: make that every new game uses the setup */
1386             /* from file as long as we do not switch variant          */
1387             if(!blackPlaysFirst) {
1388                 startedFromPositionFile = TRUE;
1389                 CopyBoard(filePosition, boards[0]);
1390             }
1391         }
1392         if (initialMode == AnalyzeMode) {
1393           if (appData.noChessProgram) {
1394             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1395             return;
1396           }
1397           if (appData.icsActive) {
1398             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1399             return;
1400           }
1401           AnalyzeModeEvent();
1402         } else if (initialMode == AnalyzeFile) {
1403           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1404           ShowThinkingEvent();
1405           AnalyzeFileEvent();
1406           AnalysisPeriodicEvent(1);
1407         } else if (initialMode == MachinePlaysWhite) {
1408           if (appData.noChessProgram) {
1409             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1410                               0, 2);
1411             return;
1412           }
1413           if (appData.icsActive) {
1414             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1415                               0, 2);
1416             return;
1417           }
1418           MachineWhiteEvent();
1419         } else if (initialMode == MachinePlaysBlack) {
1420           if (appData.noChessProgram) {
1421             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1422                               0, 2);
1423             return;
1424           }
1425           if (appData.icsActive) {
1426             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1427                               0, 2);
1428             return;
1429           }
1430           MachineBlackEvent();
1431         } else if (initialMode == TwoMachinesPlay) {
1432           if (appData.noChessProgram) {
1433             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1434                               0, 2);
1435             return;
1436           }
1437           if (appData.icsActive) {
1438             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1439                               0, 2);
1440             return;
1441           }
1442           TwoMachinesEvent();
1443         } else if (initialMode == EditGame) {
1444           EditGameEvent();
1445         } else if (initialMode == EditPosition) {
1446           EditPositionEvent();
1447         } else if (initialMode == Training) {
1448           if (*appData.loadGameFile == NULLCHAR) {
1449             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1450             return;
1451           }
1452           TrainingEvent();
1453         }
1454     }
1455 }
1456
1457 /*
1458  * Establish will establish a contact to a remote host.port.
1459  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1460  *  used to talk to the host.
1461  * Returns 0 if okay, error code if not.
1462  */
1463 int
1464 establish()
1465 {
1466     char buf[MSG_SIZ];
1467
1468     if (*appData.icsCommPort != NULLCHAR) {
1469         /* Talk to the host through a serial comm port */
1470         return OpenCommPort(appData.icsCommPort, &icsPR);
1471
1472     } else if (*appData.gateway != NULLCHAR) {
1473         if (*appData.remoteShell == NULLCHAR) {
1474             /* Use the rcmd protocol to run telnet program on a gateway host */
1475             snprintf(buf, sizeof(buf), "%s %s %s",
1476                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1477             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1478
1479         } else {
1480             /* Use the rsh program to run telnet program on a gateway host */
1481             if (*appData.remoteUser == NULLCHAR) {
1482                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1483                         appData.gateway, appData.telnetProgram,
1484                         appData.icsHost, appData.icsPort);
1485             } else {
1486                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1487                         appData.remoteShell, appData.gateway,
1488                         appData.remoteUser, appData.telnetProgram,
1489                         appData.icsHost, appData.icsPort);
1490             }
1491             return StartChildProcess(buf, "", &icsPR);
1492
1493         }
1494     } else if (appData.useTelnet) {
1495         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1496
1497     } else {
1498         /* TCP socket interface differs somewhat between
1499            Unix and NT; handle details in the front end.
1500            */
1501         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1502     }
1503 }
1504
1505 void EscapeExpand(char *p, char *q)
1506 {       // [HGM] initstring: routine to shape up string arguments
1507         while(*p++ = *q++) if(p[-1] == '\\')
1508             switch(*q++) {
1509                 case 'n': p[-1] = '\n'; break;
1510                 case 'r': p[-1] = '\r'; break;
1511                 case 't': p[-1] = '\t'; break;
1512                 case '\\': p[-1] = '\\'; break;
1513                 case 0: *p = 0; return;
1514                 default: p[-1] = q[-1]; break;
1515             }
1516 }
1517
1518 void
1519 show_bytes(fp, buf, count)
1520      FILE *fp;
1521      char *buf;
1522      int count;
1523 {
1524     while (count--) {
1525         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1526             fprintf(fp, "\\%03o", *buf & 0xff);
1527         } else {
1528             putc(*buf, fp);
1529         }
1530         buf++;
1531     }
1532     fflush(fp);
1533 }
1534
1535 /* Returns an errno value */
1536 int
1537 OutputMaybeTelnet(pr, message, count, outError)
1538      ProcRef pr;
1539      char *message;
1540      int count;
1541      int *outError;
1542 {
1543     char buf[8192], *p, *q, *buflim;
1544     int left, newcount, outcount;
1545
1546     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1547         *appData.gateway != NULLCHAR) {
1548         if (appData.debugMode) {
1549             fprintf(debugFP, ">ICS: ");
1550             show_bytes(debugFP, message, count);
1551             fprintf(debugFP, "\n");
1552         }
1553         return OutputToProcess(pr, message, count, outError);
1554     }
1555
1556     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1557     p = message;
1558     q = buf;
1559     left = count;
1560     newcount = 0;
1561     while (left) {
1562         if (q >= buflim) {
1563             if (appData.debugMode) {
1564                 fprintf(debugFP, ">ICS: ");
1565                 show_bytes(debugFP, buf, newcount);
1566                 fprintf(debugFP, "\n");
1567             }
1568             outcount = OutputToProcess(pr, buf, newcount, outError);
1569             if (outcount < newcount) return -1; /* to be sure */
1570             q = buf;
1571             newcount = 0;
1572         }
1573         if (*p == '\n') {
1574             *q++ = '\r';
1575             newcount++;
1576         } else if (((unsigned char) *p) == TN_IAC) {
1577             *q++ = (char) TN_IAC;
1578             newcount ++;
1579         }
1580         *q++ = *p++;
1581         newcount++;
1582         left--;
1583     }
1584     if (appData.debugMode) {
1585         fprintf(debugFP, ">ICS: ");
1586         show_bytes(debugFP, buf, newcount);
1587         fprintf(debugFP, "\n");
1588     }
1589     outcount = OutputToProcess(pr, buf, newcount, outError);
1590     if (outcount < newcount) return -1; /* to be sure */
1591     return count;
1592 }
1593
1594 void
1595 read_from_player(isr, closure, message, count, error)
1596      InputSourceRef isr;
1597      VOIDSTAR closure;
1598      char *message;
1599      int count;
1600      int error;
1601 {
1602     int outError, outCount;
1603     static int gotEof = 0;
1604
1605     /* Pass data read from player on to ICS */
1606     if (count > 0) {
1607         gotEof = 0;
1608         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1609         if (outCount < count) {
1610             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1611         }
1612     } else if (count < 0) {
1613         RemoveInputSource(isr);
1614         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1615     } else if (gotEof++ > 0) {
1616         RemoveInputSource(isr);
1617         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1618     }
1619 }
1620
1621 void
1622 KeepAlive()
1623 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1624     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1625     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1626     SendToICS("date\n");
1627     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1628 }
1629
1630 /* added routine for printf style output to ics */
1631 void ics_printf(char *format, ...)
1632 {
1633     char buffer[MSG_SIZ];
1634     va_list args;
1635
1636     va_start(args, format);
1637     vsnprintf(buffer, sizeof(buffer), format, args);
1638     buffer[sizeof(buffer)-1] = '\0';
1639     SendToICS(buffer);
1640     va_end(args);
1641 }
1642
1643 void
1644 SendToICS(s)
1645      char *s;
1646 {
1647     int count, outCount, outError;
1648
1649     if (icsPR == NULL) return;
1650
1651     count = strlen(s);
1652     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1653     if (outCount < count) {
1654         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1655     }
1656 }
1657
1658 /* This is used for sending logon scripts to the ICS. Sending
1659    without a delay causes problems when using timestamp on ICC
1660    (at least on my machine). */
1661 void
1662 SendToICSDelayed(s,msdelay)
1663      char *s;
1664      long msdelay;
1665 {
1666     int count, outCount, outError;
1667
1668     if (icsPR == NULL) return;
1669
1670     count = strlen(s);
1671     if (appData.debugMode) {
1672         fprintf(debugFP, ">ICS: ");
1673         show_bytes(debugFP, s, count);
1674         fprintf(debugFP, "\n");
1675     }
1676     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1677                                       msdelay);
1678     if (outCount < count) {
1679         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1680     }
1681 }
1682
1683
1684 /* Remove all highlighting escape sequences in s
1685    Also deletes any suffix starting with '('
1686    */
1687 char *
1688 StripHighlightAndTitle(s)
1689      char *s;
1690 {
1691     static char retbuf[MSG_SIZ];
1692     char *p = retbuf;
1693
1694     while (*s != NULLCHAR) {
1695         while (*s == '\033') {
1696             while (*s != NULLCHAR && !isalpha(*s)) s++;
1697             if (*s != NULLCHAR) s++;
1698         }
1699         while (*s != NULLCHAR && *s != '\033') {
1700             if (*s == '(' || *s == '[') {
1701                 *p = NULLCHAR;
1702                 return retbuf;
1703             }
1704             *p++ = *s++;
1705         }
1706     }
1707     *p = NULLCHAR;
1708     return retbuf;
1709 }
1710
1711 /* Remove all highlighting escape sequences in s */
1712 char *
1713 StripHighlight(s)
1714      char *s;
1715 {
1716     static char retbuf[MSG_SIZ];
1717     char *p = retbuf;
1718
1719     while (*s != NULLCHAR) {
1720         while (*s == '\033') {
1721             while (*s != NULLCHAR && !isalpha(*s)) s++;
1722             if (*s != NULLCHAR) s++;
1723         }
1724         while (*s != NULLCHAR && *s != '\033') {
1725             *p++ = *s++;
1726         }
1727     }
1728     *p = NULLCHAR;
1729     return retbuf;
1730 }
1731
1732 char *variantNames[] = VARIANT_NAMES;
1733 char *
1734 VariantName(v)
1735      VariantClass v;
1736 {
1737     return variantNames[v];
1738 }
1739
1740
1741 /* Identify a variant from the strings the chess servers use or the
1742    PGN Variant tag names we use. */
1743 VariantClass
1744 StringToVariant(e)
1745      char *e;
1746 {
1747     char *p;
1748     int wnum = -1;
1749     VariantClass v = VariantNormal;
1750     int i, found = FALSE;
1751     char buf[MSG_SIZ];
1752     int len;
1753
1754     if (!e) return v;
1755
1756     /* [HGM] skip over optional board-size prefixes */
1757     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1758         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1759         while( *e++ != '_');
1760     }
1761
1762     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1763         v = VariantNormal;
1764         found = TRUE;
1765     } else
1766     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1767       if (StrCaseStr(e, variantNames[i])) {
1768         v = (VariantClass) i;
1769         found = TRUE;
1770         break;
1771       }
1772     }
1773
1774     if (!found) {
1775       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1776           || StrCaseStr(e, "wild/fr")
1777           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1778         v = VariantFischeRandom;
1779       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1780                  (i = 1, p = StrCaseStr(e, "w"))) {
1781         p += i;
1782         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1783         if (isdigit(*p)) {
1784           wnum = atoi(p);
1785         } else {
1786           wnum = -1;
1787         }
1788         switch (wnum) {
1789         case 0: /* FICS only, actually */
1790         case 1:
1791           /* Castling legal even if K starts on d-file */
1792           v = VariantWildCastle;
1793           break;
1794         case 2:
1795         case 3:
1796         case 4:
1797           /* Castling illegal even if K & R happen to start in
1798              normal positions. */
1799           v = VariantNoCastle;
1800           break;
1801         case 5:
1802         case 7:
1803         case 8:
1804         case 10:
1805         case 11:
1806         case 12:
1807         case 13:
1808         case 14:
1809         case 15:
1810         case 18:
1811         case 19:
1812           /* Castling legal iff K & R start in normal positions */
1813           v = VariantNormal;
1814           break;
1815         case 6:
1816         case 20:
1817         case 21:
1818           /* Special wilds for position setup; unclear what to do here */
1819           v = VariantLoadable;
1820           break;
1821         case 9:
1822           /* Bizarre ICC game */
1823           v = VariantTwoKings;
1824           break;
1825         case 16:
1826           v = VariantKriegspiel;
1827           break;
1828         case 17:
1829           v = VariantLosers;
1830           break;
1831         case 22:
1832           v = VariantFischeRandom;
1833           break;
1834         case 23:
1835           v = VariantCrazyhouse;
1836           break;
1837         case 24:
1838           v = VariantBughouse;
1839           break;
1840         case 25:
1841           v = Variant3Check;
1842           break;
1843         case 26:
1844           /* Not quite the same as FICS suicide! */
1845           v = VariantGiveaway;
1846           break;
1847         case 27:
1848           v = VariantAtomic;
1849           break;
1850         case 28:
1851           v = VariantShatranj;
1852           break;
1853
1854         /* Temporary names for future ICC types.  The name *will* change in
1855            the next xboard/WinBoard release after ICC defines it. */
1856         case 29:
1857           v = Variant29;
1858           break;
1859         case 30:
1860           v = Variant30;
1861           break;
1862         case 31:
1863           v = Variant31;
1864           break;
1865         case 32:
1866           v = Variant32;
1867           break;
1868         case 33:
1869           v = Variant33;
1870           break;
1871         case 34:
1872           v = Variant34;
1873           break;
1874         case 35:
1875           v = Variant35;
1876           break;
1877         case 36:
1878           v = Variant36;
1879           break;
1880         case 37:
1881           v = VariantShogi;
1882           break;
1883         case 38:
1884           v = VariantXiangqi;
1885           break;
1886         case 39:
1887           v = VariantCourier;
1888           break;
1889         case 40:
1890           v = VariantGothic;
1891           break;
1892         case 41:
1893           v = VariantCapablanca;
1894           break;
1895         case 42:
1896           v = VariantKnightmate;
1897           break;
1898         case 43:
1899           v = VariantFairy;
1900           break;
1901         case 44:
1902           v = VariantCylinder;
1903           break;
1904         case 45:
1905           v = VariantFalcon;
1906           break;
1907         case 46:
1908           v = VariantCapaRandom;
1909           break;
1910         case 47:
1911           v = VariantBerolina;
1912           break;
1913         case 48:
1914           v = VariantJanus;
1915           break;
1916         case 49:
1917           v = VariantSuper;
1918           break;
1919         case 50:
1920           v = VariantGreat;
1921           break;
1922         case -1:
1923           /* Found "wild" or "w" in the string but no number;
1924              must assume it's normal chess. */
1925           v = VariantNormal;
1926           break;
1927         default:
1928           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1929           if( (len > MSG_SIZ) && appData.debugMode )
1930             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1931
1932           DisplayError(buf, 0);
1933           v = VariantUnknown;
1934           break;
1935         }
1936       }
1937     }
1938     if (appData.debugMode) {
1939       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1940               e, wnum, VariantName(v));
1941     }
1942     return v;
1943 }
1944
1945 static int leftover_start = 0, leftover_len = 0;
1946 char star_match[STAR_MATCH_N][MSG_SIZ];
1947
1948 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1949    advance *index beyond it, and set leftover_start to the new value of
1950    *index; else return FALSE.  If pattern contains the character '*', it
1951    matches any sequence of characters not containing '\r', '\n', or the
1952    character following the '*' (if any), and the matched sequence(s) are
1953    copied into star_match.
1954    */
1955 int
1956 looking_at(buf, index, pattern)
1957      char *buf;
1958      int *index;
1959      char *pattern;
1960 {
1961     char *bufp = &buf[*index], *patternp = pattern;
1962     int star_count = 0;
1963     char *matchp = star_match[0];
1964
1965     for (;;) {
1966         if (*patternp == NULLCHAR) {
1967             *index = leftover_start = bufp - buf;
1968             *matchp = NULLCHAR;
1969             return TRUE;
1970         }
1971         if (*bufp == NULLCHAR) return FALSE;
1972         if (*patternp == '*') {
1973             if (*bufp == *(patternp + 1)) {
1974                 *matchp = NULLCHAR;
1975                 matchp = star_match[++star_count];
1976                 patternp += 2;
1977                 bufp++;
1978                 continue;
1979             } else if (*bufp == '\n' || *bufp == '\r') {
1980                 patternp++;
1981                 if (*patternp == NULLCHAR)
1982                   continue;
1983                 else
1984                   return FALSE;
1985             } else {
1986                 *matchp++ = *bufp++;
1987                 continue;
1988             }
1989         }
1990         if (*patternp != *bufp) return FALSE;
1991         patternp++;
1992         bufp++;
1993     }
1994 }
1995
1996 void
1997 SendToPlayer(data, length)
1998      char *data;
1999      int length;
2000 {
2001     int error, outCount;
2002     outCount = OutputToProcess(NoProc, data, length, &error);
2003     if (outCount < length) {
2004         DisplayFatalError(_("Error writing to display"), error, 1);
2005     }
2006 }
2007
2008 void
2009 PackHolding(packed, holding)
2010      char packed[];
2011      char *holding;
2012 {
2013     char *p = holding;
2014     char *q = packed;
2015     int runlength = 0;
2016     int curr = 9999;
2017     do {
2018         if (*p == curr) {
2019             runlength++;
2020         } else {
2021             switch (runlength) {
2022               case 0:
2023                 break;
2024               case 1:
2025                 *q++ = curr;
2026                 break;
2027               case 2:
2028                 *q++ = curr;
2029                 *q++ = curr;
2030                 break;
2031               default:
2032                 sprintf(q, "%d", runlength);
2033                 while (*q) q++;
2034                 *q++ = curr;
2035                 break;
2036             }
2037             runlength = 1;
2038             curr = *p;
2039         }
2040     } while (*p++);
2041     *q = NULLCHAR;
2042 }
2043
2044 /* Telnet protocol requests from the front end */
2045 void
2046 TelnetRequest(ddww, option)
2047      unsigned char ddww, option;
2048 {
2049     unsigned char msg[3];
2050     int outCount, outError;
2051
2052     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2053
2054     if (appData.debugMode) {
2055         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2056         switch (ddww) {
2057           case TN_DO:
2058             ddwwStr = "DO";
2059             break;
2060           case TN_DONT:
2061             ddwwStr = "DONT";
2062             break;
2063           case TN_WILL:
2064             ddwwStr = "WILL";
2065             break;
2066           case TN_WONT:
2067             ddwwStr = "WONT";
2068             break;
2069           default:
2070             ddwwStr = buf1;
2071             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2072             break;
2073         }
2074         switch (option) {
2075           case TN_ECHO:
2076             optionStr = "ECHO";
2077             break;
2078           default:
2079             optionStr = buf2;
2080             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2081             break;
2082         }
2083         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2084     }
2085     msg[0] = TN_IAC;
2086     msg[1] = ddww;
2087     msg[2] = option;
2088     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2089     if (outCount < 3) {
2090         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2091     }
2092 }
2093
2094 void
2095 DoEcho()
2096 {
2097     if (!appData.icsActive) return;
2098     TelnetRequest(TN_DO, TN_ECHO);
2099 }
2100
2101 void
2102 DontEcho()
2103 {
2104     if (!appData.icsActive) return;
2105     TelnetRequest(TN_DONT, TN_ECHO);
2106 }
2107
2108 void
2109 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2110 {
2111     /* put the holdings sent to us by the server on the board holdings area */
2112     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2113     char p;
2114     ChessSquare piece;
2115
2116     if(gameInfo.holdingsWidth < 2)  return;
2117     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2118         return; // prevent overwriting by pre-board holdings
2119
2120     if( (int)lowestPiece >= BlackPawn ) {
2121         holdingsColumn = 0;
2122         countsColumn = 1;
2123         holdingsStartRow = BOARD_HEIGHT-1;
2124         direction = -1;
2125     } else {
2126         holdingsColumn = BOARD_WIDTH-1;
2127         countsColumn = BOARD_WIDTH-2;
2128         holdingsStartRow = 0;
2129         direction = 1;
2130     }
2131
2132     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2133         board[i][holdingsColumn] = EmptySquare;
2134         board[i][countsColumn]   = (ChessSquare) 0;
2135     }
2136     while( (p=*holdings++) != NULLCHAR ) {
2137         piece = CharToPiece( ToUpper(p) );
2138         if(piece == EmptySquare) continue;
2139         /*j = (int) piece - (int) WhitePawn;*/
2140         j = PieceToNumber(piece);
2141         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2142         if(j < 0) continue;               /* should not happen */
2143         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2144         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2145         board[holdingsStartRow+j*direction][countsColumn]++;
2146     }
2147 }
2148
2149
2150 void
2151 VariantSwitch(Board board, VariantClass newVariant)
2152 {
2153    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2154    static Board oldBoard;
2155
2156    startedFromPositionFile = FALSE;
2157    if(gameInfo.variant == newVariant) return;
2158
2159    /* [HGM] This routine is called each time an assignment is made to
2160     * gameInfo.variant during a game, to make sure the board sizes
2161     * are set to match the new variant. If that means adding or deleting
2162     * holdings, we shift the playing board accordingly
2163     * This kludge is needed because in ICS observe mode, we get boards
2164     * of an ongoing game without knowing the variant, and learn about the
2165     * latter only later. This can be because of the move list we requested,
2166     * in which case the game history is refilled from the beginning anyway,
2167     * but also when receiving holdings of a crazyhouse game. In the latter
2168     * case we want to add those holdings to the already received position.
2169     */
2170
2171
2172    if (appData.debugMode) {
2173      fprintf(debugFP, "Switch board from %s to %s\n",
2174              VariantName(gameInfo.variant), VariantName(newVariant));
2175      setbuf(debugFP, NULL);
2176    }
2177    shuffleOpenings = 0;       /* [HGM] shuffle */
2178    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2179    switch(newVariant)
2180      {
2181      case VariantShogi:
2182        newWidth = 9;  newHeight = 9;
2183        gameInfo.holdingsSize = 7;
2184      case VariantBughouse:
2185      case VariantCrazyhouse:
2186        newHoldingsWidth = 2; break;
2187      case VariantGreat:
2188        newWidth = 10;
2189      case VariantSuper:
2190        newHoldingsWidth = 2;
2191        gameInfo.holdingsSize = 8;
2192        break;
2193      case VariantGothic:
2194      case VariantCapablanca:
2195      case VariantCapaRandom:
2196        newWidth = 10;
2197      default:
2198        newHoldingsWidth = gameInfo.holdingsSize = 0;
2199      };
2200
2201    if(newWidth  != gameInfo.boardWidth  ||
2202       newHeight != gameInfo.boardHeight ||
2203       newHoldingsWidth != gameInfo.holdingsWidth ) {
2204
2205      /* shift position to new playing area, if needed */
2206      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2207        for(i=0; i<BOARD_HEIGHT; i++)
2208          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2209            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2210              board[i][j];
2211        for(i=0; i<newHeight; i++) {
2212          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2213          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2214        }
2215      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2216        for(i=0; i<BOARD_HEIGHT; i++)
2217          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2218            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2219              board[i][j];
2220      }
2221      gameInfo.boardWidth  = newWidth;
2222      gameInfo.boardHeight = newHeight;
2223      gameInfo.holdingsWidth = newHoldingsWidth;
2224      gameInfo.variant = newVariant;
2225      InitDrawingSizes(-2, 0);
2226    } else gameInfo.variant = newVariant;
2227    CopyBoard(oldBoard, board);   // remember correctly formatted board
2228      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2229    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2230 }
2231
2232 static int loggedOn = FALSE;
2233
2234 /*-- Game start info cache: --*/
2235 int gs_gamenum;
2236 char gs_kind[MSG_SIZ];
2237 static char player1Name[128] = "";
2238 static char player2Name[128] = "";
2239 static char cont_seq[] = "\n\\   ";
2240 static int player1Rating = -1;
2241 static int player2Rating = -1;
2242 /*----------------------------*/
2243
2244 ColorClass curColor = ColorNormal;
2245 int suppressKibitz = 0;
2246
2247 // [HGM] seekgraph
2248 Boolean soughtPending = FALSE;
2249 Boolean seekGraphUp;
2250 #define MAX_SEEK_ADS 200
2251 #define SQUARE 0x80
2252 char *seekAdList[MAX_SEEK_ADS];
2253 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2254 float tcList[MAX_SEEK_ADS];
2255 char colorList[MAX_SEEK_ADS];
2256 int nrOfSeekAds = 0;
2257 int minRating = 1010, maxRating = 2800;
2258 int hMargin = 10, vMargin = 20, h, w;
2259 extern int squareSize, lineGap;
2260
2261 void
2262 PlotSeekAd(int i)
2263 {
2264         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2265         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2266         if(r < minRating+100 && r >=0 ) r = minRating+100;
2267         if(r > maxRating) r = maxRating;
2268         if(tc < 1.) tc = 1.;
2269         if(tc > 95.) tc = 95.;
2270         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2271         y = ((double)r - minRating)/(maxRating - minRating)
2272             * (h-vMargin-squareSize/8-1) + vMargin;
2273         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2274         if(strstr(seekAdList[i], " u ")) color = 1;
2275         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2276            !strstr(seekAdList[i], "bullet") &&
2277            !strstr(seekAdList[i], "blitz") &&
2278            !strstr(seekAdList[i], "standard") ) color = 2;
2279         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2280         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2281 }
2282
2283 void
2284 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2285 {
2286         char buf[MSG_SIZ], *ext = "";
2287         VariantClass v = StringToVariant(type);
2288         if(strstr(type, "wild")) {
2289             ext = type + 4; // append wild number
2290             if(v == VariantFischeRandom) type = "chess960"; else
2291             if(v == VariantLoadable) type = "setup"; else
2292             type = VariantName(v);
2293         }
2294         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2295         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2296             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2297             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2298             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2299             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2300             seekNrList[nrOfSeekAds] = nr;
2301             zList[nrOfSeekAds] = 0;
2302             seekAdList[nrOfSeekAds++] = StrSave(buf);
2303             if(plot) PlotSeekAd(nrOfSeekAds-1);
2304         }
2305 }
2306
2307 void
2308 EraseSeekDot(int i)
2309 {
2310     int x = xList[i], y = yList[i], d=squareSize/4, k;
2311     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2312     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2313     // now replot every dot that overlapped
2314     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2315         int xx = xList[k], yy = yList[k];
2316         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2317             DrawSeekDot(xx, yy, colorList[k]);
2318     }
2319 }
2320
2321 void
2322 RemoveSeekAd(int nr)
2323 {
2324         int i;
2325         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2326             EraseSeekDot(i);
2327             if(seekAdList[i]) free(seekAdList[i]);
2328             seekAdList[i] = seekAdList[--nrOfSeekAds];
2329             seekNrList[i] = seekNrList[nrOfSeekAds];
2330             ratingList[i] = ratingList[nrOfSeekAds];
2331             colorList[i]  = colorList[nrOfSeekAds];
2332             tcList[i] = tcList[nrOfSeekAds];
2333             xList[i]  = xList[nrOfSeekAds];
2334             yList[i]  = yList[nrOfSeekAds];
2335             zList[i]  = zList[nrOfSeekAds];
2336             seekAdList[nrOfSeekAds] = NULL;
2337             break;
2338         }
2339 }
2340
2341 Boolean
2342 MatchSoughtLine(char *line)
2343 {
2344     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2345     int nr, base, inc, u=0; char dummy;
2346
2347     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2348        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2349        (u=1) &&
2350        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2351         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2352         // match: compact and save the line
2353         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2354         return TRUE;
2355     }
2356     return FALSE;
2357 }
2358
2359 int
2360 DrawSeekGraph()
2361 {
2362     int i;
2363     if(!seekGraphUp) return FALSE;
2364     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2365     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2366
2367     DrawSeekBackground(0, 0, w, h);
2368     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2369     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2370     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2371         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2372         yy = h-1-yy;
2373         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2374         if(i%500 == 0) {
2375             char buf[MSG_SIZ];
2376             snprintf(buf, MSG_SIZ, "%d", i);
2377             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2378         }
2379     }
2380     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2381     for(i=1; i<100; i+=(i<10?1:5)) {
2382         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2383         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2384         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2385             char buf[MSG_SIZ];
2386             snprintf(buf, MSG_SIZ, "%d", i);
2387             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2388         }
2389     }
2390     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2391     return TRUE;
2392 }
2393
2394 int SeekGraphClick(ClickType click, int x, int y, int moving)
2395 {
2396     static int lastDown = 0, displayed = 0, lastSecond;
2397     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2398         if(click == Release || moving) return FALSE;
2399         nrOfSeekAds = 0;
2400         soughtPending = TRUE;
2401         SendToICS(ics_prefix);
2402         SendToICS("sought\n"); // should this be "sought all"?
2403     } else { // issue challenge based on clicked ad
2404         int dist = 10000; int i, closest = 0, second = 0;
2405         for(i=0; i<nrOfSeekAds; i++) {
2406             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2407             if(d < dist) { dist = d; closest = i; }
2408             second += (d - zList[i] < 120); // count in-range ads
2409             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2410         }
2411         if(dist < 120) {
2412             char buf[MSG_SIZ];
2413             second = (second > 1);
2414             if(displayed != closest || second != lastSecond) {
2415                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2416                 lastSecond = second; displayed = closest;
2417             }
2418             if(click == Press) {
2419                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2420                 lastDown = closest;
2421                 return TRUE;
2422             } // on press 'hit', only show info
2423             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2424             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2425             SendToICS(ics_prefix);
2426             SendToICS(buf);
2427             return TRUE; // let incoming board of started game pop down the graph
2428         } else if(click == Release) { // release 'miss' is ignored
2429             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2430             if(moving == 2) { // right up-click
2431                 nrOfSeekAds = 0; // refresh graph
2432                 soughtPending = TRUE;
2433                 SendToICS(ics_prefix);
2434                 SendToICS("sought\n"); // should this be "sought all"?
2435             }
2436             return TRUE;
2437         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2438         // press miss or release hit 'pop down' seek graph
2439         seekGraphUp = FALSE;
2440         DrawPosition(TRUE, NULL);
2441     }
2442     return TRUE;
2443 }
2444
2445 void
2446 read_from_ics(isr, closure, data, count, error)
2447      InputSourceRef isr;
2448      VOIDSTAR closure;
2449      char *data;
2450      int count;
2451      int error;
2452 {
2453 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2454 #define STARTED_NONE 0
2455 #define STARTED_MOVES 1
2456 #define STARTED_BOARD 2
2457 #define STARTED_OBSERVE 3
2458 #define STARTED_HOLDINGS 4
2459 #define STARTED_CHATTER 5
2460 #define STARTED_COMMENT 6
2461 #define STARTED_MOVES_NOHIDE 7
2462
2463     static int started = STARTED_NONE;
2464     static char parse[20000];
2465     static int parse_pos = 0;
2466     static char buf[BUF_SIZE + 1];
2467     static int firstTime = TRUE, intfSet = FALSE;
2468     static ColorClass prevColor = ColorNormal;
2469     static int savingComment = FALSE;
2470     static int cmatch = 0; // continuation sequence match
2471     char *bp;
2472     char str[MSG_SIZ];
2473     int i, oldi;
2474     int buf_len;
2475     int next_out;
2476     int tkind;
2477     int backup;    /* [DM] For zippy color lines */
2478     char *p;
2479     char talker[MSG_SIZ]; // [HGM] chat
2480     int channel;
2481
2482     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2483
2484     if (appData.debugMode) {
2485       if (!error) {
2486         fprintf(debugFP, "<ICS: ");
2487         show_bytes(debugFP, data, count);
2488         fprintf(debugFP, "\n");
2489       }
2490     }
2491
2492     if (appData.debugMode) { int f = forwardMostMove;
2493         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2494                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2495                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2496     }
2497     if (count > 0) {
2498         /* If last read ended with a partial line that we couldn't parse,
2499            prepend it to the new read and try again. */
2500         if (leftover_len > 0) {
2501             for (i=0; i<leftover_len; i++)
2502               buf[i] = buf[leftover_start + i];
2503         }
2504
2505     /* copy new characters into the buffer */
2506     bp = buf + leftover_len;
2507     buf_len=leftover_len;
2508     for (i=0; i<count; i++)
2509     {
2510         // ignore these
2511         if (data[i] == '\r')
2512             continue;
2513
2514         // join lines split by ICS?
2515         if (!appData.noJoin)
2516         {
2517             /*
2518                 Joining just consists of finding matches against the
2519                 continuation sequence, and discarding that sequence
2520                 if found instead of copying it.  So, until a match
2521                 fails, there's nothing to do since it might be the
2522                 complete sequence, and thus, something we don't want
2523                 copied.
2524             */
2525             if (data[i] == cont_seq[cmatch])
2526             {
2527                 cmatch++;
2528                 if (cmatch == strlen(cont_seq))
2529                 {
2530                     cmatch = 0; // complete match.  just reset the counter
2531
2532                     /*
2533                         it's possible for the ICS to not include the space
2534                         at the end of the last word, making our [correct]
2535                         join operation fuse two separate words.  the server
2536                         does this when the space occurs at the width setting.
2537                     */
2538                     if (!buf_len || buf[buf_len-1] != ' ')
2539                     {
2540                         *bp++ = ' ';
2541                         buf_len++;
2542                     }
2543                 }
2544                 continue;
2545             }
2546             else if (cmatch)
2547             {
2548                 /*
2549                     match failed, so we have to copy what matched before
2550                     falling through and copying this character.  In reality,
2551                     this will only ever be just the newline character, but
2552                     it doesn't hurt to be precise.
2553                 */
2554                 strncpy(bp, cont_seq, cmatch);
2555                 bp += cmatch;
2556                 buf_len += cmatch;
2557                 cmatch = 0;
2558             }
2559         }
2560
2561         // copy this char
2562         *bp++ = data[i];
2563         buf_len++;
2564     }
2565
2566         buf[buf_len] = NULLCHAR;
2567 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2568         next_out = 0;
2569         leftover_start = 0;
2570
2571         i = 0;
2572         while (i < buf_len) {
2573             /* Deal with part of the TELNET option negotiation
2574                protocol.  We refuse to do anything beyond the
2575                defaults, except that we allow the WILL ECHO option,
2576                which ICS uses to turn off password echoing when we are
2577                directly connected to it.  We reject this option
2578                if localLineEditing mode is on (always on in xboard)
2579                and we are talking to port 23, which might be a real
2580                telnet server that will try to keep WILL ECHO on permanently.
2581              */
2582             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2583                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2584                 unsigned char option;
2585                 oldi = i;
2586                 switch ((unsigned char) buf[++i]) {
2587                   case TN_WILL:
2588                     if (appData.debugMode)
2589                       fprintf(debugFP, "\n<WILL ");
2590                     switch (option = (unsigned char) buf[++i]) {
2591                       case TN_ECHO:
2592                         if (appData.debugMode)
2593                           fprintf(debugFP, "ECHO ");
2594                         /* Reply only if this is a change, according
2595                            to the protocol rules. */
2596                         if (remoteEchoOption) break;
2597                         if (appData.localLineEditing &&
2598                             atoi(appData.icsPort) == TN_PORT) {
2599                             TelnetRequest(TN_DONT, TN_ECHO);
2600                         } else {
2601                             EchoOff();
2602                             TelnetRequest(TN_DO, TN_ECHO);
2603                             remoteEchoOption = TRUE;
2604                         }
2605                         break;
2606                       default:
2607                         if (appData.debugMode)
2608                           fprintf(debugFP, "%d ", option);
2609                         /* Whatever this is, we don't want it. */
2610                         TelnetRequest(TN_DONT, option);
2611                         break;
2612                     }
2613                     break;
2614                   case TN_WONT:
2615                     if (appData.debugMode)
2616                       fprintf(debugFP, "\n<WONT ");
2617                     switch (option = (unsigned char) buf[++i]) {
2618                       case TN_ECHO:
2619                         if (appData.debugMode)
2620                           fprintf(debugFP, "ECHO ");
2621                         /* Reply only if this is a change, according
2622                            to the protocol rules. */
2623                         if (!remoteEchoOption) break;
2624                         EchoOn();
2625                         TelnetRequest(TN_DONT, TN_ECHO);
2626                         remoteEchoOption = FALSE;
2627                         break;
2628                       default:
2629                         if (appData.debugMode)
2630                           fprintf(debugFP, "%d ", (unsigned char) option);
2631                         /* Whatever this is, it must already be turned
2632                            off, because we never agree to turn on
2633                            anything non-default, so according to the
2634                            protocol rules, we don't reply. */
2635                         break;
2636                     }
2637                     break;
2638                   case TN_DO:
2639                     if (appData.debugMode)
2640                       fprintf(debugFP, "\n<DO ");
2641                     switch (option = (unsigned char) buf[++i]) {
2642                       default:
2643                         /* Whatever this is, we refuse to do it. */
2644                         if (appData.debugMode)
2645                           fprintf(debugFP, "%d ", option);
2646                         TelnetRequest(TN_WONT, option);
2647                         break;
2648                     }
2649                     break;
2650                   case TN_DONT:
2651                     if (appData.debugMode)
2652                       fprintf(debugFP, "\n<DONT ");
2653                     switch (option = (unsigned char) buf[++i]) {
2654                       default:
2655                         if (appData.debugMode)
2656                           fprintf(debugFP, "%d ", option);
2657                         /* Whatever this is, we are already not doing
2658                            it, because we never agree to do anything
2659                            non-default, so according to the protocol
2660                            rules, we don't reply. */
2661                         break;
2662                     }
2663                     break;
2664                   case TN_IAC:
2665                     if (appData.debugMode)
2666                       fprintf(debugFP, "\n<IAC ");
2667                     /* Doubled IAC; pass it through */
2668                     i--;
2669                     break;
2670                   default:
2671                     if (appData.debugMode)
2672                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2673                     /* Drop all other telnet commands on the floor */
2674                     break;
2675                 }
2676                 if (oldi > next_out)
2677                   SendToPlayer(&buf[next_out], oldi - next_out);
2678                 if (++i > next_out)
2679                   next_out = i;
2680                 continue;
2681             }
2682
2683             /* OK, this at least will *usually* work */
2684             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2685                 loggedOn = TRUE;
2686             }
2687
2688             if (loggedOn && !intfSet) {
2689                 if (ics_type == ICS_ICC) {
2690                   snprintf(str, MSG_SIZ,
2691                           "/set-quietly interface %s\n/set-quietly style 12\n",
2692                           programVersion);
2693                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2694                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2695                 } else if (ics_type == ICS_CHESSNET) {
2696                   snprintf(str, MSG_SIZ, "/style 12\n");
2697                 } else {
2698                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2699                   strcat(str, programVersion);
2700                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2701                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2702                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2703 #ifdef WIN32
2704                   strcat(str, "$iset nohighlight 1\n");
2705 #endif
2706                   strcat(str, "$iset lock 1\n$style 12\n");
2707                 }
2708                 SendToICS(str);
2709                 NotifyFrontendLogin();
2710                 intfSet = TRUE;
2711             }
2712
2713             if (started == STARTED_COMMENT) {
2714                 /* Accumulate characters in comment */
2715                 parse[parse_pos++] = buf[i];
2716                 if (buf[i] == '\n') {
2717                     parse[parse_pos] = NULLCHAR;
2718                     if(chattingPartner>=0) {
2719                         char mess[MSG_SIZ];
2720                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2721                         OutputChatMessage(chattingPartner, mess);
2722                         chattingPartner = -1;
2723                         next_out = i+1; // [HGM] suppress printing in ICS window
2724                     } else
2725                     if(!suppressKibitz) // [HGM] kibitz
2726                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2727                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2728                         int nrDigit = 0, nrAlph = 0, j;
2729                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2730                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2731                         parse[parse_pos] = NULLCHAR;
2732                         // try to be smart: if it does not look like search info, it should go to
2733                         // ICS interaction window after all, not to engine-output window.
2734                         for(j=0; j<parse_pos; j++) { // count letters and digits
2735                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2736                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2737                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2738                         }
2739                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2740                             int depth=0; float score;
2741                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2742                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2743                                 pvInfoList[forwardMostMove-1].depth = depth;
2744                                 pvInfoList[forwardMostMove-1].score = 100*score;
2745                             }
2746                             OutputKibitz(suppressKibitz, parse);
2747                         } else {
2748                             char tmp[MSG_SIZ];
2749                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2750                             SendToPlayer(tmp, strlen(tmp));
2751                         }
2752                         next_out = i+1; // [HGM] suppress printing in ICS window
2753                     }
2754                     started = STARTED_NONE;
2755                 } else {
2756                     /* Don't match patterns against characters in comment */
2757                     i++;
2758                     continue;
2759                 }
2760             }
2761             if (started == STARTED_CHATTER) {
2762                 if (buf[i] != '\n') {
2763                     /* Don't match patterns against characters in chatter */
2764                     i++;
2765                     continue;
2766                 }
2767                 started = STARTED_NONE;
2768                 if(suppressKibitz) next_out = i+1;
2769             }
2770
2771             /* Kludge to deal with rcmd protocol */
2772             if (firstTime && looking_at(buf, &i, "\001*")) {
2773                 DisplayFatalError(&buf[1], 0, 1);
2774                 continue;
2775             } else {
2776                 firstTime = FALSE;
2777             }
2778
2779             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2780                 ics_type = ICS_ICC;
2781                 ics_prefix = "/";
2782                 if (appData.debugMode)
2783                   fprintf(debugFP, "ics_type %d\n", ics_type);
2784                 continue;
2785             }
2786             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2787                 ics_type = ICS_FICS;
2788                 ics_prefix = "$";
2789                 if (appData.debugMode)
2790                   fprintf(debugFP, "ics_type %d\n", ics_type);
2791                 continue;
2792             }
2793             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2794                 ics_type = ICS_CHESSNET;
2795                 ics_prefix = "/";
2796                 if (appData.debugMode)
2797                   fprintf(debugFP, "ics_type %d\n", ics_type);
2798                 continue;
2799             }
2800
2801             if (!loggedOn &&
2802                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2803                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2804                  looking_at(buf, &i, "will be \"*\""))) {
2805               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2806               continue;
2807             }
2808
2809             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2810               char buf[MSG_SIZ];
2811               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2812               DisplayIcsInteractionTitle(buf);
2813               have_set_title = TRUE;
2814             }
2815
2816             /* skip finger notes */
2817             if (started == STARTED_NONE &&
2818                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2819                  (buf[i] == '1' && buf[i+1] == '0')) &&
2820                 buf[i+2] == ':' && buf[i+3] == ' ') {
2821               started = STARTED_CHATTER;
2822               i += 3;
2823               continue;
2824             }
2825
2826             oldi = i;
2827             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2828             if(appData.seekGraph) {
2829                 if(soughtPending && MatchSoughtLine(buf+i)) {
2830                     i = strstr(buf+i, "rated") - buf;
2831                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2832                     next_out = leftover_start = i;
2833                     started = STARTED_CHATTER;
2834                     suppressKibitz = TRUE;
2835                     continue;
2836                 }
2837                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2838                         && looking_at(buf, &i, "* ads displayed")) {
2839                     soughtPending = FALSE;
2840                     seekGraphUp = TRUE;
2841                     DrawSeekGraph();
2842                     continue;
2843                 }
2844                 if(appData.autoRefresh) {
2845                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2846                         int s = (ics_type == ICS_ICC); // ICC format differs
2847                         if(seekGraphUp)
2848                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2849                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2850                         looking_at(buf, &i, "*% "); // eat prompt
2851                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2852                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2853                         next_out = i; // suppress
2854                         continue;
2855                     }
2856                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2857                         char *p = star_match[0];
2858                         while(*p) {
2859                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2860                             while(*p && *p++ != ' '); // next
2861                         }
2862                         looking_at(buf, &i, "*% "); // eat prompt
2863                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2864                         next_out = i;
2865                         continue;
2866                     }
2867                 }
2868             }
2869
2870             /* skip formula vars */
2871             if (started == STARTED_NONE &&
2872                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2873               started = STARTED_CHATTER;
2874               i += 3;
2875               continue;
2876             }
2877
2878             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2879             if (appData.autoKibitz && started == STARTED_NONE &&
2880                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2881                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2882                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2883                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2884                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2885                         suppressKibitz = TRUE;
2886                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2887                         next_out = i;
2888                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2889                                 && (gameMode == IcsPlayingWhite)) ||
2890                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2891                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2892                             started = STARTED_CHATTER; // own kibitz we simply discard
2893                         else {
2894                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2895                             parse_pos = 0; parse[0] = NULLCHAR;
2896                             savingComment = TRUE;
2897                             suppressKibitz = gameMode != IcsObserving ? 2 :
2898                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2899                         }
2900                         continue;
2901                 } else
2902                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2903                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2904                          && atoi(star_match[0])) {
2905                     // suppress the acknowledgements of our own autoKibitz
2906                     char *p;
2907                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2908                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2909                     SendToPlayer(star_match[0], strlen(star_match[0]));
2910                     if(looking_at(buf, &i, "*% ")) // eat prompt
2911                         suppressKibitz = FALSE;
2912                     next_out = i;
2913                     continue;
2914                 }
2915             } // [HGM] kibitz: end of patch
2916
2917             // [HGM] chat: intercept tells by users for which we have an open chat window
2918             channel = -1;
2919             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2920                                            looking_at(buf, &i, "* whispers:") ||
2921                                            looking_at(buf, &i, "* kibitzes:") ||
2922                                            looking_at(buf, &i, "* shouts:") ||
2923                                            looking_at(buf, &i, "* c-shouts:") ||
2924                                            looking_at(buf, &i, "--> * ") ||
2925                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2926                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2927                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2928                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2929                 int p;
2930                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2931                 chattingPartner = -1;
2932
2933                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2934                 for(p=0; p<MAX_CHAT; p++) {
2935                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2936                     talker[0] = '['; strcat(talker, "] ");
2937                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2938                     chattingPartner = p; break;
2939                     }
2940                 } else
2941                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2942                 for(p=0; p<MAX_CHAT; p++) {
2943                     if(!strcmp("kibitzes", chatPartner[p])) {
2944                         talker[0] = '['; strcat(talker, "] ");
2945                         chattingPartner = p; break;
2946                     }
2947                 } else
2948                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2949                 for(p=0; p<MAX_CHAT; p++) {
2950                     if(!strcmp("whispers", chatPartner[p])) {
2951                         talker[0] = '['; strcat(talker, "] ");
2952                         chattingPartner = p; break;
2953                     }
2954                 } else
2955                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2956                   if(buf[i-8] == '-' && buf[i-3] == 't')
2957                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2958                     if(!strcmp("c-shouts", chatPartner[p])) {
2959                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2960                         chattingPartner = p; break;
2961                     }
2962                   }
2963                   if(chattingPartner < 0)
2964                   for(p=0; p<MAX_CHAT; p++) {
2965                     if(!strcmp("shouts", chatPartner[p])) {
2966                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2967                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2968                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2969                         chattingPartner = p; break;
2970                     }
2971                   }
2972                 }
2973                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2974                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2975                     talker[0] = 0; Colorize(ColorTell, FALSE);
2976                     chattingPartner = p; break;
2977                 }
2978                 if(chattingPartner<0) i = oldi; else {
2979                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2980                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2981                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2982                     started = STARTED_COMMENT;
2983                     parse_pos = 0; parse[0] = NULLCHAR;
2984                     savingComment = 3 + chattingPartner; // counts as TRUE
2985                     suppressKibitz = TRUE;
2986                     continue;
2987                 }
2988             } // [HGM] chat: end of patch
2989
2990           backup = i;
2991             if (appData.zippyTalk || appData.zippyPlay) {
2992                 /* [DM] Backup address for color zippy lines */
2993 #if ZIPPY
2994                if (loggedOn == TRUE)
2995                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2996                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2997 #endif
2998             } // [DM] 'else { ' deleted
2999                 if (
3000                     /* Regular tells and says */
3001                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3002                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3003                     looking_at(buf, &i, "* says: ") ||
3004                     /* Don't color "message" or "messages" output */
3005                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3006                     looking_at(buf, &i, "*. * at *:*: ") ||
3007                     looking_at(buf, &i, "--* (*:*): ") ||
3008                     /* Message notifications (same color as tells) */
3009                     looking_at(buf, &i, "* has left a message ") ||
3010                     looking_at(buf, &i, "* just sent you a message:\n") ||
3011                     /* Whispers and kibitzes */
3012                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3013                     looking_at(buf, &i, "* kibitzes: ") ||
3014                     /* Channel tells */
3015                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3016
3017                   if (tkind == 1 && strchr(star_match[0], ':')) {
3018                       /* Avoid "tells you:" spoofs in channels */
3019                      tkind = 3;
3020                   }
3021                   if (star_match[0][0] == NULLCHAR ||
3022                       strchr(star_match[0], ' ') ||
3023                       (tkind == 3 && strchr(star_match[1], ' '))) {
3024                     /* Reject bogus matches */
3025                     i = oldi;
3026                   } else {
3027                     if (appData.colorize) {
3028                       if (oldi > next_out) {
3029                         SendToPlayer(&buf[next_out], oldi - next_out);
3030                         next_out = oldi;
3031                       }
3032                       switch (tkind) {
3033                       case 1:
3034                         Colorize(ColorTell, FALSE);
3035                         curColor = ColorTell;
3036                         break;
3037                       case 2:
3038                         Colorize(ColorKibitz, FALSE);
3039                         curColor = ColorKibitz;
3040                         break;
3041                       case 3:
3042                         p = strrchr(star_match[1], '(');
3043                         if (p == NULL) {
3044                           p = star_match[1];
3045                         } else {
3046                           p++;
3047                         }
3048                         if (atoi(p) == 1) {
3049                           Colorize(ColorChannel1, FALSE);
3050                           curColor = ColorChannel1;
3051                         } else {
3052                           Colorize(ColorChannel, FALSE);
3053                           curColor = ColorChannel;
3054                         }
3055                         break;
3056                       case 5:
3057                         curColor = ColorNormal;
3058                         break;
3059                       }
3060                     }
3061                     if (started == STARTED_NONE && appData.autoComment &&
3062                         (gameMode == IcsObserving ||
3063                          gameMode == IcsPlayingWhite ||
3064                          gameMode == IcsPlayingBlack)) {
3065                       parse_pos = i - oldi;
3066                       memcpy(parse, &buf[oldi], parse_pos);
3067                       parse[parse_pos] = NULLCHAR;
3068                       started = STARTED_COMMENT;
3069                       savingComment = TRUE;
3070                     } else {
3071                       started = STARTED_CHATTER;
3072                       savingComment = FALSE;
3073                     }
3074                     loggedOn = TRUE;
3075                     continue;
3076                   }
3077                 }
3078
3079                 if (looking_at(buf, &i, "* s-shouts: ") ||
3080                     looking_at(buf, &i, "* c-shouts: ")) {
3081                     if (appData.colorize) {
3082                         if (oldi > next_out) {
3083                             SendToPlayer(&buf[next_out], oldi - next_out);
3084                             next_out = oldi;
3085                         }
3086                         Colorize(ColorSShout, FALSE);
3087                         curColor = ColorSShout;
3088                     }
3089                     loggedOn = TRUE;
3090                     started = STARTED_CHATTER;
3091                     continue;
3092                 }
3093
3094                 if (looking_at(buf, &i, "--->")) {
3095                     loggedOn = TRUE;
3096                     continue;
3097                 }
3098
3099                 if (looking_at(buf, &i, "* shouts: ") ||
3100                     looking_at(buf, &i, "--> ")) {
3101                     if (appData.colorize) {
3102                         if (oldi > next_out) {
3103                             SendToPlayer(&buf[next_out], oldi - next_out);
3104                             next_out = oldi;
3105                         }
3106                         Colorize(ColorShout, FALSE);
3107                         curColor = ColorShout;
3108                     }
3109                     loggedOn = TRUE;
3110                     started = STARTED_CHATTER;
3111                     continue;
3112                 }
3113
3114                 if (looking_at( buf, &i, "Challenge:")) {
3115                     if (appData.colorize) {
3116                         if (oldi > next_out) {
3117                             SendToPlayer(&buf[next_out], oldi - next_out);
3118                             next_out = oldi;
3119                         }
3120                         Colorize(ColorChallenge, FALSE);
3121                         curColor = ColorChallenge;
3122                     }
3123                     loggedOn = TRUE;
3124                     continue;
3125                 }
3126
3127                 if (looking_at(buf, &i, "* offers you") ||
3128                     looking_at(buf, &i, "* offers to be") ||
3129                     looking_at(buf, &i, "* would like to") ||
3130                     looking_at(buf, &i, "* requests to") ||
3131                     looking_at(buf, &i, "Your opponent offers") ||
3132                     looking_at(buf, &i, "Your opponent requests")) {
3133
3134                     if (appData.colorize) {
3135                         if (oldi > next_out) {
3136                             SendToPlayer(&buf[next_out], oldi - next_out);
3137                             next_out = oldi;
3138                         }
3139                         Colorize(ColorRequest, FALSE);
3140                         curColor = ColorRequest;
3141                     }
3142                     continue;
3143                 }
3144
3145                 if (looking_at(buf, &i, "* (*) seeking")) {
3146                     if (appData.colorize) {
3147                         if (oldi > next_out) {
3148                             SendToPlayer(&buf[next_out], oldi - next_out);
3149                             next_out = oldi;
3150                         }
3151                         Colorize(ColorSeek, FALSE);
3152                         curColor = ColorSeek;
3153                     }
3154                     continue;
3155             }
3156
3157           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3158
3159             if (looking_at(buf, &i, "\\   ")) {
3160                 if (prevColor != ColorNormal) {
3161                     if (oldi > next_out) {
3162                         SendToPlayer(&buf[next_out], oldi - next_out);
3163                         next_out = oldi;
3164                     }
3165                     Colorize(prevColor, TRUE);
3166                     curColor = prevColor;
3167                 }
3168                 if (savingComment) {
3169                     parse_pos = i - oldi;
3170                     memcpy(parse, &buf[oldi], parse_pos);
3171                     parse[parse_pos] = NULLCHAR;
3172                     started = STARTED_COMMENT;
3173                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3174                         chattingPartner = savingComment - 3; // kludge to remember the box
3175                 } else {
3176                     started = STARTED_CHATTER;
3177                 }
3178                 continue;
3179             }
3180
3181             if (looking_at(buf, &i, "Black Strength :") ||
3182                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3183                 looking_at(buf, &i, "<10>") ||
3184                 looking_at(buf, &i, "#@#")) {
3185                 /* Wrong board style */
3186                 loggedOn = TRUE;
3187                 SendToICS(ics_prefix);
3188                 SendToICS("set style 12\n");
3189                 SendToICS(ics_prefix);
3190                 SendToICS("refresh\n");
3191                 continue;
3192             }
3193
3194             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3195                 ICSInitScript();
3196                 have_sent_ICS_logon = 1;
3197                 continue;
3198             }
3199
3200             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3201                 (looking_at(buf, &i, "\n<12> ") ||
3202                  looking_at(buf, &i, "<12> "))) {
3203                 loggedOn = TRUE;
3204                 if (oldi > next_out) {
3205                     SendToPlayer(&buf[next_out], oldi - next_out);
3206                 }
3207                 next_out = i;
3208                 started = STARTED_BOARD;
3209                 parse_pos = 0;
3210                 continue;
3211             }
3212
3213             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3214                 looking_at(buf, &i, "<b1> ")) {
3215                 if (oldi > next_out) {
3216                     SendToPlayer(&buf[next_out], oldi - next_out);
3217                 }
3218                 next_out = i;
3219                 started = STARTED_HOLDINGS;
3220                 parse_pos = 0;
3221                 continue;
3222             }
3223
3224             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3225                 loggedOn = TRUE;
3226                 /* Header for a move list -- first line */
3227
3228                 switch (ics_getting_history) {
3229                   case H_FALSE:
3230                     switch (gameMode) {
3231                       case IcsIdle:
3232                       case BeginningOfGame:
3233                         /* User typed "moves" or "oldmoves" while we
3234                            were idle.  Pretend we asked for these
3235                            moves and soak them up so user can step
3236                            through them and/or save them.
3237                            */
3238                         Reset(FALSE, TRUE);
3239                         gameMode = IcsObserving;
3240                         ModeHighlight();
3241                         ics_gamenum = -1;
3242                         ics_getting_history = H_GOT_UNREQ_HEADER;
3243                         break;
3244                       case EditGame: /*?*/
3245                       case EditPosition: /*?*/
3246                         /* Should above feature work in these modes too? */
3247                         /* For now it doesn't */
3248                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3249                         break;
3250                       default:
3251                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3252                         break;
3253                     }
3254                     break;
3255                   case H_REQUESTED:
3256                     /* Is this the right one? */
3257                     if (gameInfo.white && gameInfo.black &&
3258                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3259                         strcmp(gameInfo.black, star_match[2]) == 0) {
3260                         /* All is well */
3261                         ics_getting_history = H_GOT_REQ_HEADER;
3262                     }
3263                     break;
3264                   case H_GOT_REQ_HEADER:
3265                   case H_GOT_UNREQ_HEADER:
3266                   case H_GOT_UNWANTED_HEADER:
3267                   case H_GETTING_MOVES:
3268                     /* Should not happen */
3269                     DisplayError(_("Error gathering move list: two headers"), 0);
3270                     ics_getting_history = H_FALSE;
3271                     break;
3272                 }
3273
3274                 /* Save player ratings into gameInfo if needed */
3275                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3276                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3277                     (gameInfo.whiteRating == -1 ||
3278                      gameInfo.blackRating == -1)) {
3279
3280                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3281                     gameInfo.blackRating = string_to_rating(star_match[3]);
3282                     if (appData.debugMode)
3283                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3284                               gameInfo.whiteRating, gameInfo.blackRating);
3285                 }
3286                 continue;
3287             }
3288
3289             if (looking_at(buf, &i,
3290               "* * match, initial time: * minute*, increment: * second")) {
3291                 /* Header for a move list -- second line */
3292                 /* Initial board will follow if this is a wild game */
3293                 if (gameInfo.event != NULL) free(gameInfo.event);
3294                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3295                 gameInfo.event = StrSave(str);
3296                 /* [HGM] we switched variant. Translate boards if needed. */
3297                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3298                 continue;
3299             }
3300
3301             if (looking_at(buf, &i, "Move  ")) {
3302                 /* Beginning of a move list */
3303                 switch (ics_getting_history) {
3304                   case H_FALSE:
3305                     /* Normally should not happen */
3306                     /* Maybe user hit reset while we were parsing */
3307                     break;
3308                   case H_REQUESTED:
3309                     /* Happens if we are ignoring a move list that is not
3310                      * the one we just requested.  Common if the user
3311                      * tries to observe two games without turning off
3312                      * getMoveList */
3313                     break;
3314                   case H_GETTING_MOVES:
3315                     /* Should not happen */
3316                     DisplayError(_("Error gathering move list: nested"), 0);
3317                     ics_getting_history = H_FALSE;
3318                     break;
3319                   case H_GOT_REQ_HEADER:
3320                     ics_getting_history = H_GETTING_MOVES;
3321                     started = STARTED_MOVES;
3322                     parse_pos = 0;
3323                     if (oldi > next_out) {
3324                         SendToPlayer(&buf[next_out], oldi - next_out);
3325                     }
3326                     break;
3327                   case H_GOT_UNREQ_HEADER:
3328                     ics_getting_history = H_GETTING_MOVES;
3329                     started = STARTED_MOVES_NOHIDE;
3330                     parse_pos = 0;
3331                     break;
3332                   case H_GOT_UNWANTED_HEADER:
3333                     ics_getting_history = H_FALSE;
3334                     break;
3335                 }
3336                 continue;
3337             }
3338
3339             if (looking_at(buf, &i, "% ") ||
3340                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3341                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3342                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3343                     soughtPending = FALSE;
3344                     seekGraphUp = TRUE;
3345                     DrawSeekGraph();
3346                 }
3347                 if(suppressKibitz) next_out = i;
3348                 savingComment = FALSE;
3349                 suppressKibitz = 0;
3350                 switch (started) {
3351                   case STARTED_MOVES:
3352                   case STARTED_MOVES_NOHIDE:
3353                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3354                     parse[parse_pos + i - oldi] = NULLCHAR;
3355                     ParseGameHistory(parse);
3356 #if ZIPPY
3357                     if (appData.zippyPlay && first.initDone) {
3358                         FeedMovesToProgram(&first, forwardMostMove);
3359                         if (gameMode == IcsPlayingWhite) {
3360                             if (WhiteOnMove(forwardMostMove)) {
3361                                 if (first.sendTime) {
3362                                   if (first.useColors) {
3363                                     SendToProgram("black\n", &first);
3364                                   }
3365                                   SendTimeRemaining(&first, TRUE);
3366                                 }
3367                                 if (first.useColors) {
3368                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3369                                 }
3370                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3371                                 first.maybeThinking = TRUE;
3372                             } else {
3373                                 if (first.usePlayother) {
3374                                   if (first.sendTime) {
3375                                     SendTimeRemaining(&first, TRUE);
3376                                   }
3377                                   SendToProgram("playother\n", &first);
3378                                   firstMove = FALSE;
3379                                 } else {
3380                                   firstMove = TRUE;
3381                                 }
3382                             }
3383                         } else if (gameMode == IcsPlayingBlack) {
3384                             if (!WhiteOnMove(forwardMostMove)) {
3385                                 if (first.sendTime) {
3386                                   if (first.useColors) {
3387                                     SendToProgram("white\n", &first);
3388                                   }
3389                                   SendTimeRemaining(&first, FALSE);
3390                                 }
3391                                 if (first.useColors) {
3392                                   SendToProgram("black\n", &first);
3393                                 }
3394                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3395                                 first.maybeThinking = TRUE;
3396                             } else {
3397                                 if (first.usePlayother) {
3398                                   if (first.sendTime) {
3399                                     SendTimeRemaining(&first, FALSE);
3400                                   }
3401                                   SendToProgram("playother\n", &first);
3402                                   firstMove = FALSE;
3403                                 } else {
3404                                   firstMove = TRUE;
3405                                 }
3406                             }
3407                         }
3408                     }
3409 #endif
3410                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3411                         /* Moves came from oldmoves or moves command
3412                            while we weren't doing anything else.
3413                            */
3414                         currentMove = forwardMostMove;
3415                         ClearHighlights();/*!!could figure this out*/
3416                         flipView = appData.flipView;
3417                         DrawPosition(TRUE, boards[currentMove]);
3418                         DisplayBothClocks();
3419                         snprintf(str, MSG_SIZ, "%s vs. %s",
3420                                 gameInfo.white, gameInfo.black);
3421                         DisplayTitle(str);
3422                         gameMode = IcsIdle;
3423                     } else {
3424                         /* Moves were history of an active game */
3425                         if (gameInfo.resultDetails != NULL) {
3426                             free(gameInfo.resultDetails);
3427                             gameInfo.resultDetails = NULL;
3428                         }
3429                     }
3430                     HistorySet(parseList, backwardMostMove,
3431                                forwardMostMove, currentMove-1);
3432                     DisplayMove(currentMove - 1);
3433                     if (started == STARTED_MOVES) next_out = i;
3434                     started = STARTED_NONE;
3435                     ics_getting_history = H_FALSE;
3436                     break;
3437
3438                   case STARTED_OBSERVE:
3439                     started = STARTED_NONE;
3440                     SendToICS(ics_prefix);
3441                     SendToICS("refresh\n");
3442                     break;
3443
3444                   default:
3445                     break;
3446                 }
3447                 if(bookHit) { // [HGM] book: simulate book reply
3448                     static char bookMove[MSG_SIZ]; // a bit generous?
3449
3450                     programStats.nodes = programStats.depth = programStats.time =
3451                     programStats.score = programStats.got_only_move = 0;
3452                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3453
3454                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3455                     strcat(bookMove, bookHit);
3456                     HandleMachineMove(bookMove, &first);
3457                 }
3458                 continue;
3459             }
3460
3461             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3462                  started == STARTED_HOLDINGS ||
3463                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3464                 /* Accumulate characters in move list or board */
3465                 parse[parse_pos++] = buf[i];
3466             }
3467
3468             /* Start of game messages.  Mostly we detect start of game
3469                when the first board image arrives.  On some versions
3470                of the ICS, though, we need to do a "refresh" after starting
3471                to observe in order to get the current board right away. */
3472             if (looking_at(buf, &i, "Adding game * to observation list")) {
3473                 started = STARTED_OBSERVE;
3474                 continue;
3475             }
3476
3477             /* Handle auto-observe */
3478             if (appData.autoObserve &&
3479                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3480                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3481                 char *player;
3482                 /* Choose the player that was highlighted, if any. */
3483                 if (star_match[0][0] == '\033' ||
3484                     star_match[1][0] != '\033') {
3485                     player = star_match[0];
3486                 } else {
3487                     player = star_match[2];
3488                 }
3489                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3490                         ics_prefix, StripHighlightAndTitle(player));
3491                 SendToICS(str);
3492
3493                 /* Save ratings from notify string */
3494                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3495                 player1Rating = string_to_rating(star_match[1]);
3496                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3497                 player2Rating = string_to_rating(star_match[3]);
3498
3499                 if (appData.debugMode)
3500                   fprintf(debugFP,
3501                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3502                           player1Name, player1Rating,
3503                           player2Name, player2Rating);
3504
3505                 continue;
3506             }
3507
3508             /* Deal with automatic examine mode after a game,
3509                and with IcsObserving -> IcsExamining transition */
3510             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3511                 looking_at(buf, &i, "has made you an examiner of game *")) {
3512
3513                 int gamenum = atoi(star_match[0]);
3514                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3515                     gamenum == ics_gamenum) {
3516                     /* We were already playing or observing this game;
3517                        no need to refetch history */
3518                     gameMode = IcsExamining;
3519                     if (pausing) {
3520                         pauseExamForwardMostMove = forwardMostMove;
3521                     } else if (currentMove < forwardMostMove) {
3522                         ForwardInner(forwardMostMove);
3523                     }
3524                 } else {
3525                     /* I don't think this case really can happen */
3526                     SendToICS(ics_prefix);
3527                     SendToICS("refresh\n");
3528                 }
3529                 continue;
3530             }
3531
3532             /* Error messages */
3533 //          if (ics_user_moved) {
3534             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3535                 if (looking_at(buf, &i, "Illegal move") ||
3536                     looking_at(buf, &i, "Not a legal move") ||
3537                     looking_at(buf, &i, "Your king is in check") ||
3538                     looking_at(buf, &i, "It isn't your turn") ||
3539                     looking_at(buf, &i, "It is not your move")) {
3540                     /* Illegal move */
3541                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3542                         currentMove = forwardMostMove-1;
3543                         DisplayMove(currentMove - 1); /* before DMError */
3544                         DrawPosition(FALSE, boards[currentMove]);
3545                         SwitchClocks(forwardMostMove-1); // [HGM] race
3546                         DisplayBothClocks();
3547                     }
3548                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3549                     ics_user_moved = 0;
3550                     continue;
3551                 }
3552             }
3553
3554             if (looking_at(buf, &i, "still have time") ||
3555                 looking_at(buf, &i, "not out of time") ||
3556                 looking_at(buf, &i, "either player is out of time") ||
3557                 looking_at(buf, &i, "has timeseal; checking")) {
3558                 /* We must have called his flag a little too soon */
3559                 whiteFlag = blackFlag = FALSE;
3560                 continue;
3561             }
3562
3563             if (looking_at(buf, &i, "added * seconds to") ||
3564                 looking_at(buf, &i, "seconds were added to")) {
3565                 /* Update the clocks */
3566                 SendToICS(ics_prefix);
3567                 SendToICS("refresh\n");
3568                 continue;
3569             }
3570
3571             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3572                 ics_clock_paused = TRUE;
3573                 StopClocks();
3574                 continue;
3575             }
3576
3577             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3578                 ics_clock_paused = FALSE;
3579                 StartClocks();
3580                 continue;
3581             }
3582
3583             /* Grab player ratings from the Creating: message.
3584                Note we have to check for the special case when
3585                the ICS inserts things like [white] or [black]. */
3586             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3587                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3588                 /* star_matches:
3589                    0    player 1 name (not necessarily white)
3590                    1    player 1 rating
3591                    2    empty, white, or black (IGNORED)
3592                    3    player 2 name (not necessarily black)
3593                    4    player 2 rating
3594
3595                    The names/ratings are sorted out when the game
3596                    actually starts (below).
3597                 */
3598                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3599                 player1Rating = string_to_rating(star_match[1]);
3600                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3601                 player2Rating = string_to_rating(star_match[4]);
3602
3603                 if (appData.debugMode)
3604                   fprintf(debugFP,
3605                           "Ratings from 'Creating:' %s %d, %s %d\n",
3606                           player1Name, player1Rating,
3607                           player2Name, player2Rating);
3608
3609                 continue;
3610             }
3611
3612             /* Improved generic start/end-of-game messages */
3613             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3614                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3615                 /* If tkind == 0: */
3616                 /* star_match[0] is the game number */
3617                 /*           [1] is the white player's name */
3618                 /*           [2] is the black player's name */
3619                 /* For end-of-game: */
3620                 /*           [3] is the reason for the game end */
3621                 /*           [4] is a PGN end game-token, preceded by " " */
3622                 /* For start-of-game: */
3623                 /*           [3] begins with "Creating" or "Continuing" */
3624                 /*           [4] is " *" or empty (don't care). */
3625                 int gamenum = atoi(star_match[0]);
3626                 char *whitename, *blackname, *why, *endtoken;
3627                 ChessMove endtype = EndOfFile;
3628
3629                 if (tkind == 0) {
3630                   whitename = star_match[1];
3631                   blackname = star_match[2];
3632                   why = star_match[3];
3633                   endtoken = star_match[4];
3634                 } else {
3635                   whitename = star_match[1];
3636                   blackname = star_match[3];
3637                   why = star_match[5];
3638                   endtoken = star_match[6];
3639                 }
3640
3641                 /* Game start messages */
3642                 if (strncmp(why, "Creating ", 9) == 0 ||
3643                     strncmp(why, "Continuing ", 11) == 0) {
3644                     gs_gamenum = gamenum;
3645                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3646                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3647 #if ZIPPY
3648                     if (appData.zippyPlay) {
3649                         ZippyGameStart(whitename, blackname);
3650                     }
3651 #endif /*ZIPPY*/
3652                     partnerBoardValid = FALSE; // [HGM] bughouse
3653                     continue;
3654                 }
3655
3656                 /* Game end messages */
3657                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3658                     ics_gamenum != gamenum) {
3659                     continue;
3660                 }
3661                 while (endtoken[0] == ' ') endtoken++;
3662                 switch (endtoken[0]) {
3663                   case '*':
3664                   default:
3665                     endtype = GameUnfinished;
3666                     break;
3667                   case '0':
3668                     endtype = BlackWins;
3669                     break;
3670                   case '1':
3671                     if (endtoken[1] == '/')
3672                       endtype = GameIsDrawn;
3673                     else
3674                       endtype = WhiteWins;
3675                     break;
3676                 }
3677                 GameEnds(endtype, why, GE_ICS);
3678 #if ZIPPY
3679                 if (appData.zippyPlay && first.initDone) {
3680                     ZippyGameEnd(endtype, why);
3681                     if (first.pr == NULL) {
3682                       /* Start the next process early so that we'll
3683                          be ready for the next challenge */
3684                       StartChessProgram(&first);
3685                     }
3686                     /* Send "new" early, in case this command takes
3687                        a long time to finish, so that we'll be ready
3688                        for the next challenge. */
3689                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3690                     Reset(TRUE, TRUE);
3691                 }
3692 #endif /*ZIPPY*/
3693                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3694                 continue;
3695             }
3696
3697             if (looking_at(buf, &i, "Removing game * from observation") ||
3698                 looking_at(buf, &i, "no longer observing game *") ||
3699                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3700                 if (gameMode == IcsObserving &&
3701                     atoi(star_match[0]) == ics_gamenum)
3702                   {
3703                       /* icsEngineAnalyze */
3704                       if (appData.icsEngineAnalyze) {
3705                             ExitAnalyzeMode();
3706                             ModeHighlight();
3707                       }
3708                       StopClocks();
3709                       gameMode = IcsIdle;
3710                       ics_gamenum = -1;
3711                       ics_user_moved = FALSE;
3712                   }
3713                 continue;
3714             }
3715
3716             if (looking_at(buf, &i, "no longer examining game *")) {
3717                 if (gameMode == IcsExamining &&
3718                     atoi(star_match[0]) == ics_gamenum)
3719                   {
3720                       gameMode = IcsIdle;
3721                       ics_gamenum = -1;
3722                       ics_user_moved = FALSE;
3723                   }
3724                 continue;
3725             }
3726
3727             /* Advance leftover_start past any newlines we find,
3728                so only partial lines can get reparsed */
3729             if (looking_at(buf, &i, "\n")) {
3730                 prevColor = curColor;
3731                 if (curColor != ColorNormal) {
3732                     if (oldi > next_out) {
3733                         SendToPlayer(&buf[next_out], oldi - next_out);
3734                         next_out = oldi;
3735                     }
3736                     Colorize(ColorNormal, FALSE);
3737                     curColor = ColorNormal;
3738                 }
3739                 if (started == STARTED_BOARD) {
3740                     started = STARTED_NONE;
3741                     parse[parse_pos] = NULLCHAR;
3742                     ParseBoard12(parse);
3743                     ics_user_moved = 0;
3744
3745                     /* Send premove here */
3746                     if (appData.premove) {
3747                       char str[MSG_SIZ];
3748                       if (currentMove == 0 &&
3749                           gameMode == IcsPlayingWhite &&
3750                           appData.premoveWhite) {
3751                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3752                         if (appData.debugMode)
3753                           fprintf(debugFP, "Sending premove:\n");
3754                         SendToICS(str);
3755                       } else if (currentMove == 1 &&
3756                                  gameMode == IcsPlayingBlack &&
3757                                  appData.premoveBlack) {
3758                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3759                         if (appData.debugMode)
3760                           fprintf(debugFP, "Sending premove:\n");
3761                         SendToICS(str);
3762                       } else if (gotPremove) {
3763                         gotPremove = 0;
3764                         ClearPremoveHighlights();
3765                         if (appData.debugMode)
3766                           fprintf(debugFP, "Sending premove:\n");
3767                           UserMoveEvent(premoveFromX, premoveFromY,
3768                                         premoveToX, premoveToY,
3769                                         premovePromoChar);
3770                       }
3771                     }
3772
3773                     /* Usually suppress following prompt */
3774                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3775                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3776                         if (looking_at(buf, &i, "*% ")) {
3777                             savingComment = FALSE;
3778                             suppressKibitz = 0;
3779                         }
3780                     }
3781                     next_out = i;
3782                 } else if (started == STARTED_HOLDINGS) {
3783                     int gamenum;
3784                     char new_piece[MSG_SIZ];
3785                     started = STARTED_NONE;
3786                     parse[parse_pos] = NULLCHAR;
3787                     if (appData.debugMode)
3788                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3789                                                         parse, currentMove);
3790                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3791                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3792                         if (gameInfo.variant == VariantNormal) {
3793                           /* [HGM] We seem to switch variant during a game!
3794                            * Presumably no holdings were displayed, so we have
3795                            * to move the position two files to the right to
3796                            * create room for them!
3797                            */
3798                           VariantClass newVariant;
3799                           switch(gameInfo.boardWidth) { // base guess on board width
3800                                 case 9:  newVariant = VariantShogi; break;
3801                                 case 10: newVariant = VariantGreat; break;
3802                                 default: newVariant = VariantCrazyhouse; break;
3803                           }
3804                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3805                           /* Get a move list just to see the header, which
3806                              will tell us whether this is really bug or zh */
3807                           if (ics_getting_history == H_FALSE) {
3808                             ics_getting_history = H_REQUESTED;
3809                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3810                             SendToICS(str);
3811                           }
3812                         }
3813                         new_piece[0] = NULLCHAR;
3814                         sscanf(parse, "game %d white [%s black [%s <- %s",
3815                                &gamenum, white_holding, black_holding,
3816                                new_piece);
3817                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3818                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3819                         /* [HGM] copy holdings to board holdings area */
3820                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3821                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3822                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3823 #if ZIPPY
3824                         if (appData.zippyPlay && first.initDone) {
3825                             ZippyHoldings(white_holding, black_holding,
3826                                           new_piece);
3827                         }
3828 #endif /*ZIPPY*/
3829                         if (tinyLayout || smallLayout) {
3830                             char wh[16], bh[16];
3831                             PackHolding(wh, white_holding);
3832                             PackHolding(bh, black_holding);
3833                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3834                                     gameInfo.white, gameInfo.black);
3835                         } else {
3836                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3837                                     gameInfo.white, white_holding,
3838                                     gameInfo.black, black_holding);
3839                         }
3840                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3841                         DrawPosition(FALSE, boards[currentMove]);
3842                         DisplayTitle(str);
3843                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3844                         sscanf(parse, "game %d white [%s black [%s <- %s",
3845                                &gamenum, white_holding, black_holding,
3846                                new_piece);
3847                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3848                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3849                         /* [HGM] copy holdings to partner-board holdings area */
3850                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3851                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3852                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3853                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3854                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3855                       }
3856                     }
3857                     /* Suppress following prompt */
3858                     if (looking_at(buf, &i, "*% ")) {
3859                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3860                         savingComment = FALSE;
3861                         suppressKibitz = 0;
3862                     }
3863                     next_out = i;
3864                 }
3865                 continue;
3866             }
3867
3868             i++;                /* skip unparsed character and loop back */
3869         }
3870
3871         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3872 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3873 //          SendToPlayer(&buf[next_out], i - next_out);
3874             started != STARTED_HOLDINGS && leftover_start > next_out) {
3875             SendToPlayer(&buf[next_out], leftover_start - next_out);
3876             next_out = i;
3877         }
3878
3879         leftover_len = buf_len - leftover_start;
3880         /* if buffer ends with something we couldn't parse,
3881            reparse it after appending the next read */
3882
3883     } else if (count == 0) {
3884         RemoveInputSource(isr);
3885         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3886     } else {
3887         DisplayFatalError(_("Error reading from ICS"), error, 1);
3888     }
3889 }
3890
3891
3892 /* Board style 12 looks like this:
3893
3894    <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
3895
3896  * The "<12> " is stripped before it gets to this routine.  The two
3897  * trailing 0's (flip state and clock ticking) are later addition, and
3898  * some chess servers may not have them, or may have only the first.
3899  * Additional trailing fields may be added in the future.
3900  */
3901
3902 #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"
3903
3904 #define RELATION_OBSERVING_PLAYED    0
3905 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3906 #define RELATION_PLAYING_MYMOVE      1
3907 #define RELATION_PLAYING_NOTMYMOVE  -1
3908 #define RELATION_EXAMINING           2
3909 #define RELATION_ISOLATED_BOARD     -3
3910 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3911
3912 void
3913 ParseBoard12(string)
3914      char *string;
3915 {
3916     GameMode newGameMode;
3917     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3918     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3919     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3920     char to_play, board_chars[200];
3921     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3922     char black[32], white[32];
3923     Board board;
3924     int prevMove = currentMove;
3925     int ticking = 2;
3926     ChessMove moveType;
3927     int fromX, fromY, toX, toY;
3928     char promoChar;
3929     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3930     char *bookHit = NULL; // [HGM] book
3931     Boolean weird = FALSE, reqFlag = FALSE;
3932
3933     fromX = fromY = toX = toY = -1;
3934
3935     newGame = FALSE;
3936
3937     if (appData.debugMode)
3938       fprintf(debugFP, _("Parsing board: %s\n"), string);
3939
3940     move_str[0] = NULLCHAR;
3941     elapsed_time[0] = NULLCHAR;
3942     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3943         int  i = 0, j;
3944         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3945             if(string[i] == ' ') { ranks++; files = 0; }
3946             else files++;
3947             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3948             i++;
3949         }
3950         for(j = 0; j <i; j++) board_chars[j] = string[j];
3951         board_chars[i] = '\0';
3952         string += i + 1;
3953     }
3954     n = sscanf(string, PATTERN, &to_play, &double_push,
3955                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3956                &gamenum, white, black, &relation, &basetime, &increment,
3957                &white_stren, &black_stren, &white_time, &black_time,
3958                &moveNum, str, elapsed_time, move_str, &ics_flip,
3959                &ticking);
3960
3961     if (n < 21) {
3962         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3963         DisplayError(str, 0);
3964         return;
3965     }
3966
3967     /* Convert the move number to internal form */
3968     moveNum = (moveNum - 1) * 2;
3969     if (to_play == 'B') moveNum++;
3970     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3971       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3972                         0, 1);
3973       return;
3974     }
3975
3976     switch (relation) {
3977       case RELATION_OBSERVING_PLAYED:
3978       case RELATION_OBSERVING_STATIC:
3979         if (gamenum == -1) {
3980             /* Old ICC buglet */
3981             relation = RELATION_OBSERVING_STATIC;
3982         }
3983         newGameMode = IcsObserving;
3984         break;
3985       case RELATION_PLAYING_MYMOVE:
3986       case RELATION_PLAYING_NOTMYMOVE:
3987         newGameMode =
3988           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3989             IcsPlayingWhite : IcsPlayingBlack;
3990         break;
3991       case RELATION_EXAMINING:
3992         newGameMode = IcsExamining;
3993         break;
3994       case RELATION_ISOLATED_BOARD:
3995       default:
3996         /* Just display this board.  If user was doing something else,
3997            we will forget about it until the next board comes. */
3998         newGameMode = IcsIdle;
3999         break;
4000       case RELATION_STARTING_POSITION:
4001         newGameMode = gameMode;
4002         break;
4003     }
4004
4005     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4006          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4007       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4008       char *toSqr;
4009       for (k = 0; k < ranks; k++) {
4010         for (j = 0; j < files; j++)
4011           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4012         if(gameInfo.holdingsWidth > 1) {
4013              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4014              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4015         }
4016       }
4017       CopyBoard(partnerBoard, board);
4018       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4019         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4020         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4021       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4022       if(toSqr = strchr(str, '-')) {
4023         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4024         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4025       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4026       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4027       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4028       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4029       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4030       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4031                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4032       DisplayMessage(partnerStatus, "");
4033         partnerBoardValid = TRUE;
4034       return;
4035     }
4036
4037     /* Modify behavior for initial board display on move listing
4038        of wild games.
4039        */
4040     switch (ics_getting_history) {
4041       case H_FALSE:
4042       case H_REQUESTED:
4043         break;
4044       case H_GOT_REQ_HEADER:
4045       case H_GOT_UNREQ_HEADER:
4046         /* This is the initial position of the current game */
4047         gamenum = ics_gamenum;
4048         moveNum = 0;            /* old ICS bug workaround */
4049         if (to_play == 'B') {
4050           startedFromSetupPosition = TRUE;
4051           blackPlaysFirst = TRUE;
4052           moveNum = 1;
4053           if (forwardMostMove == 0) forwardMostMove = 1;
4054           if (backwardMostMove == 0) backwardMostMove = 1;
4055           if (currentMove == 0) currentMove = 1;
4056         }
4057         newGameMode = gameMode;
4058         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4059         break;
4060       case H_GOT_UNWANTED_HEADER:
4061         /* This is an initial board that we don't want */
4062         return;
4063       case H_GETTING_MOVES:
4064         /* Should not happen */
4065         DisplayError(_("Error gathering move list: extra board"), 0);
4066         ics_getting_history = H_FALSE;
4067         return;
4068     }
4069
4070    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4071                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4072      /* [HGM] We seem to have switched variant unexpectedly
4073       * Try to guess new variant from board size
4074       */
4075           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4076           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4077           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4078           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4079           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4080           if(!weird) newVariant = VariantNormal;
4081           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4082           /* Get a move list just to see the header, which
4083              will tell us whether this is really bug or zh */
4084           if (ics_getting_history == H_FALSE) {
4085             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4086             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4087             SendToICS(str);
4088           }
4089     }
4090
4091     /* Take action if this is the first board of a new game, or of a
4092        different game than is currently being displayed.  */
4093     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4094         relation == RELATION_ISOLATED_BOARD) {
4095
4096         /* Forget the old game and get the history (if any) of the new one */
4097         if (gameMode != BeginningOfGame) {
4098           Reset(TRUE, TRUE);
4099         }
4100         newGame = TRUE;
4101         if (appData.autoRaiseBoard) BoardToTop();
4102         prevMove = -3;
4103         if (gamenum == -1) {
4104             newGameMode = IcsIdle;
4105         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4106                    appData.getMoveList && !reqFlag) {
4107             /* Need to get game history */
4108             ics_getting_history = H_REQUESTED;
4109             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4110             SendToICS(str);
4111         }
4112
4113         /* Initially flip the board to have black on the bottom if playing
4114            black or if the ICS flip flag is set, but let the user change
4115            it with the Flip View button. */
4116         flipView = appData.autoFlipView ?
4117           (newGameMode == IcsPlayingBlack) || ics_flip :
4118           appData.flipView;
4119
4120         /* Done with values from previous mode; copy in new ones */
4121         gameMode = newGameMode;
4122         ModeHighlight();
4123         ics_gamenum = gamenum;
4124         if (gamenum == gs_gamenum) {
4125             int klen = strlen(gs_kind);
4126             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4127             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4128             gameInfo.event = StrSave(str);
4129         } else {
4130             gameInfo.event = StrSave("ICS game");
4131         }
4132         gameInfo.site = StrSave(appData.icsHost);
4133         gameInfo.date = PGNDate();
4134         gameInfo.round = StrSave("-");
4135         gameInfo.white = StrSave(white);
4136         gameInfo.black = StrSave(black);
4137         timeControl = basetime * 60 * 1000;
4138         timeControl_2 = 0;
4139         timeIncrement = increment * 1000;
4140         movesPerSession = 0;
4141         gameInfo.timeControl = TimeControlTagValue();
4142         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4143   if (appData.debugMode) {
4144     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4145     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4146     setbuf(debugFP, NULL);
4147   }
4148
4149         gameInfo.outOfBook = NULL;
4150
4151         /* Do we have the ratings? */
4152         if (strcmp(player1Name, white) == 0 &&
4153             strcmp(player2Name, black) == 0) {
4154             if (appData.debugMode)
4155               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4156                       player1Rating, player2Rating);
4157             gameInfo.whiteRating = player1Rating;
4158             gameInfo.blackRating = player2Rating;
4159         } else if (strcmp(player2Name, white) == 0 &&
4160                    strcmp(player1Name, black) == 0) {
4161             if (appData.debugMode)
4162               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4163                       player2Rating, player1Rating);
4164             gameInfo.whiteRating = player2Rating;
4165             gameInfo.blackRating = player1Rating;
4166         }
4167         player1Name[0] = player2Name[0] = NULLCHAR;
4168
4169         /* Silence shouts if requested */
4170         if (appData.quietPlay &&
4171             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4172             SendToICS(ics_prefix);
4173             SendToICS("set shout 0\n");
4174         }
4175     }
4176
4177     /* Deal with midgame name changes */
4178     if (!newGame) {
4179         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4180             if (gameInfo.white) free(gameInfo.white);
4181             gameInfo.white = StrSave(white);
4182         }
4183         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4184             if (gameInfo.black) free(gameInfo.black);
4185             gameInfo.black = StrSave(black);
4186         }
4187     }
4188
4189     /* Throw away game result if anything actually changes in examine mode */
4190     if (gameMode == IcsExamining && !newGame) {
4191         gameInfo.result = GameUnfinished;
4192         if (gameInfo.resultDetails != NULL) {
4193             free(gameInfo.resultDetails);
4194             gameInfo.resultDetails = NULL;
4195         }
4196     }
4197
4198     /* In pausing && IcsExamining mode, we ignore boards coming
4199        in if they are in a different variation than we are. */
4200     if (pauseExamInvalid) return;
4201     if (pausing && gameMode == IcsExamining) {
4202         if (moveNum <= pauseExamForwardMostMove) {
4203             pauseExamInvalid = TRUE;
4204             forwardMostMove = pauseExamForwardMostMove;
4205             return;
4206         }
4207     }
4208
4209   if (appData.debugMode) {
4210     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4211   }
4212     /* Parse the board */
4213     for (k = 0; k < ranks; k++) {
4214       for (j = 0; j < files; j++)
4215         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4216       if(gameInfo.holdingsWidth > 1) {
4217            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4218            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4219       }
4220     }
4221     CopyBoard(boards[moveNum], board);
4222     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4223     if (moveNum == 0) {
4224         startedFromSetupPosition =
4225           !CompareBoards(board, initialPosition);
4226         if(startedFromSetupPosition)
4227             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4228     }
4229
4230     /* [HGM] Set castling rights. Take the outermost Rooks,
4231        to make it also work for FRC opening positions. Note that board12
4232        is really defective for later FRC positions, as it has no way to
4233        indicate which Rook can castle if they are on the same side of King.
4234        For the initial position we grant rights to the outermost Rooks,
4235        and remember thos rights, and we then copy them on positions
4236        later in an FRC game. This means WB might not recognize castlings with
4237        Rooks that have moved back to their original position as illegal,
4238        but in ICS mode that is not its job anyway.
4239     */
4240     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4241     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4242
4243         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4244             if(board[0][i] == WhiteRook) j = i;
4245         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4246         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4247             if(board[0][i] == WhiteRook) j = i;
4248         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4249         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4250             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4251         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4252         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4253             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4254         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4255
4256         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4257         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4258             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4259         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4260             if(board[BOARD_HEIGHT-1][k] == bKing)
4261                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4262         if(gameInfo.variant == VariantTwoKings) {
4263             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4264             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4265             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4266         }
4267     } else { int r;
4268         r = boards[moveNum][CASTLING][0] = initialRights[0];
4269         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4270         r = boards[moveNum][CASTLING][1] = initialRights[1];
4271         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4272         r = boards[moveNum][CASTLING][3] = initialRights[3];
4273         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4274         r = boards[moveNum][CASTLING][4] = initialRights[4];
4275         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4276         /* wildcastle kludge: always assume King has rights */
4277         r = boards[moveNum][CASTLING][2] = initialRights[2];
4278         r = boards[moveNum][CASTLING][5] = initialRights[5];
4279     }
4280     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4281     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4282
4283
4284     if (ics_getting_history == H_GOT_REQ_HEADER ||
4285         ics_getting_history == H_GOT_UNREQ_HEADER) {
4286         /* This was an initial position from a move list, not
4287            the current position */
4288         return;
4289     }
4290
4291     /* Update currentMove and known move number limits */
4292     newMove = newGame || moveNum > forwardMostMove;
4293
4294     if (newGame) {
4295         forwardMostMove = backwardMostMove = currentMove = moveNum;
4296         if (gameMode == IcsExamining && moveNum == 0) {
4297           /* Workaround for ICS limitation: we are not told the wild
4298              type when starting to examine a game.  But if we ask for
4299              the move list, the move list header will tell us */
4300             ics_getting_history = H_REQUESTED;
4301             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4302             SendToICS(str);
4303         }
4304     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4305                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4306 #if ZIPPY
4307         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4308         /* [HGM] applied this also to an engine that is silently watching        */
4309         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4310             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4311             gameInfo.variant == currentlyInitializedVariant) {
4312           takeback = forwardMostMove - moveNum;
4313           for (i = 0; i < takeback; i++) {
4314             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4315             SendToProgram("undo\n", &first);
4316           }
4317         }
4318 #endif
4319
4320         forwardMostMove = moveNum;
4321         if (!pausing || currentMove > forwardMostMove)
4322           currentMove = forwardMostMove;
4323     } else {
4324         /* New part of history that is not contiguous with old part */
4325         if (pausing && gameMode == IcsExamining) {
4326             pauseExamInvalid = TRUE;
4327             forwardMostMove = pauseExamForwardMostMove;
4328             return;
4329         }
4330         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4331 #if ZIPPY
4332             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4333                 // [HGM] when we will receive the move list we now request, it will be
4334                 // fed to the engine from the first move on. So if the engine is not
4335                 // in the initial position now, bring it there.
4336                 InitChessProgram(&first, 0);
4337             }
4338 #endif
4339             ics_getting_history = H_REQUESTED;
4340             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4341             SendToICS(str);
4342         }
4343         forwardMostMove = backwardMostMove = currentMove = moveNum;
4344     }
4345
4346     /* Update the clocks */
4347     if (strchr(elapsed_time, '.')) {
4348       /* Time is in ms */
4349       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4350       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4351     } else {
4352       /* Time is in seconds */
4353       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4354       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4355     }
4356
4357
4358 #if ZIPPY
4359     if (appData.zippyPlay && newGame &&
4360         gameMode != IcsObserving && gameMode != IcsIdle &&
4361         gameMode != IcsExamining)
4362       ZippyFirstBoard(moveNum, basetime, increment);
4363 #endif
4364
4365     /* Put the move on the move list, first converting
4366        to canonical algebraic form. */
4367     if (moveNum > 0) {
4368   if (appData.debugMode) {
4369     if (appData.debugMode) { int f = forwardMostMove;
4370         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4371                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4372                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4373     }
4374     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4375     fprintf(debugFP, "moveNum = %d\n", moveNum);
4376     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4377     setbuf(debugFP, NULL);
4378   }
4379         if (moveNum <= backwardMostMove) {
4380             /* We don't know what the board looked like before
4381                this move.  Punt. */
4382           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4383             strcat(parseList[moveNum - 1], " ");
4384             strcat(parseList[moveNum - 1], elapsed_time);
4385             moveList[moveNum - 1][0] = NULLCHAR;
4386         } else if (strcmp(move_str, "none") == 0) {
4387             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4388             /* Again, we don't know what the board looked like;
4389                this is really the start of the game. */
4390             parseList[moveNum - 1][0] = NULLCHAR;
4391             moveList[moveNum - 1][0] = NULLCHAR;
4392             backwardMostMove = moveNum;
4393             startedFromSetupPosition = TRUE;
4394             fromX = fromY = toX = toY = -1;
4395         } else {
4396           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4397           //                 So we parse the long-algebraic move string in stead of the SAN move
4398           int valid; char buf[MSG_SIZ], *prom;
4399
4400           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4401                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4402           // str looks something like "Q/a1-a2"; kill the slash
4403           if(str[1] == '/')
4404             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4405           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4406           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4407                 strcat(buf, prom); // long move lacks promo specification!
4408           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4409                 if(appData.debugMode)
4410                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4411                 safeStrCpy(move_str, buf, MSG_SIZ);
4412           }
4413           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4414                                 &fromX, &fromY, &toX, &toY, &promoChar)
4415                || ParseOneMove(buf, moveNum - 1, &moveType,
4416                                 &fromX, &fromY, &toX, &toY, &promoChar);
4417           // end of long SAN patch
4418           if (valid) {
4419             (void) CoordsToAlgebraic(boards[moveNum - 1],
4420                                      PosFlags(moveNum - 1),
4421                                      fromY, fromX, toY, toX, promoChar,
4422                                      parseList[moveNum-1]);
4423             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4424               case MT_NONE:
4425               case MT_STALEMATE:
4426               default:
4427                 break;
4428               case MT_CHECK:
4429                 if(gameInfo.variant != VariantShogi)
4430                     strcat(parseList[moveNum - 1], "+");
4431                 break;
4432               case MT_CHECKMATE:
4433               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4434                 strcat(parseList[moveNum - 1], "#");
4435                 break;
4436             }
4437             strcat(parseList[moveNum - 1], " ");
4438             strcat(parseList[moveNum - 1], elapsed_time);
4439             /* currentMoveString is set as a side-effect of ParseOneMove */
4440             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4441             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4442             strcat(moveList[moveNum - 1], "\n");
4443
4444             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4445                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4446               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4447                 ChessSquare old, new = boards[moveNum][k][j];
4448                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4449                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4450                   if(old == new) continue;
4451                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4452                   else if(new == WhiteWazir || new == BlackWazir) {
4453                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4454                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4455                       else boards[moveNum][k][j] = old; // preserve type of Gold
4456                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4457                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4458               }
4459           } else {
4460             /* Move from ICS was illegal!?  Punt. */
4461             if (appData.debugMode) {
4462               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4463               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4464             }
4465             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4466             strcat(parseList[moveNum - 1], " ");
4467             strcat(parseList[moveNum - 1], elapsed_time);
4468             moveList[moveNum - 1][0] = NULLCHAR;
4469             fromX = fromY = toX = toY = -1;
4470           }
4471         }
4472   if (appData.debugMode) {
4473     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4474     setbuf(debugFP, NULL);
4475   }
4476
4477 #if ZIPPY
4478         /* Send move to chess program (BEFORE animating it). */
4479         if (appData.zippyPlay && !newGame && newMove &&
4480            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4481
4482             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4483                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4484                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4485                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4486                             move_str);
4487                     DisplayError(str, 0);
4488                 } else {
4489                     if (first.sendTime) {
4490                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4491                     }
4492                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4493                     if (firstMove && !bookHit) {
4494                         firstMove = FALSE;
4495                         if (first.useColors) {
4496                           SendToProgram(gameMode == IcsPlayingWhite ?
4497                                         "white\ngo\n" :
4498                                         "black\ngo\n", &first);
4499                         } else {
4500                           SendToProgram("go\n", &first);
4501                         }
4502                         first.maybeThinking = TRUE;
4503                     }
4504                 }
4505             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4506               if (moveList[moveNum - 1][0] == NULLCHAR) {
4507                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4508                 DisplayError(str, 0);
4509               } else {
4510                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4511                 SendMoveToProgram(moveNum - 1, &first);
4512               }
4513             }
4514         }
4515 #endif
4516     }
4517
4518     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4519         /* If move comes from a remote source, animate it.  If it
4520            isn't remote, it will have already been animated. */
4521         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4522             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4523         }
4524         if (!pausing && appData.highlightLastMove) {
4525             SetHighlights(fromX, fromY, toX, toY);
4526         }
4527     }
4528
4529     /* Start the clocks */
4530     whiteFlag = blackFlag = FALSE;
4531     appData.clockMode = !(basetime == 0 && increment == 0);
4532     if (ticking == 0) {
4533       ics_clock_paused = TRUE;
4534       StopClocks();
4535     } else if (ticking == 1) {
4536       ics_clock_paused = FALSE;
4537     }
4538     if (gameMode == IcsIdle ||
4539         relation == RELATION_OBSERVING_STATIC ||
4540         relation == RELATION_EXAMINING ||
4541         ics_clock_paused)
4542       DisplayBothClocks();
4543     else
4544       StartClocks();
4545
4546     /* Display opponents and material strengths */
4547     if (gameInfo.variant != VariantBughouse &&
4548         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4549         if (tinyLayout || smallLayout) {
4550             if(gameInfo.variant == VariantNormal)
4551               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4552                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4553                     basetime, increment);
4554             else
4555               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4556                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4557                     basetime, increment, (int) gameInfo.variant);
4558         } else {
4559             if(gameInfo.variant == VariantNormal)
4560               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4561                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4562                     basetime, increment);
4563             else
4564               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4565                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4566                     basetime, increment, VariantName(gameInfo.variant));
4567         }
4568         DisplayTitle(str);
4569   if (appData.debugMode) {
4570     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4571   }
4572     }
4573
4574
4575     /* Display the board */
4576     if (!pausing && !appData.noGUI) {
4577
4578       if (appData.premove)
4579           if (!gotPremove ||
4580              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4581              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4582               ClearPremoveHighlights();
4583
4584       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4585         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4586       DrawPosition(j, boards[currentMove]);
4587
4588       DisplayMove(moveNum - 1);
4589       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4590             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4591               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4592         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4593       }
4594     }
4595
4596     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4597 #if ZIPPY
4598     if(bookHit) { // [HGM] book: simulate book reply
4599         static char bookMove[MSG_SIZ]; // a bit generous?
4600
4601         programStats.nodes = programStats.depth = programStats.time =
4602         programStats.score = programStats.got_only_move = 0;
4603         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4604
4605         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4606         strcat(bookMove, bookHit);
4607         HandleMachineMove(bookMove, &first);
4608     }
4609 #endif
4610 }
4611
4612 void
4613 GetMoveListEvent()
4614 {
4615     char buf[MSG_SIZ];
4616     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4617         ics_getting_history = H_REQUESTED;
4618         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4619         SendToICS(buf);
4620     }
4621 }
4622
4623 void
4624 AnalysisPeriodicEvent(force)
4625      int force;
4626 {
4627     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4628          && !force) || !appData.periodicUpdates)
4629       return;
4630
4631     /* Send . command to Crafty to collect stats */
4632     SendToProgram(".\n", &first);
4633
4634     /* Don't send another until we get a response (this makes
4635        us stop sending to old Crafty's which don't understand
4636        the "." command (sending illegal cmds resets node count & time,
4637        which looks bad)) */
4638     programStats.ok_to_send = 0;
4639 }
4640
4641 void ics_update_width(new_width)
4642         int new_width;
4643 {
4644         ics_printf("set width %d\n", new_width);
4645 }
4646
4647 void
4648 SendMoveToProgram(moveNum, cps)
4649      int moveNum;
4650      ChessProgramState *cps;
4651 {
4652     char buf[MSG_SIZ];
4653
4654     if (cps->useUsermove) {
4655       SendToProgram("usermove ", cps);
4656     }
4657     if (cps->useSAN) {
4658       char *space;
4659       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4660         int len = space - parseList[moveNum];
4661         memcpy(buf, parseList[moveNum], len);
4662         buf[len++] = '\n';
4663         buf[len] = NULLCHAR;
4664       } else {
4665         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4666       }
4667       SendToProgram(buf, cps);
4668     } else {
4669       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4670         AlphaRank(moveList[moveNum], 4);
4671         SendToProgram(moveList[moveNum], cps);
4672         AlphaRank(moveList[moveNum], 4); // and back
4673       } else
4674       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4675        * the engine. It would be nice to have a better way to identify castle
4676        * moves here. */
4677       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4678                                                                          && cps->useOOCastle) {
4679         int fromX = moveList[moveNum][0] - AAA;
4680         int fromY = moveList[moveNum][1] - ONE;
4681         int toX = moveList[moveNum][2] - AAA;
4682         int toY = moveList[moveNum][3] - ONE;
4683         if((boards[moveNum][fromY][fromX] == WhiteKing
4684             && boards[moveNum][toY][toX] == WhiteRook)
4685            || (boards[moveNum][fromY][fromX] == BlackKing
4686                && boards[moveNum][toY][toX] == BlackRook)) {
4687           if(toX > fromX) SendToProgram("O-O\n", cps);
4688           else SendToProgram("O-O-O\n", cps);
4689         }
4690         else SendToProgram(moveList[moveNum], cps);
4691       }
4692       else SendToProgram(moveList[moveNum], cps);
4693       /* End of additions by Tord */
4694     }
4695
4696     /* [HGM] setting up the opening has brought engine in force mode! */
4697     /*       Send 'go' if we are in a mode where machine should play. */
4698     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4699         (gameMode == TwoMachinesPlay   ||
4700 #if ZIPPY
4701          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4702 #endif
4703          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4704         SendToProgram("go\n", cps);
4705   if (appData.debugMode) {
4706     fprintf(debugFP, "(extra)\n");
4707   }
4708     }
4709     setboardSpoiledMachineBlack = 0;
4710 }
4711
4712 void
4713 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4714      ChessMove moveType;
4715      int fromX, fromY, toX, toY;
4716      char promoChar;
4717 {
4718     char user_move[MSG_SIZ];
4719
4720     switch (moveType) {
4721       default:
4722         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4723                 (int)moveType, fromX, fromY, toX, toY);
4724         DisplayError(user_move + strlen("say "), 0);
4725         break;
4726       case WhiteKingSideCastle:
4727       case BlackKingSideCastle:
4728       case WhiteQueenSideCastleWild:
4729       case BlackQueenSideCastleWild:
4730       /* PUSH Fabien */
4731       case WhiteHSideCastleFR:
4732       case BlackHSideCastleFR:
4733       /* POP Fabien */
4734         snprintf(user_move, MSG_SIZ, "o-o\n");
4735         break;
4736       case WhiteQueenSideCastle:
4737       case BlackQueenSideCastle:
4738       case WhiteKingSideCastleWild:
4739       case BlackKingSideCastleWild:
4740       /* PUSH Fabien */
4741       case WhiteASideCastleFR:
4742       case BlackASideCastleFR:
4743       /* POP Fabien */
4744         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4745         break;
4746       case WhiteNonPromotion:
4747       case BlackNonPromotion:
4748         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4749         break;
4750       case WhitePromotion:
4751       case BlackPromotion:
4752         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4753           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4754                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4755                 PieceToChar(WhiteFerz));
4756         else if(gameInfo.variant == VariantGreat)
4757           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4758                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4759                 PieceToChar(WhiteMan));
4760         else
4761           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4762                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4763                 promoChar);
4764         break;
4765       case WhiteDrop:
4766       case BlackDrop:
4767       drop:
4768         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4769                  ToUpper(PieceToChar((ChessSquare) fromX)),
4770                  AAA + toX, ONE + toY);
4771         break;
4772       case IllegalMove:  /* could be a variant we don't quite understand */
4773         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4774       case NormalMove:
4775       case WhiteCapturesEnPassant:
4776       case BlackCapturesEnPassant:
4777         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4778                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4779         break;
4780     }
4781     SendToICS(user_move);
4782     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4783         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4784 }
4785
4786 void
4787 UploadGameEvent()
4788 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4789     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4790     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4791     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4792         DisplayError("You cannot do this while you are playing or observing", 0);
4793         return;
4794     }
4795     if(gameMode != IcsExamining) { // is this ever not the case?
4796         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4797
4798         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4799           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4800         } else { // on FICS we must first go to general examine mode
4801           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4802         }
4803         if(gameInfo.variant != VariantNormal) {
4804             // try figure out wild number, as xboard names are not always valid on ICS
4805             for(i=1; i<=36; i++) {
4806               snprintf(buf, MSG_SIZ, "wild/%d", i);
4807                 if(StringToVariant(buf) == gameInfo.variant) break;
4808             }
4809             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4810             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4811             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4812         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4813         SendToICS(ics_prefix);
4814         SendToICS(buf);
4815         if(startedFromSetupPosition || backwardMostMove != 0) {
4816           fen = PositionToFEN(backwardMostMove, NULL);
4817           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4818             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4819             SendToICS(buf);
4820           } else { // FICS: everything has to set by separate bsetup commands
4821             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4822             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4823             SendToICS(buf);
4824             if(!WhiteOnMove(backwardMostMove)) {
4825                 SendToICS("bsetup tomove black\n");
4826             }
4827             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4828             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4829             SendToICS(buf);
4830             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4831             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4832             SendToICS(buf);
4833             i = boards[backwardMostMove][EP_STATUS];
4834             if(i >= 0) { // set e.p.
4835               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4836                 SendToICS(buf);
4837             }
4838             bsetup++;
4839           }
4840         }
4841       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4842             SendToICS("bsetup done\n"); // switch to normal examining.
4843     }
4844     for(i = backwardMostMove; i<last; i++) {
4845         char buf[20];
4846         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4847         SendToICS(buf);
4848     }
4849     SendToICS(ics_prefix);
4850     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4851 }
4852
4853 void
4854 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4855      int rf, ff, rt, ft;
4856      char promoChar;
4857      char move[7];
4858 {
4859     if (rf == DROP_RANK) {
4860       sprintf(move, "%c@%c%c\n",
4861                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4862     } else {
4863         if (promoChar == 'x' || promoChar == NULLCHAR) {
4864           sprintf(move, "%c%c%c%c\n",
4865                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4866         } else {
4867             sprintf(move, "%c%c%c%c%c\n",
4868                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4869         }
4870     }
4871 }
4872
4873 void
4874 ProcessICSInitScript(f)
4875      FILE *f;
4876 {
4877     char buf[MSG_SIZ];
4878
4879     while (fgets(buf, MSG_SIZ, f)) {
4880         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4881     }
4882
4883     fclose(f);
4884 }
4885
4886
4887 static int lastX, lastY, selectFlag, dragging;
4888
4889 void
4890 Sweep(int step)
4891 {
4892     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4893     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4894     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4895     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4896     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4897     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4898     do {
4899         promoSweep -= step;
4900         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4901         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4902         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4903         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4904         if(!step) step = 1;
4905     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4906             appData.testLegality && (promoSweep == king ||
4907             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4908     ChangeDragPiece(promoSweep);
4909 }
4910
4911 int PromoScroll(int x, int y)
4912 {
4913   int step = 0;
4914
4915   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4916   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4917   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4918   if(!step) return FALSE;
4919   lastX = x; lastY = y;
4920   if((promoSweep < BlackPawn) == flipView) step = -step;
4921   if(step > 0) selectFlag = 1;
4922   if(!selectFlag) Sweep(step);
4923   return FALSE;
4924 }
4925
4926 void
4927 NextPiece(int step)
4928 {
4929     ChessSquare piece = boards[currentMove][toY][toX];
4930     do {
4931         pieceSweep -= step;
4932         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4933         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4934         if(!step) step = -1;
4935     } while(PieceToChar(pieceSweep) == '.');
4936     boards[currentMove][toY][toX] = pieceSweep;
4937     DrawPosition(FALSE, boards[currentMove]);
4938     boards[currentMove][toY][toX] = piece;
4939 }
4940 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4941 void
4942 AlphaRank(char *move, int n)
4943 {
4944 //    char *p = move, c; int x, y;
4945
4946     if (appData.debugMode) {
4947         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4948     }
4949
4950     if(move[1]=='*' &&
4951        move[2]>='0' && move[2]<='9' &&
4952        move[3]>='a' && move[3]<='x'    ) {
4953         move[1] = '@';
4954         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4955         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4956     } else
4957     if(move[0]>='0' && move[0]<='9' &&
4958        move[1]>='a' && move[1]<='x' &&
4959        move[2]>='0' && move[2]<='9' &&
4960        move[3]>='a' && move[3]<='x'    ) {
4961         /* input move, Shogi -> normal */
4962         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4963         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4964         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4965         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4966     } else
4967     if(move[1]=='@' &&
4968        move[3]>='0' && move[3]<='9' &&
4969        move[2]>='a' && move[2]<='x'    ) {
4970         move[1] = '*';
4971         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4972         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4973     } else
4974     if(
4975        move[0]>='a' && move[0]<='x' &&
4976        move[3]>='0' && move[3]<='9' &&
4977        move[2]>='a' && move[2]<='x'    ) {
4978          /* output move, normal -> Shogi */
4979         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4980         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4981         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4982         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4983         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4984     }
4985     if (appData.debugMode) {
4986         fprintf(debugFP, "   out = '%s'\n", move);
4987     }
4988 }
4989
4990 char yy_textstr[8000];
4991
4992 /* Parser for moves from gnuchess, ICS, or user typein box */
4993 Boolean
4994 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4995      char *move;
4996      int moveNum;
4997      ChessMove *moveType;
4998      int *fromX, *fromY, *toX, *toY;
4999      char *promoChar;
5000 {
5001     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5002
5003     switch (*moveType) {
5004       case WhitePromotion:
5005       case BlackPromotion:
5006       case WhiteNonPromotion:
5007       case BlackNonPromotion:
5008       case NormalMove:
5009       case WhiteCapturesEnPassant:
5010       case BlackCapturesEnPassant:
5011       case WhiteKingSideCastle:
5012       case WhiteQueenSideCastle:
5013       case BlackKingSideCastle:
5014       case BlackQueenSideCastle:
5015       case WhiteKingSideCastleWild:
5016       case WhiteQueenSideCastleWild:
5017       case BlackKingSideCastleWild:
5018       case BlackQueenSideCastleWild:
5019       /* Code added by Tord: */
5020       case WhiteHSideCastleFR:
5021       case WhiteASideCastleFR:
5022       case BlackHSideCastleFR:
5023       case BlackASideCastleFR:
5024       /* End of code added by Tord */
5025       case IllegalMove:         /* bug or odd chess variant */
5026         *fromX = currentMoveString[0] - AAA;
5027         *fromY = currentMoveString[1] - ONE;
5028         *toX = currentMoveString[2] - AAA;
5029         *toY = currentMoveString[3] - ONE;
5030         *promoChar = currentMoveString[4];
5031         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5032             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5033     if (appData.debugMode) {
5034         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5035     }
5036             *fromX = *fromY = *toX = *toY = 0;
5037             return FALSE;
5038         }
5039         if (appData.testLegality) {
5040           return (*moveType != IllegalMove);
5041         } else {
5042           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5043                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5044         }
5045
5046       case WhiteDrop:
5047       case BlackDrop:
5048         *fromX = *moveType == WhiteDrop ?
5049           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5050           (int) CharToPiece(ToLower(currentMoveString[0]));
5051         *fromY = DROP_RANK;
5052         *toX = currentMoveString[2] - AAA;
5053         *toY = currentMoveString[3] - ONE;
5054         *promoChar = NULLCHAR;
5055         return TRUE;
5056
5057       case AmbiguousMove:
5058       case ImpossibleMove:
5059       case EndOfFile:
5060       case ElapsedTime:
5061       case Comment:
5062       case PGNTag:
5063       case NAG:
5064       case WhiteWins:
5065       case BlackWins:
5066       case GameIsDrawn:
5067       default:
5068     if (appData.debugMode) {
5069         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5070     }
5071         /* bug? */
5072         *fromX = *fromY = *toX = *toY = 0;
5073         *promoChar = NULLCHAR;
5074         return FALSE;
5075     }
5076 }
5077
5078
5079 void
5080 ParsePV(char *pv, Boolean storeComments)
5081 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5082   int fromX, fromY, toX, toY; char promoChar;
5083   ChessMove moveType;
5084   Boolean valid;
5085   int nr = 0;
5086
5087   endPV = forwardMostMove;
5088   do {
5089     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5090     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5091     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5092 if(appData.debugMode){
5093 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);
5094 }
5095     if(!valid && nr == 0 &&
5096        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5097         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5098         // Hande case where played move is different from leading PV move
5099         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5100         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5101         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5102         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5103           endPV += 2; // if position different, keep this
5104           moveList[endPV-1][0] = fromX + AAA;
5105           moveList[endPV-1][1] = fromY + ONE;
5106           moveList[endPV-1][2] = toX + AAA;
5107           moveList[endPV-1][3] = toY + ONE;
5108           parseList[endPV-1][0] = NULLCHAR;
5109           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5110         }
5111       }
5112     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5113     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5114     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5115     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5116         valid++; // allow comments in PV
5117         continue;
5118     }
5119     nr++;
5120     if(endPV+1 > framePtr) break; // no space, truncate
5121     if(!valid) break;
5122     endPV++;
5123     CopyBoard(boards[endPV], boards[endPV-1]);
5124     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5125     moveList[endPV-1][0] = fromX + AAA;
5126     moveList[endPV-1][1] = fromY + ONE;
5127     moveList[endPV-1][2] = toX + AAA;
5128     moveList[endPV-1][3] = toY + ONE;
5129     moveList[endPV-1][4] = promoChar;
5130     moveList[endPV-1][5] = NULLCHAR;
5131     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5132     if(storeComments)
5133         CoordsToAlgebraic(boards[endPV - 1],
5134                              PosFlags(endPV - 1),
5135                              fromY, fromX, toY, toX, promoChar,
5136                              parseList[endPV - 1]);
5137     else
5138         parseList[endPV-1][0] = NULLCHAR;
5139   } while(valid);
5140   currentMove = endPV;
5141   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5142   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5143                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5144   DrawPosition(TRUE, boards[currentMove]);
5145 }
5146
5147 Boolean
5148 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5149 {
5150         int startPV;
5151         char *p;
5152
5153         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5154         lastX = x; lastY = y;
5155         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5156         startPV = index;
5157         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5158         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5159         index = startPV;
5160         do{ while(buf[index] && buf[index] != '\n') index++;
5161         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5162         buf[index] = 0;
5163         ParsePV(buf+startPV, FALSE);
5164         *start = startPV; *end = index-1;
5165         return TRUE;
5166 }
5167
5168 Boolean
5169 LoadPV(int x, int y)
5170 { // called on right mouse click to load PV
5171   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5172   lastX = x; lastY = y;
5173   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5174   return TRUE;
5175 }
5176
5177 void
5178 UnLoadPV()
5179 {
5180   if(endPV < 0) return;
5181   endPV = -1;
5182   currentMove = forwardMostMove;
5183   ClearPremoveHighlights();
5184   DrawPosition(TRUE, boards[currentMove]);
5185 }
5186
5187 void
5188 MovePV(int x, int y, int h)
5189 { // step through PV based on mouse coordinates (called on mouse move)
5190   int margin = h>>3, step = 0;
5191
5192   // we must somehow check if right button is still down (might be released off board!)
5193   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5194   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5195   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5196   if(!step) return;
5197   lastX = x; lastY = y;
5198
5199   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5200   if(endPV < 0) return;
5201   if(y < margin) step = 1; else
5202   if(y > h - margin) step = -1;
5203   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5204   currentMove += step;
5205   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5206   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5207                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5208   DrawPosition(FALSE, boards[currentMove]);
5209 }
5210
5211
5212 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5213 // All positions will have equal probability, but the current method will not provide a unique
5214 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5215 #define DARK 1
5216 #define LITE 2
5217 #define ANY 3
5218
5219 int squaresLeft[4];
5220 int piecesLeft[(int)BlackPawn];
5221 int seed, nrOfShuffles;
5222
5223 void GetPositionNumber()
5224 {       // sets global variable seed
5225         int i;
5226
5227         seed = appData.defaultFrcPosition;
5228         if(seed < 0) { // randomize based on time for negative FRC position numbers
5229                 for(i=0; i<50; i++) seed += random();
5230                 seed = random() ^ random() >> 8 ^ random() << 8;
5231                 if(seed<0) seed = -seed;
5232         }
5233 }
5234
5235 int put(Board board, int pieceType, int rank, int n, int shade)
5236 // put the piece on the (n-1)-th empty squares of the given shade
5237 {
5238         int i;
5239
5240         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5241                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5242                         board[rank][i] = (ChessSquare) pieceType;
5243                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5244                         squaresLeft[ANY]--;
5245                         piecesLeft[pieceType]--;
5246                         return i;
5247                 }
5248         }
5249         return -1;
5250 }
5251
5252
5253 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5254 // calculate where the next piece goes, (any empty square), and put it there
5255 {
5256         int i;
5257
5258         i = seed % squaresLeft[shade];
5259         nrOfShuffles *= squaresLeft[shade];
5260         seed /= squaresLeft[shade];
5261         put(board, pieceType, rank, i, shade);
5262 }
5263
5264 void AddTwoPieces(Board board, int pieceType, int rank)
5265 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5266 {
5267         int i, n=squaresLeft[ANY], j=n-1, k;
5268
5269         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5270         i = seed % k;  // pick one
5271         nrOfShuffles *= k;
5272         seed /= k;
5273         while(i >= j) i -= j--;
5274         j = n - 1 - j; i += j;
5275         put(board, pieceType, rank, j, ANY);
5276         put(board, pieceType, rank, i, ANY);
5277 }
5278
5279 void SetUpShuffle(Board board, int number)
5280 {
5281         int i, p, first=1;
5282
5283         GetPositionNumber(); nrOfShuffles = 1;
5284
5285         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5286         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5287         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5288
5289         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5290
5291         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5292             p = (int) board[0][i];
5293             if(p < (int) BlackPawn) piecesLeft[p] ++;
5294             board[0][i] = EmptySquare;
5295         }
5296
5297         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5298             // shuffles restricted to allow normal castling put KRR first
5299             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5300                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5301             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5302                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5303             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5304                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5305             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5306                 put(board, WhiteRook, 0, 0, ANY);
5307             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5308         }
5309
5310         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5311             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5312             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5313                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5314                 while(piecesLeft[p] >= 2) {
5315                     AddOnePiece(board, p, 0, LITE);
5316                     AddOnePiece(board, p, 0, DARK);
5317                 }
5318                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5319             }
5320
5321         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5322             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5323             // but we leave King and Rooks for last, to possibly obey FRC restriction
5324             if(p == (int)WhiteRook) continue;
5325             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5326             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5327         }
5328
5329         // now everything is placed, except perhaps King (Unicorn) and Rooks
5330
5331         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5332             // Last King gets castling rights
5333             while(piecesLeft[(int)WhiteUnicorn]) {
5334                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5335                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5336             }
5337
5338             while(piecesLeft[(int)WhiteKing]) {
5339                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5340                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5341             }
5342
5343
5344         } else {
5345             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5346             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5347         }
5348
5349         // Only Rooks can be left; simply place them all
5350         while(piecesLeft[(int)WhiteRook]) {
5351                 i = put(board, WhiteRook, 0, 0, ANY);
5352                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5353                         if(first) {
5354                                 first=0;
5355                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5356                         }
5357                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5358                 }
5359         }
5360         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5361             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5362         }
5363
5364         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5365 }
5366
5367 int SetCharTable( char *table, const char * map )
5368 /* [HGM] moved here from winboard.c because of its general usefulness */
5369 /*       Basically a safe strcpy that uses the last character as King */
5370 {
5371     int result = FALSE; int NrPieces;
5372
5373     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5374                     && NrPieces >= 12 && !(NrPieces&1)) {
5375         int i; /* [HGM] Accept even length from 12 to 34 */
5376
5377         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5378         for( i=0; i<NrPieces/2-1; i++ ) {
5379             table[i] = map[i];
5380             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5381         }
5382         table[(int) WhiteKing]  = map[NrPieces/2-1];
5383         table[(int) BlackKing]  = map[NrPieces-1];
5384
5385         result = TRUE;
5386     }
5387
5388     return result;
5389 }
5390
5391 void Prelude(Board board)
5392 {       // [HGM] superchess: random selection of exo-pieces
5393         int i, j, k; ChessSquare p;
5394         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5395
5396         GetPositionNumber(); // use FRC position number
5397
5398         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5399             SetCharTable(pieceToChar, appData.pieceToCharTable);
5400             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5401                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5402         }
5403
5404         j = seed%4;                 seed /= 4;
5405         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5406         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5407         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5408         j = seed%3 + (seed%3 >= j); seed /= 3;
5409         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5410         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5411         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5412         j = seed%3;                 seed /= 3;
5413         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5414         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5415         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5416         j = seed%2 + (seed%2 >= j); seed /= 2;
5417         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5418         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5419         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5420         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5421         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5422         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5423         put(board, exoPieces[0],    0, 0, ANY);
5424         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5425 }
5426
5427 void
5428 InitPosition(redraw)
5429      int redraw;
5430 {
5431     ChessSquare (* pieces)[BOARD_FILES];
5432     int i, j, pawnRow, overrule,
5433     oldx = gameInfo.boardWidth,
5434     oldy = gameInfo.boardHeight,
5435     oldh = gameInfo.holdingsWidth;
5436     static int oldv;
5437
5438     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5439
5440     /* [AS] Initialize pv info list [HGM] and game status */
5441     {
5442         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5443             pvInfoList[i].depth = 0;
5444             boards[i][EP_STATUS] = EP_NONE;
5445             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5446         }
5447
5448         initialRulePlies = 0; /* 50-move counter start */
5449
5450         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5451         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5452     }
5453
5454
5455     /* [HGM] logic here is completely changed. In stead of full positions */
5456     /* the initialized data only consist of the two backranks. The switch */
5457     /* selects which one we will use, which is than copied to the Board   */
5458     /* initialPosition, which for the rest is initialized by Pawns and    */
5459     /* empty squares. This initial position is then copied to boards[0],  */
5460     /* possibly after shuffling, so that it remains available.            */
5461
5462     gameInfo.holdingsWidth = 0; /* default board sizes */
5463     gameInfo.boardWidth    = 8;
5464     gameInfo.boardHeight   = 8;
5465     gameInfo.holdingsSize  = 0;
5466     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5467     for(i=0; i<BOARD_FILES-2; i++)
5468       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5469     initialPosition[EP_STATUS] = EP_NONE;
5470     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5471     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5472          SetCharTable(pieceNickName, appData.pieceNickNames);
5473     else SetCharTable(pieceNickName, "............");
5474     pieces = FIDEArray;
5475
5476     switch (gameInfo.variant) {
5477     case VariantFischeRandom:
5478       shuffleOpenings = TRUE;
5479     default:
5480       break;
5481     case VariantShatranj:
5482       pieces = ShatranjArray;
5483       nrCastlingRights = 0;
5484       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5485       break;
5486     case VariantMakruk:
5487       pieces = makrukArray;
5488       nrCastlingRights = 0;
5489       startedFromSetupPosition = TRUE;
5490       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5491       break;
5492     case VariantTwoKings:
5493       pieces = twoKingsArray;
5494       break;
5495     case VariantCapaRandom:
5496       shuffleOpenings = TRUE;
5497     case VariantCapablanca:
5498       pieces = CapablancaArray;
5499       gameInfo.boardWidth = 10;
5500       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5501       break;
5502     case VariantGothic:
5503       pieces = GothicArray;
5504       gameInfo.boardWidth = 10;
5505       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5506       break;
5507     case VariantSChess:
5508       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5509       gameInfo.holdingsSize = 7;
5510       break;
5511     case VariantJanus:
5512       pieces = JanusArray;
5513       gameInfo.boardWidth = 10;
5514       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5515       nrCastlingRights = 6;
5516         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5517         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5518         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5519         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5520         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5521         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5522       break;
5523     case VariantFalcon:
5524       pieces = FalconArray;
5525       gameInfo.boardWidth = 10;
5526       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5527       break;
5528     case VariantXiangqi:
5529       pieces = XiangqiArray;
5530       gameInfo.boardWidth  = 9;
5531       gameInfo.boardHeight = 10;
5532       nrCastlingRights = 0;
5533       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5534       break;
5535     case VariantShogi:
5536       pieces = ShogiArray;
5537       gameInfo.boardWidth  = 9;
5538       gameInfo.boardHeight = 9;
5539       gameInfo.holdingsSize = 7;
5540       nrCastlingRights = 0;
5541       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5542       break;
5543     case VariantCourier:
5544       pieces = CourierArray;
5545       gameInfo.boardWidth  = 12;
5546       nrCastlingRights = 0;
5547       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5548       break;
5549     case VariantKnightmate:
5550       pieces = KnightmateArray;
5551       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5552       break;
5553     case VariantSpartan:
5554       pieces = SpartanArray;
5555       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5556       break;
5557     case VariantFairy:
5558       pieces = fairyArray;
5559       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5560       break;
5561     case VariantGreat:
5562       pieces = GreatArray;
5563       gameInfo.boardWidth = 10;
5564       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5565       gameInfo.holdingsSize = 8;
5566       break;
5567     case VariantSuper:
5568       pieces = FIDEArray;
5569       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5570       gameInfo.holdingsSize = 8;
5571       startedFromSetupPosition = TRUE;
5572       break;
5573     case VariantCrazyhouse:
5574     case VariantBughouse:
5575       pieces = FIDEArray;
5576       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5577       gameInfo.holdingsSize = 5;
5578       break;
5579     case VariantWildCastle:
5580       pieces = FIDEArray;
5581       /* !!?shuffle with kings guaranteed to be on d or e file */
5582       shuffleOpenings = 1;
5583       break;
5584     case VariantNoCastle:
5585       pieces = FIDEArray;
5586       nrCastlingRights = 0;
5587       /* !!?unconstrained back-rank shuffle */
5588       shuffleOpenings = 1;
5589       break;
5590     }
5591
5592     overrule = 0;
5593     if(appData.NrFiles >= 0) {
5594         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5595         gameInfo.boardWidth = appData.NrFiles;
5596     }
5597     if(appData.NrRanks >= 0) {
5598         gameInfo.boardHeight = appData.NrRanks;
5599     }
5600     if(appData.holdingsSize >= 0) {
5601         i = appData.holdingsSize;
5602         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5603         gameInfo.holdingsSize = i;
5604     }
5605     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5606     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5607         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5608
5609     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5610     if(pawnRow < 1) pawnRow = 1;
5611     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5612
5613     /* User pieceToChar list overrules defaults */
5614     if(appData.pieceToCharTable != NULL)
5615         SetCharTable(pieceToChar, appData.pieceToCharTable);
5616
5617     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5618
5619         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5620             s = (ChessSquare) 0; /* account holding counts in guard band */
5621         for( i=0; i<BOARD_HEIGHT; i++ )
5622             initialPosition[i][j] = s;
5623
5624         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5625         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5626         initialPosition[pawnRow][j] = WhitePawn;
5627         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5628         if(gameInfo.variant == VariantXiangqi) {
5629             if(j&1) {
5630                 initialPosition[pawnRow][j] =
5631                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5632                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5633                    initialPosition[2][j] = WhiteCannon;
5634                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5635                 }
5636             }
5637         }
5638         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5639     }
5640     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5641
5642             j=BOARD_LEFT+1;
5643             initialPosition[1][j] = WhiteBishop;
5644             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5645             j=BOARD_RGHT-2;
5646             initialPosition[1][j] = WhiteRook;
5647             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5648     }
5649
5650     if( nrCastlingRights == -1) {
5651         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5652         /*       This sets default castling rights from none to normal corners   */
5653         /* Variants with other castling rights must set them themselves above    */
5654         nrCastlingRights = 6;
5655
5656         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5657         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5658         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5659         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5660         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5661         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5662      }
5663
5664      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5665      if(gameInfo.variant == VariantGreat) { // promotion commoners
5666         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5667         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5668         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5669         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5670      }
5671      if( gameInfo.variant == VariantSChess ) {
5672       initialPosition[1][0] = BlackMarshall;
5673       initialPosition[2][0] = BlackAngel;
5674       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5675       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5676       initialPosition[1][1] = initialPosition[2][1] = 
5677       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5678      }
5679   if (appData.debugMode) {
5680     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5681   }
5682     if(shuffleOpenings) {
5683         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5684         startedFromSetupPosition = TRUE;
5685     }
5686     if(startedFromPositionFile) {
5687       /* [HGM] loadPos: use PositionFile for every new game */
5688       CopyBoard(initialPosition, filePosition);
5689       for(i=0; i<nrCastlingRights; i++)
5690           initialRights[i] = filePosition[CASTLING][i];
5691       startedFromSetupPosition = TRUE;
5692     }
5693
5694     CopyBoard(boards[0], initialPosition);
5695
5696     if(oldx != gameInfo.boardWidth ||
5697        oldy != gameInfo.boardHeight ||
5698        oldv != gameInfo.variant ||
5699        oldh != gameInfo.holdingsWidth
5700                                          )
5701             InitDrawingSizes(-2 ,0);
5702
5703     oldv = gameInfo.variant;
5704     if (redraw)
5705       DrawPosition(TRUE, boards[currentMove]);
5706 }
5707
5708 void
5709 SendBoard(cps, moveNum)
5710      ChessProgramState *cps;
5711      int moveNum;
5712 {
5713     char message[MSG_SIZ];
5714
5715     if (cps->useSetboard) {
5716       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5717       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5718       SendToProgram(message, cps);
5719       free(fen);
5720
5721     } else {
5722       ChessSquare *bp;
5723       int i, j;
5724       /* Kludge to set black to move, avoiding the troublesome and now
5725        * deprecated "black" command.
5726        */
5727       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5728         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5729
5730       SendToProgram("edit\n", cps);
5731       SendToProgram("#\n", cps);
5732       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5733         bp = &boards[moveNum][i][BOARD_LEFT];
5734         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5735           if ((int) *bp < (int) BlackPawn) {
5736             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5737                     AAA + j, ONE + i);
5738             if(message[0] == '+' || message[0] == '~') {
5739               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5740                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5741                         AAA + j, ONE + i);
5742             }
5743             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5744                 message[1] = BOARD_RGHT   - 1 - j + '1';
5745                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5746             }
5747             SendToProgram(message, cps);
5748           }
5749         }
5750       }
5751
5752       SendToProgram("c\n", cps);
5753       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5754         bp = &boards[moveNum][i][BOARD_LEFT];
5755         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5756           if (((int) *bp != (int) EmptySquare)
5757               && ((int) *bp >= (int) BlackPawn)) {
5758             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5759                     AAA + j, ONE + i);
5760             if(message[0] == '+' || message[0] == '~') {
5761               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5762                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5763                         AAA + j, ONE + i);
5764             }
5765             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5766                 message[1] = BOARD_RGHT   - 1 - j + '1';
5767                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5768             }
5769             SendToProgram(message, cps);
5770           }
5771         }
5772       }
5773
5774       SendToProgram(".\n", cps);
5775     }
5776     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5777 }
5778
5779 ChessSquare
5780 DefaultPromoChoice(int white)
5781 {
5782     ChessSquare result;
5783     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5784         result = WhiteFerz; // no choice
5785     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5786         result= WhiteKing; // in Suicide Q is the last thing we want
5787     else if(gameInfo.variant == VariantSpartan)
5788         result = white ? WhiteQueen : WhiteAngel;
5789     else result = WhiteQueen;
5790     if(!white) result = WHITE_TO_BLACK result;
5791     return result;
5792 }
5793
5794 static int autoQueen; // [HGM] oneclick
5795
5796 int
5797 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5798 {
5799     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5800     /* [HGM] add Shogi promotions */
5801     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5802     ChessSquare piece;
5803     ChessMove moveType;
5804     Boolean premove;
5805
5806     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5807     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5808
5809     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5810       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5811         return FALSE;
5812
5813     piece = boards[currentMove][fromY][fromX];
5814     if(gameInfo.variant == VariantShogi) {
5815         promotionZoneSize = BOARD_HEIGHT/3;
5816         highestPromotingPiece = (int)WhiteFerz;
5817     } else if(gameInfo.variant == VariantMakruk) {
5818         promotionZoneSize = 3;
5819     }
5820
5821     // Treat Lance as Pawn when it is not representing Amazon
5822     if(gameInfo.variant != VariantSuper) {
5823         if(piece == WhiteLance) piece = WhitePawn; else
5824         if(piece == BlackLance) piece = BlackPawn;
5825     }
5826
5827     // next weed out all moves that do not touch the promotion zone at all
5828     if((int)piece >= BlackPawn) {
5829         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5830              return FALSE;
5831         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5832     } else {
5833         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5834            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5835     }
5836
5837     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5838
5839     // weed out mandatory Shogi promotions
5840     if(gameInfo.variant == VariantShogi) {
5841         if(piece >= BlackPawn) {
5842             if(toY == 0 && piece == BlackPawn ||
5843                toY == 0 && piece == BlackQueen ||
5844                toY <= 1 && piece == BlackKnight) {
5845                 *promoChoice = '+';
5846                 return FALSE;
5847             }
5848         } else {
5849             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5850                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5851                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5852                 *promoChoice = '+';
5853                 return FALSE;
5854             }
5855         }
5856     }
5857
5858     // weed out obviously illegal Pawn moves
5859     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5860         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5861         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5862         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5863         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5864         // note we are not allowed to test for valid (non-)capture, due to premove
5865     }
5866
5867     // we either have a choice what to promote to, or (in Shogi) whether to promote
5868     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5869         *promoChoice = PieceToChar(BlackFerz);  // no choice
5870         return FALSE;
5871     }
5872     // no sense asking what we must promote to if it is going to explode...
5873     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5874         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5875         return FALSE;
5876     }
5877     // give caller the default choice even if we will not make it
5878     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5879     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5880     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5881                            && gameInfo.variant != VariantShogi
5882                            && gameInfo.variant != VariantSuper) return FALSE;
5883     if(autoQueen) return FALSE; // predetermined
5884
5885     // suppress promotion popup on illegal moves that are not premoves
5886     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5887               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5888     if(appData.testLegality && !premove) {
5889         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5890                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5891         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5892             return FALSE;
5893     }
5894
5895     return TRUE;
5896 }
5897
5898 int
5899 InPalace(row, column)
5900      int row, column;
5901 {   /* [HGM] for Xiangqi */
5902     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5903          column < (BOARD_WIDTH + 4)/2 &&
5904          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5905     return FALSE;
5906 }
5907
5908 int
5909 PieceForSquare (x, y)
5910      int x;
5911      int y;
5912 {
5913   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5914      return -1;
5915   else
5916      return boards[currentMove][y][x];
5917 }
5918
5919 int
5920 OKToStartUserMove(x, y)
5921      int x, y;
5922 {
5923     ChessSquare from_piece;
5924     int white_piece;
5925
5926     if (matchMode) return FALSE;
5927     if (gameMode == EditPosition) return TRUE;
5928
5929     if (x >= 0 && y >= 0)
5930       from_piece = boards[currentMove][y][x];
5931     else
5932       from_piece = EmptySquare;
5933
5934     if (from_piece == EmptySquare) return FALSE;
5935
5936     white_piece = (int)from_piece >= (int)WhitePawn &&
5937       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5938
5939     switch (gameMode) {
5940       case PlayFromGameFile:
5941       case AnalyzeFile:
5942       case TwoMachinesPlay:
5943       case EndOfGame:
5944         return FALSE;
5945
5946       case IcsObserving:
5947       case IcsIdle:
5948         return FALSE;
5949
5950       case MachinePlaysWhite:
5951       case IcsPlayingBlack:
5952         if (appData.zippyPlay) return FALSE;
5953         if (white_piece) {
5954             DisplayMoveError(_("You are playing Black"));
5955             return FALSE;
5956         }
5957         break;
5958
5959       case MachinePlaysBlack:
5960       case IcsPlayingWhite:
5961         if (appData.zippyPlay) return FALSE;
5962         if (!white_piece) {
5963             DisplayMoveError(_("You are playing White"));
5964             return FALSE;
5965         }
5966         break;
5967
5968       case EditGame:
5969         if (!white_piece && WhiteOnMove(currentMove)) {
5970             DisplayMoveError(_("It is White's turn"));
5971             return FALSE;
5972         }
5973         if (white_piece && !WhiteOnMove(currentMove)) {
5974             DisplayMoveError(_("It is Black's turn"));
5975             return FALSE;
5976         }
5977         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5978             /* Editing correspondence game history */
5979             /* Could disallow this or prompt for confirmation */
5980             cmailOldMove = -1;
5981         }
5982         break;
5983
5984       case BeginningOfGame:
5985         if (appData.icsActive) return FALSE;
5986         if (!appData.noChessProgram) {
5987             if (!white_piece) {
5988                 DisplayMoveError(_("You are playing White"));
5989                 return FALSE;
5990             }
5991         }
5992         break;
5993
5994       case Training:
5995         if (!white_piece && WhiteOnMove(currentMove)) {
5996             DisplayMoveError(_("It is White's turn"));
5997             return FALSE;
5998         }
5999         if (white_piece && !WhiteOnMove(currentMove)) {
6000             DisplayMoveError(_("It is Black's turn"));
6001             return FALSE;
6002         }
6003         break;
6004
6005       default:
6006       case IcsExamining:
6007         break;
6008     }
6009     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6010         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6011         && gameMode != AnalyzeFile && gameMode != Training) {
6012         DisplayMoveError(_("Displayed position is not current"));
6013         return FALSE;
6014     }
6015     return TRUE;
6016 }
6017
6018 Boolean
6019 OnlyMove(int *x, int *y, Boolean captures) {
6020     DisambiguateClosure cl;
6021     if (appData.zippyPlay) return FALSE;
6022     switch(gameMode) {
6023       case MachinePlaysBlack:
6024       case IcsPlayingWhite:
6025       case BeginningOfGame:
6026         if(!WhiteOnMove(currentMove)) return FALSE;
6027         break;
6028       case MachinePlaysWhite:
6029       case IcsPlayingBlack:
6030         if(WhiteOnMove(currentMove)) return FALSE;
6031         break;
6032       case EditGame:
6033         break;
6034       default:
6035         return FALSE;
6036     }
6037     cl.pieceIn = EmptySquare;
6038     cl.rfIn = *y;
6039     cl.ffIn = *x;
6040     cl.rtIn = -1;
6041     cl.ftIn = -1;
6042     cl.promoCharIn = NULLCHAR;
6043     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6044     if( cl.kind == NormalMove ||
6045         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6046         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6047         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6048       fromX = cl.ff;
6049       fromY = cl.rf;
6050       *x = cl.ft;
6051       *y = cl.rt;
6052       return TRUE;
6053     }
6054     if(cl.kind != ImpossibleMove) return FALSE;
6055     cl.pieceIn = EmptySquare;
6056     cl.rfIn = -1;
6057     cl.ffIn = -1;
6058     cl.rtIn = *y;
6059     cl.ftIn = *x;
6060     cl.promoCharIn = NULLCHAR;
6061     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6062     if( cl.kind == NormalMove ||
6063         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6064         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6065         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6066       fromX = cl.ff;
6067       fromY = cl.rf;
6068       *x = cl.ft;
6069       *y = cl.rt;
6070       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6071       return TRUE;
6072     }
6073     return FALSE;
6074 }
6075
6076 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6077 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6078 int lastLoadGameUseList = FALSE;
6079 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6080 ChessMove lastLoadGameStart = EndOfFile;
6081
6082 void
6083 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6084      int fromX, fromY, toX, toY;
6085      int promoChar;
6086 {
6087     ChessMove moveType;
6088     ChessSquare pdown, pup;
6089
6090     /* Check if the user is playing in turn.  This is complicated because we
6091        let the user "pick up" a piece before it is his turn.  So the piece he
6092        tried to pick up may have been captured by the time he puts it down!
6093        Therefore we use the color the user is supposed to be playing in this
6094        test, not the color of the piece that is currently on the starting
6095        square---except in EditGame mode, where the user is playing both
6096        sides; fortunately there the capture race can't happen.  (It can
6097        now happen in IcsExamining mode, but that's just too bad.  The user
6098        will get a somewhat confusing message in that case.)
6099        */
6100
6101     switch (gameMode) {
6102       case PlayFromGameFile:
6103       case AnalyzeFile:
6104       case TwoMachinesPlay:
6105       case EndOfGame:
6106       case IcsObserving:
6107       case IcsIdle:
6108         /* We switched into a game mode where moves are not accepted,
6109            perhaps while the mouse button was down. */
6110         return;
6111
6112       case MachinePlaysWhite:
6113         /* User is moving for Black */
6114         if (WhiteOnMove(currentMove)) {
6115             DisplayMoveError(_("It is White's turn"));
6116             return;
6117         }
6118         break;
6119
6120       case MachinePlaysBlack:
6121         /* User is moving for White */
6122         if (!WhiteOnMove(currentMove)) {
6123             DisplayMoveError(_("It is Black's turn"));
6124             return;
6125         }
6126         break;
6127
6128       case EditGame:
6129       case IcsExamining:
6130       case BeginningOfGame:
6131       case AnalyzeMode:
6132       case Training:
6133         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6134         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6135             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6136             /* User is moving for Black */
6137             if (WhiteOnMove(currentMove)) {
6138                 DisplayMoveError(_("It is White's turn"));
6139                 return;
6140             }
6141         } else {
6142             /* User is moving for White */
6143             if (!WhiteOnMove(currentMove)) {
6144                 DisplayMoveError(_("It is Black's turn"));
6145                 return;
6146             }
6147         }
6148         break;
6149
6150       case IcsPlayingBlack:
6151         /* User is moving for Black */
6152         if (WhiteOnMove(currentMove)) {
6153             if (!appData.premove) {
6154                 DisplayMoveError(_("It is White's turn"));
6155             } else if (toX >= 0 && toY >= 0) {
6156                 premoveToX = toX;
6157                 premoveToY = toY;
6158                 premoveFromX = fromX;
6159                 premoveFromY = fromY;
6160                 premovePromoChar = promoChar;
6161                 gotPremove = 1;
6162                 if (appData.debugMode)
6163                     fprintf(debugFP, "Got premove: fromX %d,"
6164                             "fromY %d, toX %d, toY %d\n",
6165                             fromX, fromY, toX, toY);
6166             }
6167             return;
6168         }
6169         break;
6170
6171       case IcsPlayingWhite:
6172         /* User is moving for White */
6173         if (!WhiteOnMove(currentMove)) {
6174             if (!appData.premove) {
6175                 DisplayMoveError(_("It is Black's turn"));
6176             } else if (toX >= 0 && toY >= 0) {
6177                 premoveToX = toX;
6178                 premoveToY = toY;
6179                 premoveFromX = fromX;
6180                 premoveFromY = fromY;
6181                 premovePromoChar = promoChar;
6182                 gotPremove = 1;
6183                 if (appData.debugMode)
6184                     fprintf(debugFP, "Got premove: fromX %d,"
6185                             "fromY %d, toX %d, toY %d\n",
6186                             fromX, fromY, toX, toY);
6187             }
6188             return;
6189         }
6190         break;
6191
6192       default:
6193         break;
6194
6195       case EditPosition:
6196         /* EditPosition, empty square, or different color piece;
6197            click-click move is possible */
6198         if (toX == -2 || toY == -2) {
6199             boards[0][fromY][fromX] = EmptySquare;
6200             DrawPosition(FALSE, boards[currentMove]);
6201             return;
6202         } else if (toX >= 0 && toY >= 0) {
6203             boards[0][toY][toX] = boards[0][fromY][fromX];
6204             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6205                 if(boards[0][fromY][0] != EmptySquare) {
6206                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6207                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6208                 }
6209             } else
6210             if(fromX == BOARD_RGHT+1) {
6211                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6212                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6213                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6214                 }
6215             } else
6216             boards[0][fromY][fromX] = EmptySquare;
6217             DrawPosition(FALSE, boards[currentMove]);
6218             return;
6219         }
6220         return;
6221     }
6222
6223     if(toX < 0 || toY < 0) return;
6224     pdown = boards[currentMove][fromY][fromX];
6225     pup = boards[currentMove][toY][toX];
6226
6227     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6228     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6229          if( pup != EmptySquare ) return;
6230          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6231            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6232                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6233            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6234            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6235            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6236            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6237          fromY = DROP_RANK;
6238     }
6239
6240     /* [HGM] always test for legality, to get promotion info */
6241     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6242                                          fromY, fromX, toY, toX, promoChar);
6243     /* [HGM] but possibly ignore an IllegalMove result */
6244     if (appData.testLegality) {
6245         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6246             DisplayMoveError(_("Illegal move"));
6247             return;
6248         }
6249     }
6250
6251     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6252 }
6253
6254 /* Common tail of UserMoveEvent and DropMenuEvent */
6255 int
6256 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6257      ChessMove moveType;
6258      int fromX, fromY, toX, toY;
6259      /*char*/int promoChar;
6260 {
6261     char *bookHit = 0;
6262
6263     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6264         // [HGM] superchess: suppress promotions to non-available piece
6265         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6266         if(WhiteOnMove(currentMove)) {
6267             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6268         } else {
6269             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6270         }
6271     }
6272
6273     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6274        move type in caller when we know the move is a legal promotion */
6275     if(moveType == NormalMove && promoChar)
6276         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6277
6278     /* [HGM] <popupFix> The following if has been moved here from
6279        UserMoveEvent(). Because it seemed to belong here (why not allow
6280        piece drops in training games?), and because it can only be
6281        performed after it is known to what we promote. */
6282     if (gameMode == Training) {
6283       /* compare the move played on the board to the next move in the
6284        * game. If they match, display the move and the opponent's response.
6285        * If they don't match, display an error message.
6286        */
6287       int saveAnimate;
6288       Board testBoard;
6289       CopyBoard(testBoard, boards[currentMove]);
6290       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6291
6292       if (CompareBoards(testBoard, boards[currentMove+1])) {
6293         ForwardInner(currentMove+1);
6294
6295         /* Autoplay the opponent's response.
6296          * if appData.animate was TRUE when Training mode was entered,
6297          * the response will be animated.
6298          */
6299         saveAnimate = appData.animate;
6300         appData.animate = animateTraining;
6301         ForwardInner(currentMove+1);
6302         appData.animate = saveAnimate;
6303
6304         /* check for the end of the game */
6305         if (currentMove >= forwardMostMove) {
6306           gameMode = PlayFromGameFile;
6307           ModeHighlight();
6308           SetTrainingModeOff();
6309           DisplayInformation(_("End of game"));
6310         }
6311       } else {
6312         DisplayError(_("Incorrect move"), 0);
6313       }
6314       return 1;
6315     }
6316
6317   /* Ok, now we know that the move is good, so we can kill
6318      the previous line in Analysis Mode */
6319   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6320                                 && currentMove < forwardMostMove) {
6321     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6322     else forwardMostMove = currentMove;
6323   }
6324
6325   /* If we need the chess program but it's dead, restart it */
6326   ResurrectChessProgram();
6327
6328   /* A user move restarts a paused game*/
6329   if (pausing)
6330     PauseEvent();
6331
6332   thinkOutput[0] = NULLCHAR;
6333
6334   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6335
6336   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6337     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6338     return 1;
6339   }
6340
6341   if (gameMode == BeginningOfGame) {
6342     if (appData.noChessProgram) {
6343       gameMode = EditGame;
6344       SetGameInfo();
6345     } else {
6346       char buf[MSG_SIZ];
6347       gameMode = MachinePlaysBlack;
6348       StartClocks();
6349       SetGameInfo();
6350       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6351       DisplayTitle(buf);
6352       if (first.sendName) {
6353         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6354         SendToProgram(buf, &first);
6355       }
6356       StartClocks();
6357     }
6358     ModeHighlight();
6359   }
6360
6361   /* Relay move to ICS or chess engine */
6362   if (appData.icsActive) {
6363     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6364         gameMode == IcsExamining) {
6365       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6366         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6367         SendToICS("draw ");
6368         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6369       }
6370       // also send plain move, in case ICS does not understand atomic claims
6371       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6372       ics_user_moved = 1;
6373     }
6374   } else {
6375     if (first.sendTime && (gameMode == BeginningOfGame ||
6376                            gameMode == MachinePlaysWhite ||
6377                            gameMode == MachinePlaysBlack)) {
6378       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6379     }
6380     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6381          // [HGM] book: if program might be playing, let it use book
6382         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6383         first.maybeThinking = TRUE;
6384     } else SendMoveToProgram(forwardMostMove-1, &first);
6385     if (currentMove == cmailOldMove + 1) {
6386       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6387     }
6388   }
6389
6390   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6391
6392   switch (gameMode) {
6393   case EditGame:
6394     if(appData.testLegality)
6395     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6396     case MT_NONE:
6397     case MT_CHECK:
6398       break;
6399     case MT_CHECKMATE:
6400     case MT_STAINMATE:
6401       if (WhiteOnMove(currentMove)) {
6402         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6403       } else {
6404         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6405       }
6406       break;
6407     case MT_STALEMATE:
6408       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6409       break;
6410     }
6411     break;
6412
6413   case MachinePlaysBlack:
6414   case MachinePlaysWhite:
6415     /* disable certain menu options while machine is thinking */
6416     SetMachineThinkingEnables();
6417     break;
6418
6419   default:
6420     break;
6421   }
6422
6423   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6424   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6425
6426   if(bookHit) { // [HGM] book: simulate book reply
6427         static char bookMove[MSG_SIZ]; // a bit generous?
6428
6429         programStats.nodes = programStats.depth = programStats.time =
6430         programStats.score = programStats.got_only_move = 0;
6431         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6432
6433         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6434         strcat(bookMove, bookHit);
6435         HandleMachineMove(bookMove, &first);
6436   }
6437   return 1;
6438 }
6439
6440 void
6441 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6442      Board board;
6443      int flags;
6444      ChessMove kind;
6445      int rf, ff, rt, ft;
6446      VOIDSTAR closure;
6447 {
6448     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6449     Markers *m = (Markers *) closure;
6450     if(rf == fromY && ff == fromX)
6451         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6452                          || kind == WhiteCapturesEnPassant
6453                          || kind == BlackCapturesEnPassant);
6454     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6455 }
6456
6457 void
6458 MarkTargetSquares(int clear)
6459 {
6460   int x, y;
6461   if(!appData.markers || !appData.highlightDragging ||
6462      !appData.testLegality || gameMode == EditPosition) return;
6463   if(clear) {
6464     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6465   } else {
6466     int capt = 0;
6467     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6468     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6469       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6470       if(capt)
6471       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6472     }
6473   }
6474   DrawPosition(TRUE, NULL);
6475 }
6476
6477 int
6478 Explode(Board board, int fromX, int fromY, int toX, int toY)
6479 {
6480     if(gameInfo.variant == VariantAtomic &&
6481        (board[toY][toX] != EmptySquare ||                     // capture?
6482         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6483                          board[fromY][fromX] == BlackPawn   )
6484       )) {
6485         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6486         return TRUE;
6487     }
6488     return FALSE;
6489 }
6490
6491 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6492
6493 int CanPromote(ChessSquare piece, int y)
6494 {
6495         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6496         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6497         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6498            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6499            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6500                                                   gameInfo.variant == VariantMakruk) return FALSE;
6501         return (piece == BlackPawn && y == 1 ||
6502                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6503                 piece == BlackLance && y == 1 ||
6504                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6505 }
6506
6507 void LeftClick(ClickType clickType, int xPix, int yPix)
6508 {
6509     int x, y;
6510     Boolean saveAnimate;
6511     static int second = 0, promotionChoice = 0, clearFlag = 0;
6512     char promoChoice = NULLCHAR;
6513     ChessSquare piece;
6514
6515     if(appData.seekGraph && appData.icsActive && loggedOn &&
6516         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6517         SeekGraphClick(clickType, xPix, yPix, 0);
6518         return;
6519     }
6520
6521     if (clickType == Press) ErrorPopDown();
6522     MarkTargetSquares(1);
6523
6524     x = EventToSquare(xPix, BOARD_WIDTH);
6525     y = EventToSquare(yPix, BOARD_HEIGHT);
6526     if (!flipView && y >= 0) {
6527         y = BOARD_HEIGHT - 1 - y;
6528     }
6529     if (flipView && x >= 0) {
6530         x = BOARD_WIDTH - 1 - x;
6531     }
6532
6533     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6534         defaultPromoChoice = promoSweep;
6535         promoSweep = EmptySquare;   // terminate sweep
6536         promoDefaultAltered = TRUE;
6537         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6538     }
6539
6540     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6541         if(clickType == Release) return; // ignore upclick of click-click destination
6542         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6543         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6544         if(gameInfo.holdingsWidth &&
6545                 (WhiteOnMove(currentMove)
6546                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6547                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6548             // click in right holdings, for determining promotion piece
6549             ChessSquare p = boards[currentMove][y][x];
6550             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6551             if(p != EmptySquare) {
6552                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6553                 fromX = fromY = -1;
6554                 return;
6555             }
6556         }
6557         DrawPosition(FALSE, boards[currentMove]);
6558         return;
6559     }
6560
6561     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6562     if(clickType == Press
6563             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6564               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6565               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6566         return;
6567
6568     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6569         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6570
6571     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6572         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6573                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6574         defaultPromoChoice = DefaultPromoChoice(side);
6575     }
6576
6577     autoQueen = appData.alwaysPromoteToQueen;
6578
6579     if (fromX == -1) {
6580       int originalY = y;
6581       gatingPiece = EmptySquare;
6582       if (clickType != Press) {
6583         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6584             DragPieceEnd(xPix, yPix); dragging = 0;
6585             DrawPosition(FALSE, NULL);
6586         }
6587         return;
6588       }
6589       fromX = x; fromY = y;
6590       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6591          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6592          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6593             /* First square */
6594             if (OKToStartUserMove(fromX, fromY)) {
6595                 second = 0;
6596                 MarkTargetSquares(0);
6597                 DragPieceBegin(xPix, yPix); dragging = 1;
6598                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6599                     promoSweep = defaultPromoChoice;
6600                     selectFlag = 0; lastX = xPix; lastY = yPix;
6601                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6602                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6603                 }
6604                 if (appData.highlightDragging) {
6605                     SetHighlights(fromX, fromY, -1, -1);
6606                 }
6607             } else fromX = fromY = -1;
6608             return;
6609         }
6610     }
6611
6612     /* fromX != -1 */
6613     if (clickType == Press && gameMode != EditPosition) {
6614         ChessSquare fromP;
6615         ChessSquare toP;
6616         int frc;
6617
6618         // ignore off-board to clicks
6619         if(y < 0 || x < 0) return;
6620
6621         /* Check if clicking again on the same color piece */
6622         fromP = boards[currentMove][fromY][fromX];
6623         toP = boards[currentMove][y][x];
6624         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6625         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6626              WhitePawn <= toP && toP <= WhiteKing &&
6627              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6628              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6629             (BlackPawn <= fromP && fromP <= BlackKing &&
6630              BlackPawn <= toP && toP <= BlackKing &&
6631              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6632              !(fromP == BlackKing && toP == BlackRook && frc))) {
6633             /* Clicked again on same color piece -- changed his mind */
6634             second = (x == fromX && y == fromY);
6635             promoDefaultAltered = FALSE;
6636            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6637             if (appData.highlightDragging) {
6638                 SetHighlights(x, y, -1, -1);
6639             } else {
6640                 ClearHighlights();
6641             }
6642             if (OKToStartUserMove(x, y)) {
6643                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6644                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6645                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6646                  gatingPiece = boards[currentMove][fromY][fromX];
6647                 else gatingPiece = EmptySquare;
6648                 fromX = x;
6649                 fromY = y; dragging = 1;
6650                 MarkTargetSquares(0);
6651                 DragPieceBegin(xPix, yPix);
6652                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6653                     promoSweep = defaultPromoChoice;
6654                     selectFlag = 0; lastX = xPix; lastY = yPix;
6655                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6656                 }
6657             }
6658            }
6659            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6660            second = FALSE; 
6661         }
6662         // ignore clicks on holdings
6663         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6664     }
6665
6666     if (clickType == Release && x == fromX && y == fromY) {
6667         DragPieceEnd(xPix, yPix); dragging = 0;
6668         if(clearFlag) {
6669             // a deferred attempt to click-click move an empty square on top of a piece
6670             boards[currentMove][y][x] = EmptySquare;
6671             ClearHighlights();
6672             DrawPosition(FALSE, boards[currentMove]);
6673             fromX = fromY = -1; clearFlag = 0;
6674             return;
6675         }
6676         if (appData.animateDragging) {
6677             /* Undo animation damage if any */
6678             DrawPosition(FALSE, NULL);
6679         }
6680         if (second) {
6681             /* Second up/down in same square; just abort move */
6682             second = 0;
6683             fromX = fromY = -1;
6684             gatingPiece = EmptySquare;
6685             ClearHighlights();
6686             gotPremove = 0;
6687             ClearPremoveHighlights();
6688         } else {
6689             /* First upclick in same square; start click-click mode */
6690             SetHighlights(x, y, -1, -1);
6691         }
6692         return;
6693     }
6694
6695     clearFlag = 0;
6696
6697     /* we now have a different from- and (possibly off-board) to-square */
6698     /* Completed move */
6699     toX = x;
6700     toY = y;
6701     saveAnimate = appData.animate;
6702     if (clickType == Press) {
6703         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6704             // must be Edit Position mode with empty-square selected
6705             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6706             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6707             return;
6708         }
6709         /* Finish clickclick move */
6710         if (appData.animate || appData.highlightLastMove) {
6711             SetHighlights(fromX, fromY, toX, toY);
6712         } else {
6713             ClearHighlights();
6714         }
6715     } else {
6716         /* Finish drag move */
6717         if (appData.highlightLastMove) {
6718             SetHighlights(fromX, fromY, toX, toY);
6719         } else {
6720             ClearHighlights();
6721         }
6722         DragPieceEnd(xPix, yPix); dragging = 0;
6723         /* Don't animate move and drag both */
6724         appData.animate = FALSE;
6725     }
6726
6727     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6728     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6729         ChessSquare piece = boards[currentMove][fromY][fromX];
6730         if(gameMode == EditPosition && piece != EmptySquare &&
6731            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6732             int n;
6733
6734             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6735                 n = PieceToNumber(piece - (int)BlackPawn);
6736                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6737                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6738                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6739             } else
6740             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6741                 n = PieceToNumber(piece);
6742                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6743                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6744                 boards[currentMove][n][BOARD_WIDTH-2]++;
6745             }
6746             boards[currentMove][fromY][fromX] = EmptySquare;
6747         }
6748         ClearHighlights();
6749         fromX = fromY = -1;
6750         DrawPosition(TRUE, boards[currentMove]);
6751         return;
6752     }
6753
6754     // off-board moves should not be highlighted
6755     if(x < 0 || y < 0) ClearHighlights();
6756
6757     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6758
6759     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6760         SetHighlights(fromX, fromY, toX, toY);
6761         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6762             // [HGM] super: promotion to captured piece selected from holdings
6763             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6764             promotionChoice = TRUE;
6765             // kludge follows to temporarily execute move on display, without promoting yet
6766             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6767             boards[currentMove][toY][toX] = p;
6768             DrawPosition(FALSE, boards[currentMove]);
6769             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6770             boards[currentMove][toY][toX] = q;
6771             DisplayMessage("Click in holdings to choose piece", "");
6772             return;
6773         }
6774         PromotionPopUp();
6775     } else {
6776         int oldMove = currentMove;
6777         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6778         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6779         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6780         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6781            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6782             DrawPosition(TRUE, boards[currentMove]);
6783         fromX = fromY = -1;
6784     }
6785     appData.animate = saveAnimate;
6786     if (appData.animate || appData.animateDragging) {
6787         /* Undo animation damage if needed */
6788         DrawPosition(FALSE, NULL);
6789     }
6790 }
6791
6792 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6793 {   // front-end-free part taken out of PieceMenuPopup
6794     int whichMenu; int xSqr, ySqr;
6795
6796     if(seekGraphUp) { // [HGM] seekgraph
6797         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6798         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6799         return -2;
6800     }
6801
6802     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6803          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6804         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6805         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6806         if(action == Press)   {
6807             originalFlip = flipView;
6808             flipView = !flipView; // temporarily flip board to see game from partners perspective
6809             DrawPosition(TRUE, partnerBoard);
6810             DisplayMessage(partnerStatus, "");
6811             partnerUp = TRUE;
6812         } else if(action == Release) {
6813             flipView = originalFlip;
6814             DrawPosition(TRUE, boards[currentMove]);
6815             partnerUp = FALSE;
6816         }
6817         return -2;
6818     }
6819
6820     xSqr = EventToSquare(x, BOARD_WIDTH);
6821     ySqr = EventToSquare(y, BOARD_HEIGHT);
6822     if (action == Release) {
6823         if(pieceSweep != EmptySquare) {
6824             EditPositionMenuEvent(pieceSweep, toX, toY);
6825             pieceSweep = EmptySquare;
6826         } else UnLoadPV(); // [HGM] pv
6827     }
6828     if (action != Press) return -2; // return code to be ignored
6829     switch (gameMode) {
6830       case IcsExamining:
6831         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6832       case EditPosition:
6833         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6834         if (xSqr < 0 || ySqr < 0) return -1;
6835         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6836         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6837         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6838         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6839         NextPiece(0);
6840         return -2;\r
6841       case IcsObserving:
6842         if(!appData.icsEngineAnalyze) return -1;
6843       case IcsPlayingWhite:
6844       case IcsPlayingBlack:
6845         if(!appData.zippyPlay) goto noZip;
6846       case AnalyzeMode:
6847       case AnalyzeFile:
6848       case MachinePlaysWhite:
6849       case MachinePlaysBlack:
6850       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6851         if (!appData.dropMenu) {
6852           LoadPV(x, y);
6853           return 2; // flag front-end to grab mouse events
6854         }
6855         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6856            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6857       case EditGame:
6858       noZip:
6859         if (xSqr < 0 || ySqr < 0) return -1;
6860         if (!appData.dropMenu || appData.testLegality &&
6861             gameInfo.variant != VariantBughouse &&
6862             gameInfo.variant != VariantCrazyhouse) return -1;
6863         whichMenu = 1; // drop menu
6864         break;
6865       default:
6866         return -1;
6867     }
6868
6869     if (((*fromX = xSqr) < 0) ||
6870         ((*fromY = ySqr) < 0)) {
6871         *fromX = *fromY = -1;
6872         return -1;
6873     }
6874     if (flipView)
6875       *fromX = BOARD_WIDTH - 1 - *fromX;
6876     else
6877       *fromY = BOARD_HEIGHT - 1 - *fromY;
6878
6879     return whichMenu;
6880 }
6881
6882 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6883 {
6884 //    char * hint = lastHint;
6885     FrontEndProgramStats stats;
6886
6887     stats.which = cps == &first ? 0 : 1;
6888     stats.depth = cpstats->depth;
6889     stats.nodes = cpstats->nodes;
6890     stats.score = cpstats->score;
6891     stats.time = cpstats->time;
6892     stats.pv = cpstats->movelist;
6893     stats.hint = lastHint;
6894     stats.an_move_index = 0;
6895     stats.an_move_count = 0;
6896
6897     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6898         stats.hint = cpstats->move_name;
6899         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6900         stats.an_move_count = cpstats->nr_moves;
6901     }
6902
6903     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
6904
6905     SetProgramStats( &stats );
6906 }
6907
6908 void
6909 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6910 {       // count all piece types
6911         int p, f, r;
6912         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6913         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6914         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6915                 p = board[r][f];
6916                 pCnt[p]++;
6917                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6918                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6919                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6920                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6921                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6922                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6923         }
6924 }
6925
6926 int
6927 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6928 {
6929         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6930         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6931
6932         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6933         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6934         if(myPawns == 2 && nMine == 3) // KPP
6935             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6936         if(myPawns == 1 && nMine == 2) // KP
6937             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6938         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6939             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6940         if(myPawns) return FALSE;
6941         if(pCnt[WhiteRook+side])
6942             return pCnt[BlackRook-side] ||
6943                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6944                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6945                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6946         if(pCnt[WhiteCannon+side]) {
6947             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6948             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6949         }
6950         if(pCnt[WhiteKnight+side])
6951             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6952         return FALSE;
6953 }
6954
6955 int
6956 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6957 {
6958         VariantClass v = gameInfo.variant;
6959
6960         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6961         if(v == VariantShatranj) return TRUE; // always winnable through baring
6962         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6963         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6964
6965         if(v == VariantXiangqi) {
6966                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6967
6968                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6969                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6970                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6971                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6972                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6973                 if(stale) // we have at least one last-rank P plus perhaps C
6974                     return majors // KPKX
6975                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6976                 else // KCA*E*
6977                     return pCnt[WhiteFerz+side] // KCAK
6978                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6979                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6980                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6981
6982         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6983                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6984
6985                 if(nMine == 1) return FALSE; // bare King
6986                 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
6987                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6988                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6989                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6990                 if(pCnt[WhiteKnight+side])
6991                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6992                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6993                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6994                 if(nBishops)
6995                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6996                 if(pCnt[WhiteAlfil+side])
6997                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6998                 if(pCnt[WhiteWazir+side])
6999                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7000         }
7001
7002         return TRUE;
7003 }
7004
7005 int
7006 Adjudicate(ChessProgramState *cps)
7007 {       // [HGM] some adjudications useful with buggy engines
7008         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7009         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7010         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7011         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7012         int k, count = 0; static int bare = 1;
7013         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7014         Boolean canAdjudicate = !appData.icsActive;
7015
7016         // most tests only when we understand the game, i.e. legality-checking on
7017             if( appData.testLegality )
7018             {   /* [HGM] Some more adjudications for obstinate engines */
7019                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7020                 static int moveCount = 6;
7021                 ChessMove result;
7022                 char *reason = NULL;
7023
7024                 /* Count what is on board. */
7025                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7026
7027                 /* Some material-based adjudications that have to be made before stalemate test */
7028                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7029                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7030                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7031                      if(canAdjudicate && appData.checkMates) {
7032                          if(engineOpponent)
7033                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7034                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7035                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7036                          return 1;
7037                      }
7038                 }
7039
7040                 /* Bare King in Shatranj (loses) or Losers (wins) */
7041                 if( nrW == 1 || nrB == 1) {
7042                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7043                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7044                      if(canAdjudicate && appData.checkMates) {
7045                          if(engineOpponent)
7046                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7047                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7048                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7049                          return 1;
7050                      }
7051                   } else
7052                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7053                   {    /* bare King */
7054                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7055                         if(canAdjudicate && appData.checkMates) {
7056                             /* but only adjudicate if adjudication enabled */
7057                             if(engineOpponent)
7058                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7059                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7060                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7061                             return 1;
7062                         }
7063                   }
7064                 } else bare = 1;
7065
7066
7067             // don't wait for engine to announce game end if we can judge ourselves
7068             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7069               case MT_CHECK:
7070                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7071                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7072                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7073                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7074                             checkCnt++;
7075                         if(checkCnt >= 2) {
7076                             reason = "Xboard adjudication: 3rd check";
7077                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7078                             break;
7079                         }
7080                     }
7081                 }
7082               case MT_NONE:
7083               default:
7084                 break;
7085               case MT_STALEMATE:
7086               case MT_STAINMATE:
7087                 reason = "Xboard adjudication: Stalemate";
7088                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7089                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7090                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7091                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7092                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7093                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7094                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7095                                                                         EP_CHECKMATE : EP_WINS);
7096                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7097                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7098                 }
7099                 break;
7100               case MT_CHECKMATE:
7101                 reason = "Xboard adjudication: Checkmate";
7102                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7103                 break;
7104             }
7105
7106                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7107                     case EP_STALEMATE:
7108                         result = GameIsDrawn; break;
7109                     case EP_CHECKMATE:
7110                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7111                     case EP_WINS:
7112                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7113                     default:
7114                         result = EndOfFile;
7115                 }
7116                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7117                     if(engineOpponent)
7118                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7119                     GameEnds( result, reason, GE_XBOARD );
7120                     return 1;
7121                 }
7122
7123                 /* Next absolutely insufficient mating material. */
7124                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7125                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7126                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7127
7128                      /* always flag draws, for judging claims */
7129                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7130
7131                      if(canAdjudicate && appData.materialDraws) {
7132                          /* but only adjudicate them if adjudication enabled */
7133                          if(engineOpponent) {
7134                            SendToProgram("force\n", engineOpponent); // suppress reply
7135                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7136                          }
7137                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7138                          return 1;
7139                      }
7140                 }
7141
7142                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7143                 if(gameInfo.variant == VariantXiangqi ?
7144                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7145                  : nrW + nrB == 4 &&
7146                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7147                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7148                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7149                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7150                    ) ) {
7151                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7152                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7153                           if(engineOpponent) {
7154                             SendToProgram("force\n", engineOpponent); // suppress reply
7155                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7156                           }
7157                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7158                           return 1;
7159                      }
7160                 } else moveCount = 6;
7161             }
7162         if (appData.debugMode) { int i;
7163             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7164                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7165                     appData.drawRepeats);
7166             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7167               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7168
7169         }
7170
7171         // Repetition draws and 50-move rule can be applied independently of legality testing
7172
7173                 /* Check for rep-draws */
7174                 count = 0;
7175                 for(k = forwardMostMove-2;
7176                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7177                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7178                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7179                     k-=2)
7180                 {   int rights=0;
7181                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7182                         /* compare castling rights */
7183                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7184                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7185                                 rights++; /* King lost rights, while rook still had them */
7186                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7187                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7188                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7189                                    rights++; /* but at least one rook lost them */
7190                         }
7191                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7192                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7193                                 rights++;
7194                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7195                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7196                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7197                                    rights++;
7198                         }
7199                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7200                             && appData.drawRepeats > 1) {
7201                              /* adjudicate after user-specified nr of repeats */
7202                              int result = GameIsDrawn;
7203                              char *details = "XBoard adjudication: repetition draw";
7204                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7205                                 // [HGM] xiangqi: check for forbidden perpetuals
7206                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7207                                 for(m=forwardMostMove; m>k; m-=2) {
7208                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7209                                         ourPerpetual = 0; // the current mover did not always check
7210                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7211                                         hisPerpetual = 0; // the opponent did not always check
7212                                 }
7213                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7214                                                                         ourPerpetual, hisPerpetual);
7215                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7216                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7217                                     details = "Xboard adjudication: perpetual checking";
7218                                 } else
7219                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7220                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7221                                 } else
7222                                 // Now check for perpetual chases
7223                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7224                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7225                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7226                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7227                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7228                                         details = "Xboard adjudication: perpetual chasing";
7229                                     } else
7230                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7231                                         break; // Abort repetition-checking loop.
7232                                 }
7233                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7234                              }
7235                              if(engineOpponent) {
7236                                SendToProgram("force\n", engineOpponent); // suppress reply
7237                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7238                              }
7239                              GameEnds( result, details, GE_XBOARD );
7240                              return 1;
7241                         }
7242                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7243                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7244                     }
7245                 }
7246
7247                 /* Now we test for 50-move draws. Determine ply count */
7248                 count = forwardMostMove;
7249                 /* look for last irreversble move */
7250                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7251                     count--;
7252                 /* if we hit starting position, add initial plies */
7253                 if( count == backwardMostMove )
7254                     count -= initialRulePlies;
7255                 count = forwardMostMove - count;
7256                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7257                         // adjust reversible move counter for checks in Xiangqi
7258                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7259                         if(i < backwardMostMove) i = backwardMostMove;
7260                         while(i <= forwardMostMove) {
7261                                 lastCheck = inCheck; // check evasion does not count
7262                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7263                                 if(inCheck || lastCheck) count--; // check does not count
7264                                 i++;
7265                         }
7266                 }
7267                 if( count >= 100)
7268                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7269                          /* this is used to judge if draw claims are legal */
7270                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7271                          if(engineOpponent) {
7272                            SendToProgram("force\n", engineOpponent); // suppress reply
7273                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7274                          }
7275                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7276                          return 1;
7277                 }
7278
7279                 /* if draw offer is pending, treat it as a draw claim
7280                  * when draw condition present, to allow engines a way to
7281                  * claim draws before making their move to avoid a race
7282                  * condition occurring after their move
7283                  */
7284                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7285                          char *p = NULL;
7286                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7287                              p = "Draw claim: 50-move rule";
7288                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7289                              p = "Draw claim: 3-fold repetition";
7290                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7291                              p = "Draw claim: insufficient mating material";
7292                          if( p != NULL && canAdjudicate) {
7293                              if(engineOpponent) {
7294                                SendToProgram("force\n", engineOpponent); // suppress reply
7295                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7296                              }
7297                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7298                              return 1;
7299                          }
7300                 }
7301
7302                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7303                     if(engineOpponent) {
7304                       SendToProgram("force\n", engineOpponent); // suppress reply
7305                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7306                     }
7307                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7308                     return 1;
7309                 }
7310         return 0;
7311 }
7312
7313 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7314 {   // [HGM] book: this routine intercepts moves to simulate book replies
7315     char *bookHit = NULL;
7316
7317     //first determine if the incoming move brings opponent into his book
7318     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7319         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7320     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7321     if(bookHit != NULL && !cps->bookSuspend) {
7322         // make sure opponent is not going to reply after receiving move to book position
7323         SendToProgram("force\n", cps);
7324         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7325     }
7326     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7327     // now arrange restart after book miss
7328     if(bookHit) {
7329         // after a book hit we never send 'go', and the code after the call to this routine
7330         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7331         char buf[MSG_SIZ];
7332         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7333         SendToProgram(buf, cps);
7334         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7335     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7336         SendToProgram("go\n", cps);
7337         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7338     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7339         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7340             SendToProgram("go\n", cps);
7341         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7342     }
7343     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7344 }
7345
7346 char *savedMessage;
7347 ChessProgramState *savedState;
7348 void DeferredBookMove(void)
7349 {
7350         if(savedState->lastPing != savedState->lastPong)
7351                     ScheduleDelayedEvent(DeferredBookMove, 10);
7352         else
7353         HandleMachineMove(savedMessage, savedState);
7354 }
7355
7356 void
7357 HandleMachineMove(message, cps)
7358      char *message;
7359      ChessProgramState *cps;
7360 {
7361     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7362     char realname[MSG_SIZ];
7363     int fromX, fromY, toX, toY;
7364     ChessMove moveType;
7365     char promoChar;
7366     char *p;
7367     int machineWhite;
7368     char *bookHit;
7369
7370     cps->userError = 0;
7371
7372 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7373     /*
7374      * Kludge to ignore BEL characters
7375      */
7376     while (*message == '\007') message++;
7377
7378     /*
7379      * [HGM] engine debug message: ignore lines starting with '#' character
7380      */
7381     if(cps->debug && *message == '#') return;
7382
7383     /*
7384      * Look for book output
7385      */
7386     if (cps == &first && bookRequested) {
7387         if (message[0] == '\t' || message[0] == ' ') {
7388             /* Part of the book output is here; append it */
7389             strcat(bookOutput, message);
7390             strcat(bookOutput, "  \n");
7391             return;
7392         } else if (bookOutput[0] != NULLCHAR) {
7393             /* All of book output has arrived; display it */
7394             char *p = bookOutput;
7395             while (*p != NULLCHAR) {
7396                 if (*p == '\t') *p = ' ';
7397                 p++;
7398             }
7399             DisplayInformation(bookOutput);
7400             bookRequested = FALSE;
7401             /* Fall through to parse the current output */
7402         }
7403     }
7404
7405     /*
7406      * Look for machine move.
7407      */
7408     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7409         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7410     {
7411         /* This method is only useful on engines that support ping */
7412         if (cps->lastPing != cps->lastPong) {
7413           if (gameMode == BeginningOfGame) {
7414             /* Extra move from before last new; ignore */
7415             if (appData.debugMode) {
7416                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7417             }
7418           } else {
7419             if (appData.debugMode) {
7420                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7421                         cps->which, gameMode);
7422             }
7423
7424             SendToProgram("undo\n", cps);
7425           }
7426           return;
7427         }
7428
7429         switch (gameMode) {
7430           case BeginningOfGame:
7431             /* Extra move from before last reset; ignore */
7432             if (appData.debugMode) {
7433                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7434             }
7435             return;
7436
7437           case EndOfGame:
7438           case IcsIdle:
7439           default:
7440             /* Extra move after we tried to stop.  The mode test is
7441                not a reliable way of detecting this problem, but it's
7442                the best we can do on engines that don't support ping.
7443             */
7444             if (appData.debugMode) {
7445                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7446                         cps->which, gameMode);
7447             }
7448             SendToProgram("undo\n", cps);
7449             return;
7450
7451           case MachinePlaysWhite:
7452           case IcsPlayingWhite:
7453             machineWhite = TRUE;
7454             break;
7455
7456           case MachinePlaysBlack:
7457           case IcsPlayingBlack:
7458             machineWhite = FALSE;
7459             break;
7460
7461           case TwoMachinesPlay:
7462             machineWhite = (cps->twoMachinesColor[0] == 'w');
7463             break;
7464         }
7465         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7466             if (appData.debugMode) {
7467                 fprintf(debugFP,
7468                         "Ignoring move out of turn by %s, gameMode %d"
7469                         ", forwardMost %d\n",
7470                         cps->which, gameMode, forwardMostMove);
7471             }
7472             return;
7473         }
7474
7475     if (appData.debugMode) { int f = forwardMostMove;
7476         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7477                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7478                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7479     }
7480         if(cps->alphaRank) AlphaRank(machineMove, 4);
7481         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7482                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7483             /* Machine move could not be parsed; ignore it. */
7484           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7485                     machineMove, _(cps->which));
7486             DisplayError(buf1, 0);
7487             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7488                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7489             if (gameMode == TwoMachinesPlay) {
7490               GameEnds(machineWhite ? BlackWins : WhiteWins,
7491                        buf1, GE_XBOARD);
7492             }
7493             return;
7494         }
7495
7496         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7497         /* So we have to redo legality test with true e.p. status here,  */
7498         /* to make sure an illegal e.p. capture does not slip through,   */
7499         /* to cause a forfeit on a justified illegal-move complaint      */
7500         /* of the opponent.                                              */
7501         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7502            ChessMove moveType;
7503            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7504                              fromY, fromX, toY, toX, promoChar);
7505             if (appData.debugMode) {
7506                 int i;
7507                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7508                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7509                 fprintf(debugFP, "castling rights\n");
7510             }
7511             if(moveType == IllegalMove) {
7512               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7513                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7514                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7515                            buf1, GE_XBOARD);
7516                 return;
7517            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7518            /* [HGM] Kludge to handle engines that send FRC-style castling
7519               when they shouldn't (like TSCP-Gothic) */
7520            switch(moveType) {
7521              case WhiteASideCastleFR:
7522              case BlackASideCastleFR:
7523                toX+=2;
7524                currentMoveString[2]++;
7525                break;
7526              case WhiteHSideCastleFR:
7527              case BlackHSideCastleFR:
7528                toX--;
7529                currentMoveString[2]--;
7530                break;
7531              default: ; // nothing to do, but suppresses warning of pedantic compilers
7532            }
7533         }
7534         hintRequested = FALSE;
7535         lastHint[0] = NULLCHAR;
7536         bookRequested = FALSE;
7537         /* Program may be pondering now */
7538         cps->maybeThinking = TRUE;
7539         if (cps->sendTime == 2) cps->sendTime = 1;
7540         if (cps->offeredDraw) cps->offeredDraw--;
7541
7542         /* [AS] Save move info*/
7543         pvInfoList[ forwardMostMove ].score = programStats.score;
7544         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7545         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7546
7547         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7548
7549         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7550         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7551             int count = 0;
7552
7553             while( count < adjudicateLossPlies ) {
7554                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7555
7556                 if( count & 1 ) {
7557                     score = -score; /* Flip score for winning side */
7558                 }
7559
7560                 if( score > adjudicateLossThreshold ) {
7561                     break;
7562                 }
7563
7564                 count++;
7565             }
7566
7567             if( count >= adjudicateLossPlies ) {
7568                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7569
7570                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7571                     "Xboard adjudication",
7572                     GE_XBOARD );
7573
7574                 return;
7575             }
7576         }
7577
7578         if(Adjudicate(cps)) {
7579             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7580             return; // [HGM] adjudicate: for all automatic game ends
7581         }
7582
7583 #if ZIPPY
7584         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7585             first.initDone) {
7586           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7587                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7588                 SendToICS("draw ");
7589                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7590           }
7591           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7592           ics_user_moved = 1;
7593           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7594                 char buf[3*MSG_SIZ];
7595
7596                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7597                         programStats.score / 100.,
7598                         programStats.depth,
7599                         programStats.time / 100.,
7600                         (unsigned int)programStats.nodes,
7601                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7602                         programStats.movelist);
7603                 SendToICS(buf);
7604 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7605           }
7606         }
7607 #endif
7608
7609         /* [AS] Clear stats for next move */
7610         ClearProgramStats();
7611         thinkOutput[0] = NULLCHAR;
7612         hiddenThinkOutputState = 0;
7613
7614         bookHit = NULL;
7615         if (gameMode == TwoMachinesPlay) {
7616             /* [HGM] relaying draw offers moved to after reception of move */
7617             /* and interpreting offer as claim if it brings draw condition */
7618             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7619                 SendToProgram("draw\n", cps->other);
7620             }
7621             if (cps->other->sendTime) {
7622                 SendTimeRemaining(cps->other,
7623                                   cps->other->twoMachinesColor[0] == 'w');
7624             }
7625             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7626             if (firstMove && !bookHit) {
7627                 firstMove = FALSE;
7628                 if (cps->other->useColors) {
7629                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7630                 }
7631                 SendToProgram("go\n", cps->other);
7632             }
7633             cps->other->maybeThinking = TRUE;
7634         }
7635
7636         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7637
7638         if (!pausing && appData.ringBellAfterMoves) {
7639             RingBell();
7640         }
7641
7642         /*
7643          * Reenable menu items that were disabled while
7644          * machine was thinking
7645          */
7646         if (gameMode != TwoMachinesPlay)
7647             SetUserThinkingEnables();
7648
7649         // [HGM] book: after book hit opponent has received move and is now in force mode
7650         // force the book reply into it, and then fake that it outputted this move by jumping
7651         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7652         if(bookHit) {
7653                 static char bookMove[MSG_SIZ]; // a bit generous?
7654
7655                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7656                 strcat(bookMove, bookHit);
7657                 message = bookMove;
7658                 cps = cps->other;
7659                 programStats.nodes = programStats.depth = programStats.time =
7660                 programStats.score = programStats.got_only_move = 0;
7661                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7662
7663                 if(cps->lastPing != cps->lastPong) {
7664                     savedMessage = message; // args for deferred call
7665                     savedState = cps;
7666                     ScheduleDelayedEvent(DeferredBookMove, 10);
7667                     return;
7668                 }
7669                 goto FakeBookMove;
7670         }
7671
7672         return;
7673     }
7674
7675     /* Set special modes for chess engines.  Later something general
7676      *  could be added here; for now there is just one kludge feature,
7677      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7678      *  when "xboard" is given as an interactive command.
7679      */
7680     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7681         cps->useSigint = FALSE;
7682         cps->useSigterm = FALSE;
7683     }
7684     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7685       ParseFeatures(message+8, cps);
7686       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7687     }
7688
7689     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7690       int dummy, s=6; char buf[MSG_SIZ];
7691       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7692       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7693       ParseFEN(boards[0], &dummy, message+s);
7694       DrawPosition(TRUE, boards[0]);
7695       startedFromSetupPosition = TRUE;
7696       return;
7697     }
7698     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7699      * want this, I was asked to put it in, and obliged.
7700      */
7701     if (!strncmp(message, "setboard ", 9)) {
7702         Board initial_position;
7703
7704         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7705
7706         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7707             DisplayError(_("Bad FEN received from engine"), 0);
7708             return ;
7709         } else {
7710            Reset(TRUE, FALSE);
7711            CopyBoard(boards[0], initial_position);
7712            initialRulePlies = FENrulePlies;
7713            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7714            else gameMode = MachinePlaysBlack;
7715            DrawPosition(FALSE, boards[currentMove]);
7716         }
7717         return;
7718     }
7719
7720     /*
7721      * Look for communication commands
7722      */
7723     if (!strncmp(message, "telluser ", 9)) {
7724         if(message[9] == '\\' && message[10] == '\\')
7725             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7726         DisplayNote(message + 9);
7727         return;
7728     }
7729     if (!strncmp(message, "tellusererror ", 14)) {
7730         cps->userError = 1;
7731         if(message[14] == '\\' && message[15] == '\\')
7732             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7733         DisplayError(message + 14, 0);
7734         return;
7735     }
7736     if (!strncmp(message, "tellopponent ", 13)) {
7737       if (appData.icsActive) {
7738         if (loggedOn) {
7739           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7740           SendToICS(buf1);
7741         }
7742       } else {
7743         DisplayNote(message + 13);
7744       }
7745       return;
7746     }
7747     if (!strncmp(message, "tellothers ", 11)) {
7748       if (appData.icsActive) {
7749         if (loggedOn) {
7750           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7751           SendToICS(buf1);
7752         }
7753       }
7754       return;
7755     }
7756     if (!strncmp(message, "tellall ", 8)) {
7757       if (appData.icsActive) {
7758         if (loggedOn) {
7759           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7760           SendToICS(buf1);
7761         }
7762       } else {
7763         DisplayNote(message + 8);
7764       }
7765       return;
7766     }
7767     if (strncmp(message, "warning", 7) == 0) {
7768         /* Undocumented feature, use tellusererror in new code */
7769         DisplayError(message, 0);
7770         return;
7771     }
7772     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7773         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7774         strcat(realname, " query");
7775         AskQuestion(realname, buf2, buf1, cps->pr);
7776         return;
7777     }
7778     /* Commands from the engine directly to ICS.  We don't allow these to be
7779      *  sent until we are logged on. Crafty kibitzes have been known to
7780      *  interfere with the login process.
7781      */
7782     if (loggedOn) {
7783         if (!strncmp(message, "tellics ", 8)) {
7784             SendToICS(message + 8);
7785             SendToICS("\n");
7786             return;
7787         }
7788         if (!strncmp(message, "tellicsnoalias ", 15)) {
7789             SendToICS(ics_prefix);
7790             SendToICS(message + 15);
7791             SendToICS("\n");
7792             return;
7793         }
7794         /* The following are for backward compatibility only */
7795         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7796             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7797             SendToICS(ics_prefix);
7798             SendToICS(message);
7799             SendToICS("\n");
7800             return;
7801         }
7802     }
7803     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7804         return;
7805     }
7806     /*
7807      * If the move is illegal, cancel it and redraw the board.
7808      * Also deal with other error cases.  Matching is rather loose
7809      * here to accommodate engines written before the spec.
7810      */
7811     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7812         strncmp(message, "Error", 5) == 0) {
7813         if (StrStr(message, "name") ||
7814             StrStr(message, "rating") || StrStr(message, "?") ||
7815             StrStr(message, "result") || StrStr(message, "board") ||
7816             StrStr(message, "bk") || StrStr(message, "computer") ||
7817             StrStr(message, "variant") || StrStr(message, "hint") ||
7818             StrStr(message, "random") || StrStr(message, "depth") ||
7819             StrStr(message, "accepted")) {
7820             return;
7821         }
7822         if (StrStr(message, "protover")) {
7823           /* Program is responding to input, so it's apparently done
7824              initializing, and this error message indicates it is
7825              protocol version 1.  So we don't need to wait any longer
7826              for it to initialize and send feature commands. */
7827           FeatureDone(cps, 1);
7828           cps->protocolVersion = 1;
7829           return;
7830         }
7831         cps->maybeThinking = FALSE;
7832
7833         if (StrStr(message, "draw")) {
7834             /* Program doesn't have "draw" command */
7835             cps->sendDrawOffers = 0;
7836             return;
7837         }
7838         if (cps->sendTime != 1 &&
7839             (StrStr(message, "time") || StrStr(message, "otim"))) {
7840           /* Program apparently doesn't have "time" or "otim" command */
7841           cps->sendTime = 0;
7842           return;
7843         }
7844         if (StrStr(message, "analyze")) {
7845             cps->analysisSupport = FALSE;
7846             cps->analyzing = FALSE;
7847             Reset(FALSE, TRUE);
7848             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7849             DisplayError(buf2, 0);
7850             return;
7851         }
7852         if (StrStr(message, "(no matching move)st")) {
7853           /* Special kludge for GNU Chess 4 only */
7854           cps->stKludge = TRUE;
7855           SendTimeControl(cps, movesPerSession, timeControl,
7856                           timeIncrement, appData.searchDepth,
7857                           searchTime);
7858           return;
7859         }
7860         if (StrStr(message, "(no matching move)sd")) {
7861           /* Special kludge for GNU Chess 4 only */
7862           cps->sdKludge = TRUE;
7863           SendTimeControl(cps, movesPerSession, timeControl,
7864                           timeIncrement, appData.searchDepth,
7865                           searchTime);
7866           return;
7867         }
7868         if (!StrStr(message, "llegal")) {
7869             return;
7870         }
7871         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7872             gameMode == IcsIdle) return;
7873         if (forwardMostMove <= backwardMostMove) return;
7874         if (pausing) PauseEvent();
7875       if(appData.forceIllegal) {
7876             // [HGM] illegal: machine refused move; force position after move into it
7877           SendToProgram("force\n", cps);
7878           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7879                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7880                 // when black is to move, while there might be nothing on a2 or black
7881                 // might already have the move. So send the board as if white has the move.
7882                 // But first we must change the stm of the engine, as it refused the last move
7883                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7884                 if(WhiteOnMove(forwardMostMove)) {
7885                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7886                     SendBoard(cps, forwardMostMove); // kludgeless board
7887                 } else {
7888                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7889                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7890                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7891                 }
7892           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7893             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7894                  gameMode == TwoMachinesPlay)
7895               SendToProgram("go\n", cps);
7896             return;
7897       } else
7898         if (gameMode == PlayFromGameFile) {
7899             /* Stop reading this game file */
7900             gameMode = EditGame;
7901             ModeHighlight();
7902         }
7903         /* [HGM] illegal-move claim should forfeit game when Xboard */
7904         /* only passes fully legal moves                            */
7905         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7906             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7907                                 "False illegal-move claim", GE_XBOARD );
7908             return; // do not take back move we tested as valid
7909         }
7910         currentMove = forwardMostMove-1;
7911         DisplayMove(currentMove-1); /* before DisplayMoveError */
7912         SwitchClocks(forwardMostMove-1); // [HGM] race
7913         DisplayBothClocks();
7914         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7915                 parseList[currentMove], _(cps->which));
7916         DisplayMoveError(buf1);
7917         DrawPosition(FALSE, boards[currentMove]);
7918         return;
7919     }
7920     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7921         /* Program has a broken "time" command that
7922            outputs a string not ending in newline.
7923            Don't use it. */
7924         cps->sendTime = 0;
7925     }
7926
7927     /*
7928      * If chess program startup fails, exit with an error message.
7929      * Attempts to recover here are futile.
7930      */
7931     if ((StrStr(message, "unknown host") != NULL)
7932         || (StrStr(message, "No remote directory") != NULL)
7933         || (StrStr(message, "not found") != NULL)
7934         || (StrStr(message, "No such file") != NULL)
7935         || (StrStr(message, "can't alloc") != NULL)
7936         || (StrStr(message, "Permission denied") != NULL)) {
7937
7938         cps->maybeThinking = FALSE;
7939         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7940                 _(cps->which), cps->program, cps->host, message);
7941         RemoveInputSource(cps->isr);
7942         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
7943             if(cps == &first) appData.noChessProgram = TRUE;
7944             DisplayError(buf1, 0);
7945         }
7946         return;
7947     }
7948
7949     /*
7950      * Look for hint output
7951      */
7952     if (sscanf(message, "Hint: %s", buf1) == 1) {
7953         if (cps == &first && hintRequested) {
7954             hintRequested = FALSE;
7955             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7956                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7957                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7958                                     PosFlags(forwardMostMove),
7959                                     fromY, fromX, toY, toX, promoChar, buf1);
7960                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7961                 DisplayInformation(buf2);
7962             } else {
7963                 /* Hint move could not be parsed!? */
7964               snprintf(buf2, sizeof(buf2),
7965                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7966                         buf1, _(cps->which));
7967                 DisplayError(buf2, 0);
7968             }
7969         } else {
7970           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7971         }
7972         return;
7973     }
7974
7975     /*
7976      * Ignore other messages if game is not in progress
7977      */
7978     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7979         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7980
7981     /*
7982      * look for win, lose, draw, or draw offer
7983      */
7984     if (strncmp(message, "1-0", 3) == 0) {
7985         char *p, *q, *r = "";
7986         p = strchr(message, '{');
7987         if (p) {
7988             q = strchr(p, '}');
7989             if (q) {
7990                 *q = NULLCHAR;
7991                 r = p + 1;
7992             }
7993         }
7994         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7995         return;
7996     } else if (strncmp(message, "0-1", 3) == 0) {
7997         char *p, *q, *r = "";
7998         p = strchr(message, '{');
7999         if (p) {
8000             q = strchr(p, '}');
8001             if (q) {
8002                 *q = NULLCHAR;
8003                 r = p + 1;
8004             }
8005         }
8006         /* Kludge for Arasan 4.1 bug */
8007         if (strcmp(r, "Black resigns") == 0) {
8008             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8009             return;
8010         }
8011         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8012         return;
8013     } else if (strncmp(message, "1/2", 3) == 0) {
8014         char *p, *q, *r = "";
8015         p = strchr(message, '{');
8016         if (p) {
8017             q = strchr(p, '}');
8018             if (q) {
8019                 *q = NULLCHAR;
8020                 r = p + 1;
8021             }
8022         }
8023
8024         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8025         return;
8026
8027     } else if (strncmp(message, "White resign", 12) == 0) {
8028         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8029         return;
8030     } else if (strncmp(message, "Black resign", 12) == 0) {
8031         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8032         return;
8033     } else if (strncmp(message, "White matches", 13) == 0 ||
8034                strncmp(message, "Black matches", 13) == 0   ) {
8035         /* [HGM] ignore GNUShogi noises */
8036         return;
8037     } else if (strncmp(message, "White", 5) == 0 &&
8038                message[5] != '(' &&
8039                StrStr(message, "Black") == NULL) {
8040         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8041         return;
8042     } else if (strncmp(message, "Black", 5) == 0 &&
8043                message[5] != '(') {
8044         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8045         return;
8046     } else if (strcmp(message, "resign") == 0 ||
8047                strcmp(message, "computer resigns") == 0) {
8048         switch (gameMode) {
8049           case MachinePlaysBlack:
8050           case IcsPlayingBlack:
8051             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8052             break;
8053           case MachinePlaysWhite:
8054           case IcsPlayingWhite:
8055             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8056             break;
8057           case TwoMachinesPlay:
8058             if (cps->twoMachinesColor[0] == 'w')
8059               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8060             else
8061               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8062             break;
8063           default:
8064             /* can't happen */
8065             break;
8066         }
8067         return;
8068     } else if (strncmp(message, "opponent mates", 14) == 0) {
8069         switch (gameMode) {
8070           case MachinePlaysBlack:
8071           case IcsPlayingBlack:
8072             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8073             break;
8074           case MachinePlaysWhite:
8075           case IcsPlayingWhite:
8076             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8077             break;
8078           case TwoMachinesPlay:
8079             if (cps->twoMachinesColor[0] == 'w')
8080               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8081             else
8082               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8083             break;
8084           default:
8085             /* can't happen */
8086             break;
8087         }
8088         return;
8089     } else if (strncmp(message, "computer mates", 14) == 0) {
8090         switch (gameMode) {
8091           case MachinePlaysBlack:
8092           case IcsPlayingBlack:
8093             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8094             break;
8095           case MachinePlaysWhite:
8096           case IcsPlayingWhite:
8097             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8098             break;
8099           case TwoMachinesPlay:
8100             if (cps->twoMachinesColor[0] == 'w')
8101               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8102             else
8103               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8104             break;
8105           default:
8106             /* can't happen */
8107             break;
8108         }
8109         return;
8110     } else if (strncmp(message, "checkmate", 9) == 0) {
8111         if (WhiteOnMove(forwardMostMove)) {
8112             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8113         } else {
8114             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8115         }
8116         return;
8117     } else if (strstr(message, "Draw") != NULL ||
8118                strstr(message, "game is a draw") != NULL) {
8119         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8120         return;
8121     } else if (strstr(message, "offer") != NULL &&
8122                strstr(message, "draw") != NULL) {
8123 #if ZIPPY
8124         if (appData.zippyPlay && first.initDone) {
8125             /* Relay offer to ICS */
8126             SendToICS(ics_prefix);
8127             SendToICS("draw\n");
8128         }
8129 #endif
8130         cps->offeredDraw = 2; /* valid until this engine moves twice */
8131         if (gameMode == TwoMachinesPlay) {
8132             if (cps->other->offeredDraw) {
8133                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8134             /* [HGM] in two-machine mode we delay relaying draw offer      */
8135             /* until after we also have move, to see if it is really claim */
8136             }
8137         } else if (gameMode == MachinePlaysWhite ||
8138                    gameMode == MachinePlaysBlack) {
8139           if (userOfferedDraw) {
8140             DisplayInformation(_("Machine accepts your draw offer"));
8141             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8142           } else {
8143             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8144           }
8145         }
8146     }
8147
8148
8149     /*
8150      * Look for thinking output
8151      */
8152     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8153           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8154                                 ) {
8155         int plylev, mvleft, mvtot, curscore, time;
8156         char mvname[MOVE_LEN];
8157         u64 nodes; // [DM]
8158         char plyext;
8159         int ignore = FALSE;
8160         int prefixHint = FALSE;
8161         mvname[0] = NULLCHAR;
8162
8163         switch (gameMode) {
8164           case MachinePlaysBlack:
8165           case IcsPlayingBlack:
8166             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8167             break;
8168           case MachinePlaysWhite:
8169           case IcsPlayingWhite:
8170             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8171             break;
8172           case AnalyzeMode:
8173           case AnalyzeFile:
8174             break;
8175           case IcsObserving: /* [DM] icsEngineAnalyze */
8176             if (!appData.icsEngineAnalyze) ignore = TRUE;
8177             break;
8178           case TwoMachinesPlay:
8179             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8180                 ignore = TRUE;
8181             }
8182             break;
8183           default:
8184             ignore = TRUE;
8185             break;
8186         }
8187
8188         if (!ignore) {
8189             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8190             buf1[0] = NULLCHAR;
8191             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8192                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8193
8194                 if (plyext != ' ' && plyext != '\t') {
8195                     time *= 100;
8196                 }
8197
8198                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8199                 if( cps->scoreIsAbsolute &&
8200                     ( gameMode == MachinePlaysBlack ||
8201                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8202                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8203                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8204                      !WhiteOnMove(currentMove)
8205                     ) )
8206                 {
8207                     curscore = -curscore;
8208                 }
8209
8210
8211                 tempStats.depth = plylev;
8212                 tempStats.nodes = nodes;
8213                 tempStats.time = time;
8214                 tempStats.score = curscore;
8215                 tempStats.got_only_move = 0;
8216
8217                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8218                         int ticklen;
8219
8220                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8221                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8222                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8223                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8224                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8225                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8226                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8227                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8228                 }
8229
8230                 /* Buffer overflow protection */
8231                 if (buf1[0] != NULLCHAR) {
8232                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8233                         && appData.debugMode) {
8234                         fprintf(debugFP,
8235                                 "PV is too long; using the first %u bytes.\n",
8236                                 (unsigned) sizeof(tempStats.movelist) - 1);
8237                     }
8238
8239                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8240                 } else {
8241                     sprintf(tempStats.movelist, " no PV\n");
8242                 }
8243
8244                 if (tempStats.seen_stat) {
8245                     tempStats.ok_to_send = 1;
8246                 }
8247
8248                 if (strchr(tempStats.movelist, '(') != NULL) {
8249                     tempStats.line_is_book = 1;
8250                     tempStats.nr_moves = 0;
8251                     tempStats.moves_left = 0;
8252                 } else {
8253                     tempStats.line_is_book = 0;
8254                 }
8255
8256                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8257                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8258
8259                 SendProgramStatsToFrontend( cps, &tempStats );
8260
8261                 /*
8262                     [AS] Protect the thinkOutput buffer from overflow... this
8263                     is only useful if buf1 hasn't overflowed first!
8264                 */
8265                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8266                          plylev,
8267                          (gameMode == TwoMachinesPlay ?
8268                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8269                          ((double) curscore) / 100.0,
8270                          prefixHint ? lastHint : "",
8271                          prefixHint ? " " : "" );
8272
8273                 if( buf1[0] != NULLCHAR ) {
8274                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8275
8276                     if( strlen(buf1) > max_len ) {
8277                         if( appData.debugMode) {
8278                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8279                         }
8280                         buf1[max_len+1] = '\0';
8281                     }
8282
8283                     strcat( thinkOutput, buf1 );
8284                 }
8285
8286                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8287                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8288                     DisplayMove(currentMove - 1);
8289                 }
8290                 return;
8291
8292             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8293                 /* crafty (9.25+) says "(only move) <move>"
8294                  * if there is only 1 legal move
8295                  */
8296                 sscanf(p, "(only move) %s", buf1);
8297                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8298                 sprintf(programStats.movelist, "%s (only move)", buf1);
8299                 programStats.depth = 1;
8300                 programStats.nr_moves = 1;
8301                 programStats.moves_left = 1;
8302                 programStats.nodes = 1;
8303                 programStats.time = 1;
8304                 programStats.got_only_move = 1;
8305
8306                 /* Not really, but we also use this member to
8307                    mean "line isn't going to change" (Crafty
8308                    isn't searching, so stats won't change) */
8309                 programStats.line_is_book = 1;
8310
8311                 SendProgramStatsToFrontend( cps, &programStats );
8312
8313                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8314                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8315                     DisplayMove(currentMove - 1);
8316                 }
8317                 return;
8318             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8319                               &time, &nodes, &plylev, &mvleft,
8320                               &mvtot, mvname) >= 5) {
8321                 /* The stat01: line is from Crafty (9.29+) in response
8322                    to the "." command */
8323                 programStats.seen_stat = 1;
8324                 cps->maybeThinking = TRUE;
8325
8326                 if (programStats.got_only_move || !appData.periodicUpdates)
8327                   return;
8328
8329                 programStats.depth = plylev;
8330                 programStats.time = time;
8331                 programStats.nodes = nodes;
8332                 programStats.moves_left = mvleft;
8333                 programStats.nr_moves = mvtot;
8334                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8335                 programStats.ok_to_send = 1;
8336                 programStats.movelist[0] = '\0';
8337
8338                 SendProgramStatsToFrontend( cps, &programStats );
8339
8340                 return;
8341
8342             } else if (strncmp(message,"++",2) == 0) {
8343                 /* Crafty 9.29+ outputs this */
8344                 programStats.got_fail = 2;
8345                 return;
8346
8347             } else if (strncmp(message,"--",2) == 0) {
8348                 /* Crafty 9.29+ outputs this */
8349                 programStats.got_fail = 1;
8350                 return;
8351
8352             } else if (thinkOutput[0] != NULLCHAR &&
8353                        strncmp(message, "    ", 4) == 0) {
8354                 unsigned message_len;
8355
8356                 p = message;
8357                 while (*p && *p == ' ') p++;
8358
8359                 message_len = strlen( p );
8360
8361                 /* [AS] Avoid buffer overflow */
8362                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8363                     strcat(thinkOutput, " ");
8364                     strcat(thinkOutput, p);
8365                 }
8366
8367                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8368                     strcat(programStats.movelist, " ");
8369                     strcat(programStats.movelist, p);
8370                 }
8371
8372                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8373                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8374                     DisplayMove(currentMove - 1);
8375                 }
8376                 return;
8377             }
8378         }
8379         else {
8380             buf1[0] = NULLCHAR;
8381
8382             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8383                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8384             {
8385                 ChessProgramStats cpstats;
8386
8387                 if (plyext != ' ' && plyext != '\t') {
8388                     time *= 100;
8389                 }
8390
8391                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8392                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8393                     curscore = -curscore;
8394                 }
8395
8396                 cpstats.depth = plylev;
8397                 cpstats.nodes = nodes;
8398                 cpstats.time = time;
8399                 cpstats.score = curscore;
8400                 cpstats.got_only_move = 0;
8401                 cpstats.movelist[0] = '\0';
8402
8403                 if (buf1[0] != NULLCHAR) {
8404                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8405                 }
8406
8407                 cpstats.ok_to_send = 0;
8408                 cpstats.line_is_book = 0;
8409                 cpstats.nr_moves = 0;
8410                 cpstats.moves_left = 0;
8411
8412                 SendProgramStatsToFrontend( cps, &cpstats );
8413             }
8414         }
8415     }
8416 }
8417
8418
8419 /* Parse a game score from the character string "game", and
8420    record it as the history of the current game.  The game
8421    score is NOT assumed to start from the standard position.
8422    The display is not updated in any way.
8423    */
8424 void
8425 ParseGameHistory(game)
8426      char *game;
8427 {
8428     ChessMove moveType;
8429     int fromX, fromY, toX, toY, boardIndex;
8430     char promoChar;
8431     char *p, *q;
8432     char buf[MSG_SIZ];
8433
8434     if (appData.debugMode)
8435       fprintf(debugFP, "Parsing game history: %s\n", game);
8436
8437     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8438     gameInfo.site = StrSave(appData.icsHost);
8439     gameInfo.date = PGNDate();
8440     gameInfo.round = StrSave("-");
8441
8442     /* Parse out names of players */
8443     while (*game == ' ') game++;
8444     p = buf;
8445     while (*game != ' ') *p++ = *game++;
8446     *p = NULLCHAR;
8447     gameInfo.white = StrSave(buf);
8448     while (*game == ' ') game++;
8449     p = buf;
8450     while (*game != ' ' && *game != '\n') *p++ = *game++;
8451     *p = NULLCHAR;
8452     gameInfo.black = StrSave(buf);
8453
8454     /* Parse moves */
8455     boardIndex = blackPlaysFirst ? 1 : 0;
8456     yynewstr(game);
8457     for (;;) {
8458         yyboardindex = boardIndex;
8459         moveType = (ChessMove) Myylex();
8460         switch (moveType) {
8461           case IllegalMove:             /* maybe suicide chess, etc. */
8462   if (appData.debugMode) {
8463     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8464     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8465     setbuf(debugFP, NULL);
8466   }
8467           case WhitePromotion:
8468           case BlackPromotion:
8469           case WhiteNonPromotion:
8470           case BlackNonPromotion:
8471           case NormalMove:
8472           case WhiteCapturesEnPassant:
8473           case BlackCapturesEnPassant:
8474           case WhiteKingSideCastle:
8475           case WhiteQueenSideCastle:
8476           case BlackKingSideCastle:
8477           case BlackQueenSideCastle:
8478           case WhiteKingSideCastleWild:
8479           case WhiteQueenSideCastleWild:
8480           case BlackKingSideCastleWild:
8481           case BlackQueenSideCastleWild:
8482           /* PUSH Fabien */
8483           case WhiteHSideCastleFR:
8484           case WhiteASideCastleFR:
8485           case BlackHSideCastleFR:
8486           case BlackASideCastleFR:
8487           /* POP Fabien */
8488             fromX = currentMoveString[0] - AAA;
8489             fromY = currentMoveString[1] - ONE;
8490             toX = currentMoveString[2] - AAA;
8491             toY = currentMoveString[3] - ONE;
8492             promoChar = currentMoveString[4];
8493             break;
8494           case WhiteDrop:
8495           case BlackDrop:
8496             fromX = moveType == WhiteDrop ?
8497               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8498             (int) CharToPiece(ToLower(currentMoveString[0]));
8499             fromY = DROP_RANK;
8500             toX = currentMoveString[2] - AAA;
8501             toY = currentMoveString[3] - ONE;
8502             promoChar = NULLCHAR;
8503             break;
8504           case AmbiguousMove:
8505             /* bug? */
8506             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8507   if (appData.debugMode) {
8508     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8509     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8510     setbuf(debugFP, NULL);
8511   }
8512             DisplayError(buf, 0);
8513             return;
8514           case ImpossibleMove:
8515             /* bug? */
8516             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8517   if (appData.debugMode) {
8518     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8519     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8520     setbuf(debugFP, NULL);
8521   }
8522             DisplayError(buf, 0);
8523             return;
8524           case EndOfFile:
8525             if (boardIndex < backwardMostMove) {
8526                 /* Oops, gap.  How did that happen? */
8527                 DisplayError(_("Gap in move list"), 0);
8528                 return;
8529             }
8530             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8531             if (boardIndex > forwardMostMove) {
8532                 forwardMostMove = boardIndex;
8533             }
8534             return;
8535           case ElapsedTime:
8536             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8537                 strcat(parseList[boardIndex-1], " ");
8538                 strcat(parseList[boardIndex-1], yy_text);
8539             }
8540             continue;
8541           case Comment:
8542           case PGNTag:
8543           case NAG:
8544           default:
8545             /* ignore */
8546             continue;
8547           case WhiteWins:
8548           case BlackWins:
8549           case GameIsDrawn:
8550           case GameUnfinished:
8551             if (gameMode == IcsExamining) {
8552                 if (boardIndex < backwardMostMove) {
8553                     /* Oops, gap.  How did that happen? */
8554                     return;
8555                 }
8556                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8557                 return;
8558             }
8559             gameInfo.result = moveType;
8560             p = strchr(yy_text, '{');
8561             if (p == NULL) p = strchr(yy_text, '(');
8562             if (p == NULL) {
8563                 p = yy_text;
8564                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8565             } else {
8566                 q = strchr(p, *p == '{' ? '}' : ')');
8567                 if (q != NULL) *q = NULLCHAR;
8568                 p++;
8569             }
8570             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8571             gameInfo.resultDetails = StrSave(p);
8572             continue;
8573         }
8574         if (boardIndex >= forwardMostMove &&
8575             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8576             backwardMostMove = blackPlaysFirst ? 1 : 0;
8577             return;
8578         }
8579         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8580                                  fromY, fromX, toY, toX, promoChar,
8581                                  parseList[boardIndex]);
8582         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8583         /* currentMoveString is set as a side-effect of yylex */
8584         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8585         strcat(moveList[boardIndex], "\n");
8586         boardIndex++;
8587         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8588         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8589           case MT_NONE:
8590           case MT_STALEMATE:
8591           default:
8592             break;
8593           case MT_CHECK:
8594             if(gameInfo.variant != VariantShogi)
8595                 strcat(parseList[boardIndex - 1], "+");
8596             break;
8597           case MT_CHECKMATE:
8598           case MT_STAINMATE:
8599             strcat(parseList[boardIndex - 1], "#");
8600             break;
8601         }
8602     }
8603 }
8604
8605
8606 /* Apply a move to the given board  */
8607 void
8608 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8609      int fromX, fromY, toX, toY;
8610      int promoChar;
8611      Board board;
8612 {
8613   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8614   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8615
8616     /* [HGM] compute & store e.p. status and castling rights for new position */
8617     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8618
8619       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8620       oldEP = (signed char)board[EP_STATUS];
8621       board[EP_STATUS] = EP_NONE;
8622
8623       if( board[toY][toX] != EmptySquare )
8624            board[EP_STATUS] = EP_CAPTURE;
8625
8626   if (fromY == DROP_RANK) {
8627         /* must be first */
8628         piece = board[toY][toX] = (ChessSquare) fromX;
8629   } else {
8630       int i;
8631
8632       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8633            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8634                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8635       } else
8636       if( board[fromY][fromX] == WhitePawn ) {
8637            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8638                board[EP_STATUS] = EP_PAWN_MOVE;
8639            if( toY-fromY==2) {
8640                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8641                         gameInfo.variant != VariantBerolina || toX < fromX)
8642                       board[EP_STATUS] = toX | berolina;
8643                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8644                         gameInfo.variant != VariantBerolina || toX > fromX)
8645                       board[EP_STATUS] = toX;
8646            }
8647       } else
8648       if( board[fromY][fromX] == BlackPawn ) {
8649            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8650                board[EP_STATUS] = EP_PAWN_MOVE;
8651            if( toY-fromY== -2) {
8652                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8653                         gameInfo.variant != VariantBerolina || toX < fromX)
8654                       board[EP_STATUS] = toX | berolina;
8655                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8656                         gameInfo.variant != VariantBerolina || toX > fromX)
8657                       board[EP_STATUS] = toX;
8658            }
8659        }
8660
8661        for(i=0; i<nrCastlingRights; i++) {
8662            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8663               board[CASTLING][i] == toX   && castlingRank[i] == toY
8664              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8665        }
8666
8667      if (fromX == toX && fromY == toY) return;
8668
8669      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8670      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8671      if(gameInfo.variant == VariantKnightmate)
8672          king += (int) WhiteUnicorn - (int) WhiteKing;
8673
8674     /* Code added by Tord: */
8675     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8676     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8677         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8678       board[fromY][fromX] = EmptySquare;
8679       board[toY][toX] = EmptySquare;
8680       if((toX > fromX) != (piece == WhiteRook)) {
8681         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8682       } else {
8683         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8684       }
8685     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8686                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8687       board[fromY][fromX] = EmptySquare;
8688       board[toY][toX] = EmptySquare;
8689       if((toX > fromX) != (piece == BlackRook)) {
8690         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8691       } else {
8692         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8693       }
8694     /* End of code added by Tord */
8695
8696     } else if (board[fromY][fromX] == king
8697         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8698         && toY == fromY && toX > fromX+1) {
8699         board[fromY][fromX] = EmptySquare;
8700         board[toY][toX] = king;
8701         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8702         board[fromY][BOARD_RGHT-1] = EmptySquare;
8703     } else if (board[fromY][fromX] == king
8704         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8705                && toY == fromY && toX < fromX-1) {
8706         board[fromY][fromX] = EmptySquare;
8707         board[toY][toX] = king;
8708         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8709         board[fromY][BOARD_LEFT] = EmptySquare;
8710     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8711                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8712                && toY >= BOARD_HEIGHT-promoRank
8713                ) {
8714         /* white pawn promotion */
8715         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8716         if (board[toY][toX] == EmptySquare) {
8717             board[toY][toX] = WhiteQueen;
8718         }
8719         if(gameInfo.variant==VariantBughouse ||
8720            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8721             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8722         board[fromY][fromX] = EmptySquare;
8723     } else if ((fromY == BOARD_HEIGHT-4)
8724                && (toX != fromX)
8725                && gameInfo.variant != VariantXiangqi
8726                && gameInfo.variant != VariantBerolina
8727                && (board[fromY][fromX] == WhitePawn)
8728                && (board[toY][toX] == EmptySquare)) {
8729         board[fromY][fromX] = EmptySquare;
8730         board[toY][toX] = WhitePawn;
8731         captured = board[toY - 1][toX];
8732         board[toY - 1][toX] = EmptySquare;
8733     } else if ((fromY == BOARD_HEIGHT-4)
8734                && (toX == fromX)
8735                && gameInfo.variant == VariantBerolina
8736                && (board[fromY][fromX] == WhitePawn)
8737                && (board[toY][toX] == EmptySquare)) {
8738         board[fromY][fromX] = EmptySquare;
8739         board[toY][toX] = WhitePawn;
8740         if(oldEP & EP_BEROLIN_A) {
8741                 captured = board[fromY][fromX-1];
8742                 board[fromY][fromX-1] = EmptySquare;
8743         }else{  captured = board[fromY][fromX+1];
8744                 board[fromY][fromX+1] = EmptySquare;
8745         }
8746     } else if (board[fromY][fromX] == king
8747         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8748                && toY == fromY && toX > fromX+1) {
8749         board[fromY][fromX] = EmptySquare;
8750         board[toY][toX] = king;
8751         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8752         board[fromY][BOARD_RGHT-1] = EmptySquare;
8753     } else if (board[fromY][fromX] == king
8754         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8755                && toY == fromY && toX < fromX-1) {
8756         board[fromY][fromX] = EmptySquare;
8757         board[toY][toX] = king;
8758         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8759         board[fromY][BOARD_LEFT] = EmptySquare;
8760     } else if (fromY == 7 && fromX == 3
8761                && board[fromY][fromX] == BlackKing
8762                && toY == 7 && toX == 5) {
8763         board[fromY][fromX] = EmptySquare;
8764         board[toY][toX] = BlackKing;
8765         board[fromY][7] = EmptySquare;
8766         board[toY][4] = BlackRook;
8767     } else if (fromY == 7 && fromX == 3
8768                && board[fromY][fromX] == BlackKing
8769                && toY == 7 && toX == 1) {
8770         board[fromY][fromX] = EmptySquare;
8771         board[toY][toX] = BlackKing;
8772         board[fromY][0] = EmptySquare;
8773         board[toY][2] = BlackRook;
8774     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8775                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8776                && toY < promoRank
8777                ) {
8778         /* black pawn promotion */
8779         board[toY][toX] = CharToPiece(ToLower(promoChar));
8780         if (board[toY][toX] == EmptySquare) {
8781             board[toY][toX] = BlackQueen;
8782         }
8783         if(gameInfo.variant==VariantBughouse ||
8784            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8785             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8786         board[fromY][fromX] = EmptySquare;
8787     } else if ((fromY == 3)
8788                && (toX != fromX)
8789                && gameInfo.variant != VariantXiangqi
8790                && gameInfo.variant != VariantBerolina
8791                && (board[fromY][fromX] == BlackPawn)
8792                && (board[toY][toX] == EmptySquare)) {
8793         board[fromY][fromX] = EmptySquare;
8794         board[toY][toX] = BlackPawn;
8795         captured = board[toY + 1][toX];
8796         board[toY + 1][toX] = EmptySquare;
8797     } else if ((fromY == 3)
8798                && (toX == fromX)
8799                && gameInfo.variant == VariantBerolina
8800                && (board[fromY][fromX] == BlackPawn)
8801                && (board[toY][toX] == EmptySquare)) {
8802         board[fromY][fromX] = EmptySquare;
8803         board[toY][toX] = BlackPawn;
8804         if(oldEP & EP_BEROLIN_A) {
8805                 captured = board[fromY][fromX-1];
8806                 board[fromY][fromX-1] = EmptySquare;
8807         }else{  captured = board[fromY][fromX+1];
8808                 board[fromY][fromX+1] = EmptySquare;
8809         }
8810     } else {
8811         board[toY][toX] = board[fromY][fromX];
8812         board[fromY][fromX] = EmptySquare;
8813     }
8814   }
8815
8816     if (gameInfo.holdingsWidth != 0) {
8817
8818       /* !!A lot more code needs to be written to support holdings  */
8819       /* [HGM] OK, so I have written it. Holdings are stored in the */
8820       /* penultimate board files, so they are automaticlly stored   */
8821       /* in the game history.                                       */
8822       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8823                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8824         /* Delete from holdings, by decreasing count */
8825         /* and erasing image if necessary            */
8826         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8827         if(p < (int) BlackPawn) { /* white drop */
8828              p -= (int)WhitePawn;
8829                  p = PieceToNumber((ChessSquare)p);
8830              if(p >= gameInfo.holdingsSize) p = 0;
8831              if(--board[p][BOARD_WIDTH-2] <= 0)
8832                   board[p][BOARD_WIDTH-1] = EmptySquare;
8833              if((int)board[p][BOARD_WIDTH-2] < 0)
8834                         board[p][BOARD_WIDTH-2] = 0;
8835         } else {                  /* black drop */
8836              p -= (int)BlackPawn;
8837                  p = PieceToNumber((ChessSquare)p);
8838              if(p >= gameInfo.holdingsSize) p = 0;
8839              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8840                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8841              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8842                         board[BOARD_HEIGHT-1-p][1] = 0;
8843         }
8844       }
8845       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8846           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8847         /* [HGM] holdings: Add to holdings, if holdings exist */
8848         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8849                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8850                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8851         }
8852         p = (int) captured;
8853         if (p >= (int) BlackPawn) {
8854           p -= (int)BlackPawn;
8855           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8856                   /* in Shogi restore piece to its original  first */
8857                   captured = (ChessSquare) (DEMOTED captured);
8858                   p = DEMOTED p;
8859           }
8860           p = PieceToNumber((ChessSquare)p);
8861           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8862           board[p][BOARD_WIDTH-2]++;
8863           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8864         } else {
8865           p -= (int)WhitePawn;
8866           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8867                   captured = (ChessSquare) (DEMOTED captured);
8868                   p = DEMOTED p;
8869           }
8870           p = PieceToNumber((ChessSquare)p);
8871           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8872           board[BOARD_HEIGHT-1-p][1]++;
8873           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8874         }
8875       }
8876     } else if (gameInfo.variant == VariantAtomic) {
8877       if (captured != EmptySquare) {
8878         int y, x;
8879         for (y = toY-1; y <= toY+1; y++) {
8880           for (x = toX-1; x <= toX+1; x++) {
8881             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8882                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8883               board[y][x] = EmptySquare;
8884             }
8885           }
8886         }
8887         board[toY][toX] = EmptySquare;
8888       }
8889     }
8890     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8891         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8892     } else
8893     if(promoChar == '+') {
8894         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8895         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8896     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8897         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8898     }
8899     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8900                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8901         // [HGM] superchess: take promotion piece out of holdings
8902         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8903         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8904             if(!--board[k][BOARD_WIDTH-2])
8905                 board[k][BOARD_WIDTH-1] = EmptySquare;
8906         } else {
8907             if(!--board[BOARD_HEIGHT-1-k][1])
8908                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8909         }
8910     }
8911
8912 }
8913
8914 /* Updates forwardMostMove */
8915 void
8916 MakeMove(fromX, fromY, toX, toY, promoChar)
8917      int fromX, fromY, toX, toY;
8918      int promoChar;
8919 {
8920 //    forwardMostMove++; // [HGM] bare: moved downstream
8921
8922     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8923         int timeLeft; static int lastLoadFlag=0; int king, piece;
8924         piece = boards[forwardMostMove][fromY][fromX];
8925         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8926         if(gameInfo.variant == VariantKnightmate)
8927             king += (int) WhiteUnicorn - (int) WhiteKing;
8928         if(forwardMostMove == 0) {
8929             if(blackPlaysFirst)
8930                 fprintf(serverMoves, "%s;", second.tidy);
8931             fprintf(serverMoves, "%s;", first.tidy);
8932             if(!blackPlaysFirst)
8933                 fprintf(serverMoves, "%s;", second.tidy);
8934         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8935         lastLoadFlag = loadFlag;
8936         // print base move
8937         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8938         // print castling suffix
8939         if( toY == fromY && piece == king ) {
8940             if(toX-fromX > 1)
8941                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8942             if(fromX-toX >1)
8943                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8944         }
8945         // e.p. suffix
8946         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8947              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8948              boards[forwardMostMove][toY][toX] == EmptySquare
8949              && fromX != toX && fromY != toY)
8950                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8951         // promotion suffix
8952         if(promoChar != NULLCHAR)
8953                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8954         if(!loadFlag) {
8955             fprintf(serverMoves, "/%d/%d",
8956                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8957             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8958             else                      timeLeft = blackTimeRemaining/1000;
8959             fprintf(serverMoves, "/%d", timeLeft);
8960         }
8961         fflush(serverMoves);
8962     }
8963
8964     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8965       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8966                         0, 1);
8967       return;
8968     }
8969     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8970     if (commentList[forwardMostMove+1] != NULL) {
8971         free(commentList[forwardMostMove+1]);
8972         commentList[forwardMostMove+1] = NULL;
8973     }
8974     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8975     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8976     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8977     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8978     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8979     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8980     gameInfo.result = GameUnfinished;
8981     if (gameInfo.resultDetails != NULL) {
8982         free(gameInfo.resultDetails);
8983         gameInfo.resultDetails = NULL;
8984     }
8985     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8986                               moveList[forwardMostMove - 1]);
8987     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8988                              PosFlags(forwardMostMove - 1),
8989                              fromY, fromX, toY, toX, promoChar,
8990                              parseList[forwardMostMove - 1]);
8991     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8992       case MT_NONE:
8993       case MT_STALEMATE:
8994       default:
8995         break;
8996       case MT_CHECK:
8997         if(gameInfo.variant != VariantShogi)
8998             strcat(parseList[forwardMostMove - 1], "+");
8999         break;
9000       case MT_CHECKMATE:
9001       case MT_STAINMATE:
9002         strcat(parseList[forwardMostMove - 1], "#");
9003         break;
9004     }
9005     if (appData.debugMode) {
9006         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9007     }
9008
9009 }
9010
9011 /* Updates currentMove if not pausing */
9012 void
9013 ShowMove(fromX, fromY, toX, toY)
9014 {
9015     int instant = (gameMode == PlayFromGameFile) ?
9016         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9017     if(appData.noGUI) return;
9018     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9019         if (!instant) {
9020             if (forwardMostMove == currentMove + 1) {
9021                 AnimateMove(boards[forwardMostMove - 1],
9022                             fromX, fromY, toX, toY);
9023             }
9024             if (appData.highlightLastMove) {
9025                 SetHighlights(fromX, fromY, toX, toY);
9026             }
9027         }
9028         currentMove = forwardMostMove;
9029     }
9030
9031     if (instant) return;
9032
9033     DisplayMove(currentMove - 1);
9034     DrawPosition(FALSE, boards[currentMove]);
9035     DisplayBothClocks();
9036     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9037 }
9038
9039 void SendEgtPath(ChessProgramState *cps)
9040 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9041         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9042
9043         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9044
9045         while(*p) {
9046             char c, *q = name+1, *r, *s;
9047
9048             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9049             while(*p && *p != ',') *q++ = *p++;
9050             *q++ = ':'; *q = 0;
9051             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9052                 strcmp(name, ",nalimov:") == 0 ) {
9053                 // take nalimov path from the menu-changeable option first, if it is defined
9054               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9055                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9056             } else
9057             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9058                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9059                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9060                 s = r = StrStr(s, ":") + 1; // beginning of path info
9061                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9062                 c = *r; *r = 0;             // temporarily null-terminate path info
9063                     *--q = 0;               // strip of trailig ':' from name
9064                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9065                 *r = c;
9066                 SendToProgram(buf,cps);     // send egtbpath command for this format
9067             }
9068             if(*p == ',') p++; // read away comma to position for next format name
9069         }
9070 }
9071
9072 void
9073 InitChessProgram(cps, setup)
9074      ChessProgramState *cps;
9075      int setup; /* [HGM] needed to setup FRC opening position */
9076 {
9077     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9078     if (appData.noChessProgram) return;
9079     hintRequested = FALSE;
9080     bookRequested = FALSE;
9081
9082     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9083     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9084     if(cps->memSize) { /* [HGM] memory */
9085       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9086         SendToProgram(buf, cps);
9087     }
9088     SendEgtPath(cps); /* [HGM] EGT */
9089     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9090       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9091         SendToProgram(buf, cps);
9092     }
9093
9094     SendToProgram(cps->initString, cps);
9095     if (gameInfo.variant != VariantNormal &&
9096         gameInfo.variant != VariantLoadable
9097         /* [HGM] also send variant if board size non-standard */
9098         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9099                                             ) {
9100       char *v = VariantName(gameInfo.variant);
9101       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9102         /* [HGM] in protocol 1 we have to assume all variants valid */
9103         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9104         DisplayFatalError(buf, 0, 1);
9105         return;
9106       }
9107
9108       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9109       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9110       if( gameInfo.variant == VariantXiangqi )
9111            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9112       if( gameInfo.variant == VariantShogi )
9113            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9114       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9115            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9116       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9117           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9118            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9119       if( gameInfo.variant == VariantCourier )
9120            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9121       if( gameInfo.variant == VariantSuper )
9122            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9123       if( gameInfo.variant == VariantGreat )
9124            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9125       if( gameInfo.variant == VariantSChess )
9126            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9127
9128       if(overruled) {
9129         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9130                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9131            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9132            if(StrStr(cps->variants, b) == NULL) {
9133                // specific sized variant not known, check if general sizing allowed
9134                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9135                    if(StrStr(cps->variants, "boardsize") == NULL) {
9136                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9137                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9138                        DisplayFatalError(buf, 0, 1);
9139                        return;
9140                    }
9141                    /* [HGM] here we really should compare with the maximum supported board size */
9142                }
9143            }
9144       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9145       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9146       SendToProgram(buf, cps);
9147     }
9148     currentlyInitializedVariant = gameInfo.variant;
9149
9150     /* [HGM] send opening position in FRC to first engine */
9151     if(setup) {
9152           SendToProgram("force\n", cps);
9153           SendBoard(cps, 0);
9154           /* engine is now in force mode! Set flag to wake it up after first move. */
9155           setboardSpoiledMachineBlack = 1;
9156     }
9157
9158     if (cps->sendICS) {
9159       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9160       SendToProgram(buf, cps);
9161     }
9162     cps->maybeThinking = FALSE;
9163     cps->offeredDraw = 0;
9164     if (!appData.icsActive) {
9165         SendTimeControl(cps, movesPerSession, timeControl,
9166                         timeIncrement, appData.searchDepth,
9167                         searchTime);
9168     }
9169     if (appData.showThinking
9170         // [HGM] thinking: four options require thinking output to be sent
9171         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9172                                 ) {
9173         SendToProgram("post\n", cps);
9174     }
9175     SendToProgram("hard\n", cps);
9176     if (!appData.ponderNextMove) {
9177         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9178            it without being sure what state we are in first.  "hard"
9179            is not a toggle, so that one is OK.
9180          */
9181         SendToProgram("easy\n", cps);
9182     }
9183     if (cps->usePing) {
9184       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9185       SendToProgram(buf, cps);
9186     }
9187     cps->initDone = TRUE;
9188 }
9189
9190
9191 void
9192 StartChessProgram(cps)
9193      ChessProgramState *cps;
9194 {
9195     char buf[MSG_SIZ];
9196     int err;
9197
9198     if (appData.noChessProgram) return;
9199     cps->initDone = FALSE;
9200
9201     if (strcmp(cps->host, "localhost") == 0) {
9202         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9203     } else if (*appData.remoteShell == NULLCHAR) {
9204         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9205     } else {
9206         if (*appData.remoteUser == NULLCHAR) {
9207           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9208                     cps->program);
9209         } else {
9210           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9211                     cps->host, appData.remoteUser, cps->program);
9212         }
9213         err = StartChildProcess(buf, "", &cps->pr);
9214     }
9215
9216     if (err != 0) {
9217       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9218         DisplayFatalError(buf, err, 1);
9219         cps->pr = NoProc;
9220         cps->isr = NULL;
9221         return;
9222     }
9223
9224     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9225     if (cps->protocolVersion > 1) {
9226       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9227       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9228       cps->comboCnt = 0;  //                and values of combo boxes
9229       SendToProgram(buf, cps);
9230     } else {
9231       SendToProgram("xboard\n", cps);
9232     }
9233 }
9234
9235
9236 void
9237 TwoMachinesEventIfReady P((void))
9238 {
9239   if (first.lastPing != first.lastPong) {
9240     DisplayMessage("", _("Waiting for first chess program"));
9241     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9242     return;
9243   }
9244   if (second.lastPing != second.lastPong) {
9245     DisplayMessage("", _("Waiting for second chess program"));
9246     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9247     return;
9248   }
9249   ThawUI();
9250   TwoMachinesEvent();
9251 }
9252
9253 void
9254 NextMatchGame P((void))
9255 {
9256     int index; /* [HGM] autoinc: step load index during match */
9257     Reset(FALSE, TRUE);
9258     if (*appData.loadGameFile != NULLCHAR) {
9259         index = appData.loadGameIndex;
9260         if(index < 0) { // [HGM] autoinc
9261             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9262             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9263         }
9264         LoadGameFromFile(appData.loadGameFile,
9265                          index,
9266                          appData.loadGameFile, FALSE);
9267     } else if (*appData.loadPositionFile != NULLCHAR) {
9268         index = appData.loadPositionIndex;
9269         if(index < 0) { // [HGM] autoinc
9270             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9271             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9272         }
9273         LoadPositionFromFile(appData.loadPositionFile,
9274                              index,
9275                              appData.loadPositionFile);
9276     }
9277     TwoMachinesEventIfReady();
9278 }
9279
9280 void UserAdjudicationEvent( int result )
9281 {
9282     ChessMove gameResult = GameIsDrawn;
9283
9284     if( result > 0 ) {
9285         gameResult = WhiteWins;
9286     }
9287     else if( result < 0 ) {
9288         gameResult = BlackWins;
9289     }
9290
9291     if( gameMode == TwoMachinesPlay ) {
9292         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9293     }
9294 }
9295
9296
9297 // [HGM] save: calculate checksum of game to make games easily identifiable
9298 int StringCheckSum(char *s)
9299 {
9300         int i = 0;
9301         if(s==NULL) return 0;
9302         while(*s) i = i*259 + *s++;
9303         return i;
9304 }
9305
9306 int GameCheckSum()
9307 {
9308         int i, sum=0;
9309         for(i=backwardMostMove; i<forwardMostMove; i++) {
9310                 sum += pvInfoList[i].depth;
9311                 sum += StringCheckSum(parseList[i]);
9312                 sum += StringCheckSum(commentList[i]);
9313                 sum *= 261;
9314         }
9315         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9316         return sum + StringCheckSum(commentList[i]);
9317 } // end of save patch
9318
9319 void
9320 GameEnds(result, resultDetails, whosays)
9321      ChessMove result;
9322      char *resultDetails;
9323      int whosays;
9324 {
9325     GameMode nextGameMode;
9326     int isIcsGame;
9327     char buf[MSG_SIZ], popupRequested = 0;
9328
9329     if(endingGame) return; /* [HGM] crash: forbid recursion */
9330     endingGame = 1;
9331     if(twoBoards) { // [HGM] dual: switch back to one board
9332         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9333         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9334     }
9335     if (appData.debugMode) {
9336       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9337               result, resultDetails ? resultDetails : "(null)", whosays);
9338     }
9339
9340     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9341
9342     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9343         /* If we are playing on ICS, the server decides when the
9344            game is over, but the engine can offer to draw, claim
9345            a draw, or resign.
9346          */
9347 #if ZIPPY
9348         if (appData.zippyPlay && first.initDone) {
9349             if (result == GameIsDrawn) {
9350                 /* In case draw still needs to be claimed */
9351                 SendToICS(ics_prefix);
9352                 SendToICS("draw\n");
9353             } else if (StrCaseStr(resultDetails, "resign")) {
9354                 SendToICS(ics_prefix);
9355                 SendToICS("resign\n");
9356             }
9357         }
9358 #endif
9359         endingGame = 0; /* [HGM] crash */
9360         return;
9361     }
9362
9363     /* If we're loading the game from a file, stop */
9364     if (whosays == GE_FILE) {
9365       (void) StopLoadGameTimer();
9366       gameFileFP = NULL;
9367     }
9368
9369     /* Cancel draw offers */
9370     first.offeredDraw = second.offeredDraw = 0;
9371
9372     /* If this is an ICS game, only ICS can really say it's done;
9373        if not, anyone can. */
9374     isIcsGame = (gameMode == IcsPlayingWhite ||
9375                  gameMode == IcsPlayingBlack ||
9376                  gameMode == IcsObserving    ||
9377                  gameMode == IcsExamining);
9378
9379     if (!isIcsGame || whosays == GE_ICS) {
9380         /* OK -- not an ICS game, or ICS said it was done */
9381         StopClocks();
9382         if (!isIcsGame && !appData.noChessProgram)
9383           SetUserThinkingEnables();
9384
9385         /* [HGM] if a machine claims the game end we verify this claim */
9386         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9387             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9388                 char claimer;
9389                 ChessMove trueResult = (ChessMove) -1;
9390
9391                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9392                                             first.twoMachinesColor[0] :
9393                                             second.twoMachinesColor[0] ;
9394
9395                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9396                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9397                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9398                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9399                 } else
9400                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9401                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9402                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9403                 } else
9404                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9405                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9406                 }
9407
9408                 // now verify win claims, but not in drop games, as we don't understand those yet
9409                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9410                                                  || gameInfo.variant == VariantGreat) &&
9411                     (result == WhiteWins && claimer == 'w' ||
9412                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9413                       if (appData.debugMode) {
9414                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9415                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9416                       }
9417                       if(result != trueResult) {
9418                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9419                               result = claimer == 'w' ? BlackWins : WhiteWins;
9420                               resultDetails = buf;
9421                       }
9422                 } else
9423                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9424                     && (forwardMostMove <= backwardMostMove ||
9425                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9426                         (claimer=='b')==(forwardMostMove&1))
9427                                                                                   ) {
9428                       /* [HGM] verify: draws that were not flagged are false claims */
9429                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9430                       result = claimer == 'w' ? BlackWins : WhiteWins;
9431                       resultDetails = buf;
9432                 }
9433                 /* (Claiming a loss is accepted no questions asked!) */
9434             }
9435             /* [HGM] bare: don't allow bare King to win */
9436             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9437                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9438                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9439                && result != GameIsDrawn)
9440             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9441                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9442                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9443                         if(p >= 0 && p <= (int)WhiteKing) k++;
9444                 }
9445                 if (appData.debugMode) {
9446                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9447                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9448                 }
9449                 if(k <= 1) {
9450                         result = GameIsDrawn;
9451                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9452                         resultDetails = buf;
9453                 }
9454             }
9455         }
9456
9457
9458         if(serverMoves != NULL && !loadFlag) { char c = '=';
9459             if(result==WhiteWins) c = '+';
9460             if(result==BlackWins) c = '-';
9461             if(resultDetails != NULL)
9462                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9463         }
9464         if (resultDetails != NULL) {
9465             gameInfo.result = result;
9466             gameInfo.resultDetails = StrSave(resultDetails);
9467
9468             /* display last move only if game was not loaded from file */
9469             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9470                 DisplayMove(currentMove - 1);
9471
9472             if (forwardMostMove != 0) {
9473                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9474                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9475                                                                 ) {
9476                     if (*appData.saveGameFile != NULLCHAR) {
9477                         SaveGameToFile(appData.saveGameFile, TRUE);
9478                     } else if (appData.autoSaveGames) {
9479                         AutoSaveGame();
9480                     }
9481                     if (*appData.savePositionFile != NULLCHAR) {
9482                         SavePositionToFile(appData.savePositionFile);
9483                     }
9484                 }
9485             }
9486
9487             /* Tell program how game ended in case it is learning */
9488             /* [HGM] Moved this to after saving the PGN, just in case */
9489             /* engine died and we got here through time loss. In that */
9490             /* case we will get a fatal error writing the pipe, which */
9491             /* would otherwise lose us the PGN.                       */
9492             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9493             /* output during GameEnds should never be fatal anymore   */
9494             if (gameMode == MachinePlaysWhite ||
9495                 gameMode == MachinePlaysBlack ||
9496                 gameMode == TwoMachinesPlay ||
9497                 gameMode == IcsPlayingWhite ||
9498                 gameMode == IcsPlayingBlack ||
9499                 gameMode == BeginningOfGame) {
9500                 char buf[MSG_SIZ];
9501                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9502                         resultDetails);
9503                 if (first.pr != NoProc) {
9504                     SendToProgram(buf, &first);
9505                 }
9506                 if (second.pr != NoProc &&
9507                     gameMode == TwoMachinesPlay) {
9508                     SendToProgram(buf, &second);
9509                 }
9510             }
9511         }
9512
9513         if (appData.icsActive) {
9514             if (appData.quietPlay &&
9515                 (gameMode == IcsPlayingWhite ||
9516                  gameMode == IcsPlayingBlack)) {
9517                 SendToICS(ics_prefix);
9518                 SendToICS("set shout 1\n");
9519             }
9520             nextGameMode = IcsIdle;
9521             ics_user_moved = FALSE;
9522             /* clean up premove.  It's ugly when the game has ended and the
9523              * premove highlights are still on the board.
9524              */
9525             if (gotPremove) {
9526               gotPremove = FALSE;
9527               ClearPremoveHighlights();
9528               DrawPosition(FALSE, boards[currentMove]);
9529             }
9530             if (whosays == GE_ICS) {
9531                 switch (result) {
9532                 case WhiteWins:
9533                     if (gameMode == IcsPlayingWhite)
9534                         PlayIcsWinSound();
9535                     else if(gameMode == IcsPlayingBlack)
9536                         PlayIcsLossSound();
9537                     break;
9538                 case BlackWins:
9539                     if (gameMode == IcsPlayingBlack)
9540                         PlayIcsWinSound();
9541                     else if(gameMode == IcsPlayingWhite)
9542                         PlayIcsLossSound();
9543                     break;
9544                 case GameIsDrawn:
9545                     PlayIcsDrawSound();
9546                     break;
9547                 default:
9548                     PlayIcsUnfinishedSound();
9549                 }
9550             }
9551         } else if (gameMode == EditGame ||
9552                    gameMode == PlayFromGameFile ||
9553                    gameMode == AnalyzeMode ||
9554                    gameMode == AnalyzeFile) {
9555             nextGameMode = gameMode;
9556         } else {
9557             nextGameMode = EndOfGame;
9558         }
9559         pausing = FALSE;
9560         ModeHighlight();
9561     } else {
9562         nextGameMode = gameMode;
9563     }
9564
9565     if (appData.noChessProgram) {
9566         gameMode = nextGameMode;
9567         ModeHighlight();
9568         endingGame = 0; /* [HGM] crash */
9569         return;
9570     }
9571
9572     if (first.reuse) {
9573         /* Put first chess program into idle state */
9574         if (first.pr != NoProc &&
9575             (gameMode == MachinePlaysWhite ||
9576              gameMode == MachinePlaysBlack ||
9577              gameMode == TwoMachinesPlay ||
9578              gameMode == IcsPlayingWhite ||
9579              gameMode == IcsPlayingBlack ||
9580              gameMode == BeginningOfGame)) {
9581             SendToProgram("force\n", &first);
9582             if (first.usePing) {
9583               char buf[MSG_SIZ];
9584               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9585               SendToProgram(buf, &first);
9586             }
9587         }
9588     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9589         /* Kill off first chess program */
9590         if (first.isr != NULL)
9591           RemoveInputSource(first.isr);
9592         first.isr = NULL;
9593
9594         if (first.pr != NoProc) {
9595             ExitAnalyzeMode();
9596             DoSleep( appData.delayBeforeQuit );
9597             SendToProgram("quit\n", &first);
9598             DoSleep( appData.delayAfterQuit );
9599             DestroyChildProcess(first.pr, first.useSigterm);
9600         }
9601         first.pr = NoProc;
9602     }
9603     if (second.reuse) {
9604         /* Put second chess program into idle state */
9605         if (second.pr != NoProc &&
9606             gameMode == TwoMachinesPlay) {
9607             SendToProgram("force\n", &second);
9608             if (second.usePing) {
9609               char buf[MSG_SIZ];
9610               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9611               SendToProgram(buf, &second);
9612             }
9613         }
9614     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9615         /* Kill off second chess program */
9616         if (second.isr != NULL)
9617           RemoveInputSource(second.isr);
9618         second.isr = NULL;
9619
9620         if (second.pr != NoProc) {
9621             DoSleep( appData.delayBeforeQuit );
9622             SendToProgram("quit\n", &second);
9623             DoSleep( appData.delayAfterQuit );
9624             DestroyChildProcess(second.pr, second.useSigterm);
9625         }
9626         second.pr = NoProc;
9627     }
9628
9629     if (matchMode && gameMode == TwoMachinesPlay) {
9630         switch (result) {
9631         case WhiteWins:
9632           if (first.twoMachinesColor[0] == 'w') {
9633             first.matchWins++;
9634           } else {
9635             second.matchWins++;
9636           }
9637           break;
9638         case BlackWins:
9639           if (first.twoMachinesColor[0] == 'b') {
9640             first.matchWins++;
9641           } else {
9642             second.matchWins++;
9643           }
9644           break;
9645         default:
9646           break;
9647         }
9648         if (matchGame < appData.matchGames) {
9649             char *tmp;
9650             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9651                 tmp = first.twoMachinesColor;
9652                 first.twoMachinesColor = second.twoMachinesColor;
9653                 second.twoMachinesColor = tmp;
9654             }
9655             gameMode = nextGameMode;
9656             matchGame++;
9657             if(appData.matchPause>10000 || appData.matchPause<10)
9658                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9659             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9660             endingGame = 0; /* [HGM] crash */
9661             return;
9662         } else {
9663             gameMode = nextGameMode;
9664             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9665                      first.tidy, second.tidy,
9666                      first.matchWins, second.matchWins,
9667                      appData.matchGames - (first.matchWins + second.matchWins));
9668             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9669             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9670                 first.twoMachinesColor = "black\n";
9671                 second.twoMachinesColor = "white\n";
9672             } else {
9673                 first.twoMachinesColor = "white\n";
9674                 second.twoMachinesColor = "black\n";
9675             }
9676         }
9677     }
9678     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9679         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9680       ExitAnalyzeMode();
9681     gameMode = nextGameMode;
9682     ModeHighlight();
9683     endingGame = 0;  /* [HGM] crash */
9684     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9685       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9686         matchMode = FALSE; appData.matchGames = matchGame = 0;
9687         DisplayNote(buf);
9688       }
9689     }
9690 }
9691
9692 /* Assumes program was just initialized (initString sent).
9693    Leaves program in force mode. */
9694 void
9695 FeedMovesToProgram(cps, upto)
9696      ChessProgramState *cps;
9697      int upto;
9698 {
9699     int i;
9700
9701     if (appData.debugMode)
9702       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9703               startedFromSetupPosition ? "position and " : "",
9704               backwardMostMove, upto, cps->which);
9705     if(currentlyInitializedVariant != gameInfo.variant) {
9706       char buf[MSG_SIZ];
9707         // [HGM] variantswitch: make engine aware of new variant
9708         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9709                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9710         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9711         SendToProgram(buf, cps);
9712         currentlyInitializedVariant = gameInfo.variant;
9713     }
9714     SendToProgram("force\n", cps);
9715     if (startedFromSetupPosition) {
9716         SendBoard(cps, backwardMostMove);
9717     if (appData.debugMode) {
9718         fprintf(debugFP, "feedMoves\n");
9719     }
9720     }
9721     for (i = backwardMostMove; i < upto; i++) {
9722         SendMoveToProgram(i, cps);
9723     }
9724 }
9725
9726
9727 void
9728 ResurrectChessProgram()
9729 {
9730      /* The chess program may have exited.
9731         If so, restart it and feed it all the moves made so far. */
9732
9733     if (appData.noChessProgram || first.pr != NoProc) return;
9734
9735     StartChessProgram(&first);
9736     InitChessProgram(&first, FALSE);
9737     FeedMovesToProgram(&first, currentMove);
9738
9739     if (!first.sendTime) {
9740         /* can't tell gnuchess what its clock should read,
9741            so we bow to its notion. */
9742         ResetClocks();
9743         timeRemaining[0][currentMove] = whiteTimeRemaining;
9744         timeRemaining[1][currentMove] = blackTimeRemaining;
9745     }
9746
9747     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9748                 appData.icsEngineAnalyze) && first.analysisSupport) {
9749       SendToProgram("analyze\n", &first);
9750       first.analyzing = TRUE;
9751     }
9752 }
9753
9754 /*
9755  * Button procedures
9756  */
9757 void
9758 Reset(redraw, init)
9759      int redraw, init;
9760 {
9761     int i;
9762
9763     if (appData.debugMode) {
9764         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9765                 redraw, init, gameMode);
9766     }
9767     CleanupTail(); // [HGM] vari: delete any stored variations
9768     pausing = pauseExamInvalid = FALSE;
9769     startedFromSetupPosition = blackPlaysFirst = FALSE;
9770     firstMove = TRUE;
9771     whiteFlag = blackFlag = FALSE;
9772     userOfferedDraw = FALSE;
9773     hintRequested = bookRequested = FALSE;
9774     first.maybeThinking = FALSE;
9775     second.maybeThinking = FALSE;
9776     first.bookSuspend = FALSE; // [HGM] book
9777     second.bookSuspend = FALSE;
9778     thinkOutput[0] = NULLCHAR;
9779     lastHint[0] = NULLCHAR;
9780     ClearGameInfo(&gameInfo);
9781     gameInfo.variant = StringToVariant(appData.variant);
9782     ics_user_moved = ics_clock_paused = FALSE;
9783     ics_getting_history = H_FALSE;
9784     ics_gamenum = -1;
9785     white_holding[0] = black_holding[0] = NULLCHAR;
9786     ClearProgramStats();
9787     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9788
9789     ResetFrontEnd();
9790     ClearHighlights();
9791     flipView = appData.flipView;
9792     ClearPremoveHighlights();
9793     gotPremove = FALSE;
9794     alarmSounded = FALSE;
9795
9796     GameEnds(EndOfFile, NULL, GE_PLAYER);
9797     if(appData.serverMovesName != NULL) {
9798         /* [HGM] prepare to make moves file for broadcasting */
9799         clock_t t = clock();
9800         if(serverMoves != NULL) fclose(serverMoves);
9801         serverMoves = fopen(appData.serverMovesName, "r");
9802         if(serverMoves != NULL) {
9803             fclose(serverMoves);
9804             /* delay 15 sec before overwriting, so all clients can see end */
9805             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9806         }
9807         serverMoves = fopen(appData.serverMovesName, "w");
9808     }
9809
9810     ExitAnalyzeMode();
9811     gameMode = BeginningOfGame;
9812     ModeHighlight();
9813     if(appData.icsActive) gameInfo.variant = VariantNormal;
9814     currentMove = forwardMostMove = backwardMostMove = 0;
9815     InitPosition(redraw);
9816     for (i = 0; i < MAX_MOVES; i++) {
9817         if (commentList[i] != NULL) {
9818             free(commentList[i]);
9819             commentList[i] = NULL;
9820         }
9821     }
9822     ResetClocks();
9823     timeRemaining[0][0] = whiteTimeRemaining;
9824     timeRemaining[1][0] = blackTimeRemaining;
9825     if (first.pr == NULL) {
9826         StartChessProgram(&first);
9827     }
9828     if (init) {
9829             InitChessProgram(&first, startedFromSetupPosition);
9830     }
9831     DisplayTitle("");
9832     DisplayMessage("", "");
9833     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9834     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9835 }
9836
9837 void
9838 AutoPlayGameLoop()
9839 {
9840     for (;;) {
9841         if (!AutoPlayOneMove())
9842           return;
9843         if (matchMode || appData.timeDelay == 0)
9844           continue;
9845         if (appData.timeDelay < 0)
9846           return;
9847         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9848         break;
9849     }
9850 }
9851
9852
9853 int
9854 AutoPlayOneMove()
9855 {
9856     int fromX, fromY, toX, toY;
9857
9858     if (appData.debugMode) {
9859       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9860     }
9861
9862     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9863       return FALSE;
9864
9865     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9866       pvInfoList[currentMove].depth = programStats.depth;
9867       pvInfoList[currentMove].score = programStats.score;
9868       pvInfoList[currentMove].time  = 0;
9869       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9870     }
9871
9872     if (currentMove >= forwardMostMove) {
9873       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9874       gameMode = EditGame;
9875       ModeHighlight();
9876
9877       /* [AS] Clear current move marker at the end of a game */
9878       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9879
9880       return FALSE;
9881     }
9882
9883     toX = moveList[currentMove][2] - AAA;
9884     toY = moveList[currentMove][3] - ONE;
9885
9886     if (moveList[currentMove][1] == '@') {
9887         if (appData.highlightLastMove) {
9888             SetHighlights(-1, -1, toX, toY);
9889         }
9890     } else {
9891         fromX = moveList[currentMove][0] - AAA;
9892         fromY = moveList[currentMove][1] - ONE;
9893
9894         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9895
9896         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9897
9898         if (appData.highlightLastMove) {
9899             SetHighlights(fromX, fromY, toX, toY);
9900         }
9901     }
9902     DisplayMove(currentMove);
9903     SendMoveToProgram(currentMove++, &first);
9904     DisplayBothClocks();
9905     DrawPosition(FALSE, boards[currentMove]);
9906     // [HGM] PV info: always display, routine tests if empty
9907     DisplayComment(currentMove - 1, commentList[currentMove]);
9908     return TRUE;
9909 }
9910
9911
9912 int
9913 LoadGameOneMove(readAhead)
9914      ChessMove readAhead;
9915 {
9916     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9917     char promoChar = NULLCHAR;
9918     ChessMove moveType;
9919     char move[MSG_SIZ];
9920     char *p, *q;
9921
9922     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9923         gameMode != AnalyzeMode && gameMode != Training) {
9924         gameFileFP = NULL;
9925         return FALSE;
9926     }
9927
9928     yyboardindex = forwardMostMove;
9929     if (readAhead != EndOfFile) {
9930       moveType = readAhead;
9931     } else {
9932       if (gameFileFP == NULL)
9933           return FALSE;
9934       moveType = (ChessMove) Myylex();
9935     }
9936
9937     done = FALSE;
9938     switch (moveType) {
9939       case Comment:
9940         if (appData.debugMode)
9941           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9942         p = yy_text;
9943
9944         /* append the comment but don't display it */
9945         AppendComment(currentMove, p, FALSE);
9946         return TRUE;
9947
9948       case WhiteCapturesEnPassant:
9949       case BlackCapturesEnPassant:
9950       case WhitePromotion:
9951       case BlackPromotion:
9952       case WhiteNonPromotion:
9953       case BlackNonPromotion:
9954       case NormalMove:
9955       case WhiteKingSideCastle:
9956       case WhiteQueenSideCastle:
9957       case BlackKingSideCastle:
9958       case BlackQueenSideCastle:
9959       case WhiteKingSideCastleWild:
9960       case WhiteQueenSideCastleWild:
9961       case BlackKingSideCastleWild:
9962       case BlackQueenSideCastleWild:
9963       /* PUSH Fabien */
9964       case WhiteHSideCastleFR:
9965       case WhiteASideCastleFR:
9966       case BlackHSideCastleFR:
9967       case BlackASideCastleFR:
9968       /* POP Fabien */
9969         if (appData.debugMode)
9970           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9971         fromX = currentMoveString[0] - AAA;
9972         fromY = currentMoveString[1] - ONE;
9973         toX = currentMoveString[2] - AAA;
9974         toY = currentMoveString[3] - ONE;
9975         promoChar = currentMoveString[4];
9976         break;
9977
9978       case WhiteDrop:
9979       case BlackDrop:
9980         if (appData.debugMode)
9981           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9982         fromX = moveType == WhiteDrop ?
9983           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9984         (int) CharToPiece(ToLower(currentMoveString[0]));
9985         fromY = DROP_RANK;
9986         toX = currentMoveString[2] - AAA;
9987         toY = currentMoveString[3] - ONE;
9988         break;
9989
9990       case WhiteWins:
9991       case BlackWins:
9992       case GameIsDrawn:
9993       case GameUnfinished:
9994         if (appData.debugMode)
9995           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9996         p = strchr(yy_text, '{');
9997         if (p == NULL) p = strchr(yy_text, '(');
9998         if (p == NULL) {
9999             p = yy_text;
10000             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10001         } else {
10002             q = strchr(p, *p == '{' ? '}' : ')');
10003             if (q != NULL) *q = NULLCHAR;
10004             p++;
10005         }
10006         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10007         GameEnds(moveType, p, GE_FILE);
10008         done = TRUE;
10009         if (cmailMsgLoaded) {
10010             ClearHighlights();
10011             flipView = WhiteOnMove(currentMove);
10012             if (moveType == GameUnfinished) flipView = !flipView;
10013             if (appData.debugMode)
10014               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10015         }
10016         break;
10017
10018       case EndOfFile:
10019         if (appData.debugMode)
10020           fprintf(debugFP, "Parser hit end of file\n");
10021         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10022           case MT_NONE:
10023           case MT_CHECK:
10024             break;
10025           case MT_CHECKMATE:
10026           case MT_STAINMATE:
10027             if (WhiteOnMove(currentMove)) {
10028                 GameEnds(BlackWins, "Black mates", GE_FILE);
10029             } else {
10030                 GameEnds(WhiteWins, "White mates", GE_FILE);
10031             }
10032             break;
10033           case MT_STALEMATE:
10034             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10035             break;
10036         }
10037         done = TRUE;
10038         break;
10039
10040       case MoveNumberOne:
10041         if (lastLoadGameStart == GNUChessGame) {
10042             /* GNUChessGames have numbers, but they aren't move numbers */
10043             if (appData.debugMode)
10044               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10045                       yy_text, (int) moveType);
10046             return LoadGameOneMove(EndOfFile); /* tail recursion */
10047         }
10048         /* else fall thru */
10049
10050       case XBoardGame:
10051       case GNUChessGame:
10052       case PGNTag:
10053         /* Reached start of next game in file */
10054         if (appData.debugMode)
10055           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10056         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10057           case MT_NONE:
10058           case MT_CHECK:
10059             break;
10060           case MT_CHECKMATE:
10061           case MT_STAINMATE:
10062             if (WhiteOnMove(currentMove)) {
10063                 GameEnds(BlackWins, "Black mates", GE_FILE);
10064             } else {
10065                 GameEnds(WhiteWins, "White mates", GE_FILE);
10066             }
10067             break;
10068           case MT_STALEMATE:
10069             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10070             break;
10071         }
10072         done = TRUE;
10073         break;
10074
10075       case PositionDiagram:     /* should not happen; ignore */
10076       case ElapsedTime:         /* ignore */
10077       case NAG:                 /* ignore */
10078         if (appData.debugMode)
10079           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10080                   yy_text, (int) moveType);
10081         return LoadGameOneMove(EndOfFile); /* tail recursion */
10082
10083       case IllegalMove:
10084         if (appData.testLegality) {
10085             if (appData.debugMode)
10086               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10087             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10088                     (forwardMostMove / 2) + 1,
10089                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10090             DisplayError(move, 0);
10091             done = TRUE;
10092         } else {
10093             if (appData.debugMode)
10094               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10095                       yy_text, currentMoveString);
10096             fromX = currentMoveString[0] - AAA;
10097             fromY = currentMoveString[1] - ONE;
10098             toX = currentMoveString[2] - AAA;
10099             toY = currentMoveString[3] - ONE;
10100             promoChar = currentMoveString[4];
10101         }
10102         break;
10103
10104       case AmbiguousMove:
10105         if (appData.debugMode)
10106           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10107         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10108                 (forwardMostMove / 2) + 1,
10109                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10110         DisplayError(move, 0);
10111         done = TRUE;
10112         break;
10113
10114       default:
10115       case ImpossibleMove:
10116         if (appData.debugMode)
10117           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10118         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10119                 (forwardMostMove / 2) + 1,
10120                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10121         DisplayError(move, 0);
10122         done = TRUE;
10123         break;
10124     }
10125
10126     if (done) {
10127         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10128             DrawPosition(FALSE, boards[currentMove]);
10129             DisplayBothClocks();
10130             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10131               DisplayComment(currentMove - 1, commentList[currentMove]);
10132         }
10133         (void) StopLoadGameTimer();
10134         gameFileFP = NULL;
10135         cmailOldMove = forwardMostMove;
10136         return FALSE;
10137     } else {
10138         /* currentMoveString is set as a side-effect of yylex */
10139
10140         thinkOutput[0] = NULLCHAR;
10141         MakeMove(fromX, fromY, toX, toY, promoChar);
10142         currentMove = forwardMostMove;
10143         return TRUE;
10144     }
10145 }
10146
10147 /* Load the nth game from the given file */
10148 int
10149 LoadGameFromFile(filename, n, title, useList)
10150      char *filename;
10151      int n;
10152      char *title;
10153      /*Boolean*/ int useList;
10154 {
10155     FILE *f;
10156     char buf[MSG_SIZ];
10157
10158     if (strcmp(filename, "-") == 0) {
10159         f = stdin;
10160         title = "stdin";
10161     } else {
10162         f = fopen(filename, "rb");
10163         if (f == NULL) {
10164           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10165             DisplayError(buf, errno);
10166             return FALSE;
10167         }
10168     }
10169     if (fseek(f, 0, 0) == -1) {
10170         /* f is not seekable; probably a pipe */
10171         useList = FALSE;
10172     }
10173     if (useList && n == 0) {
10174         int error = GameListBuild(f);
10175         if (error) {
10176             DisplayError(_("Cannot build game list"), error);
10177         } else if (!ListEmpty(&gameList) &&
10178                    ((ListGame *) gameList.tailPred)->number > 1) {
10179             GameListPopUp(f, title);
10180             return TRUE;
10181         }
10182         GameListDestroy();
10183         n = 1;
10184     }
10185     if (n == 0) n = 1;
10186     return LoadGame(f, n, title, FALSE);
10187 }
10188
10189
10190 void
10191 MakeRegisteredMove()
10192 {
10193     int fromX, fromY, toX, toY;
10194     char promoChar;
10195     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10196         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10197           case CMAIL_MOVE:
10198           case CMAIL_DRAW:
10199             if (appData.debugMode)
10200               fprintf(debugFP, "Restoring %s for game %d\n",
10201                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10202
10203             thinkOutput[0] = NULLCHAR;
10204             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10205             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10206             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10207             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10208             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10209             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10210             MakeMove(fromX, fromY, toX, toY, promoChar);
10211             ShowMove(fromX, fromY, toX, toY);
10212
10213             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10214               case MT_NONE:
10215               case MT_CHECK:
10216                 break;
10217
10218               case MT_CHECKMATE:
10219               case MT_STAINMATE:
10220                 if (WhiteOnMove(currentMove)) {
10221                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10222                 } else {
10223                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10224                 }
10225                 break;
10226
10227               case MT_STALEMATE:
10228                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10229                 break;
10230             }
10231
10232             break;
10233
10234           case CMAIL_RESIGN:
10235             if (WhiteOnMove(currentMove)) {
10236                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10237             } else {
10238                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10239             }
10240             break;
10241
10242           case CMAIL_ACCEPT:
10243             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10244             break;
10245
10246           default:
10247             break;
10248         }
10249     }
10250
10251     return;
10252 }
10253
10254 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10255 int
10256 CmailLoadGame(f, gameNumber, title, useList)
10257      FILE *f;
10258      int gameNumber;
10259      char *title;
10260      int useList;
10261 {
10262     int retVal;
10263
10264     if (gameNumber > nCmailGames) {
10265         DisplayError(_("No more games in this message"), 0);
10266         return FALSE;
10267     }
10268     if (f == lastLoadGameFP) {
10269         int offset = gameNumber - lastLoadGameNumber;
10270         if (offset == 0) {
10271             cmailMsg[0] = NULLCHAR;
10272             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10273                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10274                 nCmailMovesRegistered--;
10275             }
10276             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10277             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10278                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10279             }
10280         } else {
10281             if (! RegisterMove()) return FALSE;
10282         }
10283     }
10284
10285     retVal = LoadGame(f, gameNumber, title, useList);
10286
10287     /* Make move registered during previous look at this game, if any */
10288     MakeRegisteredMove();
10289
10290     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10291         commentList[currentMove]
10292           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10293         DisplayComment(currentMove - 1, commentList[currentMove]);
10294     }
10295
10296     return retVal;
10297 }
10298
10299 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10300 int
10301 ReloadGame(offset)
10302      int offset;
10303 {
10304     int gameNumber = lastLoadGameNumber + offset;
10305     if (lastLoadGameFP == NULL) {
10306         DisplayError(_("No game has been loaded yet"), 0);
10307         return FALSE;
10308     }
10309     if (gameNumber <= 0) {
10310         DisplayError(_("Can't back up any further"), 0);
10311         return FALSE;
10312     }
10313     if (cmailMsgLoaded) {
10314         return CmailLoadGame(lastLoadGameFP, gameNumber,
10315                              lastLoadGameTitle, lastLoadGameUseList);
10316     } else {
10317         return LoadGame(lastLoadGameFP, gameNumber,
10318                         lastLoadGameTitle, lastLoadGameUseList);
10319     }
10320 }
10321
10322
10323
10324 /* Load the nth game from open file f */
10325 int
10326 LoadGame(f, gameNumber, title, useList)
10327      FILE *f;
10328      int gameNumber;
10329      char *title;
10330      int useList;
10331 {
10332     ChessMove cm;
10333     char buf[MSG_SIZ];
10334     int gn = gameNumber;
10335     ListGame *lg = NULL;
10336     int numPGNTags = 0;
10337     int err;
10338     GameMode oldGameMode;
10339     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10340
10341     if (appData.debugMode)
10342         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10343
10344     if (gameMode == Training )
10345         SetTrainingModeOff();
10346
10347     oldGameMode = gameMode;
10348     if (gameMode != BeginningOfGame) {
10349       Reset(FALSE, TRUE);
10350     }
10351
10352     gameFileFP = f;
10353     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10354         fclose(lastLoadGameFP);
10355     }
10356
10357     if (useList) {
10358         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10359
10360         if (lg) {
10361             fseek(f, lg->offset, 0);
10362             GameListHighlight(gameNumber);
10363             gn = 1;
10364         }
10365         else {
10366             DisplayError(_("Game number out of range"), 0);
10367             return FALSE;
10368         }
10369     } else {
10370         GameListDestroy();
10371         if (fseek(f, 0, 0) == -1) {
10372             if (f == lastLoadGameFP ?
10373                 gameNumber == lastLoadGameNumber + 1 :
10374                 gameNumber == 1) {
10375                 gn = 1;
10376             } else {
10377                 DisplayError(_("Can't seek on game file"), 0);
10378                 return FALSE;
10379             }
10380         }
10381     }
10382     lastLoadGameFP = f;
10383     lastLoadGameNumber = gameNumber;
10384     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10385     lastLoadGameUseList = useList;
10386
10387     yynewfile(f);
10388
10389     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10390       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10391                 lg->gameInfo.black);
10392             DisplayTitle(buf);
10393     } else if (*title != NULLCHAR) {
10394         if (gameNumber > 1) {
10395           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10396             DisplayTitle(buf);
10397         } else {
10398             DisplayTitle(title);
10399         }
10400     }
10401
10402     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10403         gameMode = PlayFromGameFile;
10404         ModeHighlight();
10405     }
10406
10407     currentMove = forwardMostMove = backwardMostMove = 0;
10408     CopyBoard(boards[0], initialPosition);
10409     StopClocks();
10410
10411     /*
10412      * Skip the first gn-1 games in the file.
10413      * Also skip over anything that precedes an identifiable
10414      * start of game marker, to avoid being confused by
10415      * garbage at the start of the file.  Currently
10416      * recognized start of game markers are the move number "1",
10417      * the pattern "gnuchess .* game", the pattern
10418      * "^[#;%] [^ ]* game file", and a PGN tag block.
10419      * A game that starts with one of the latter two patterns
10420      * will also have a move number 1, possibly
10421      * following a position diagram.
10422      * 5-4-02: Let's try being more lenient and allowing a game to
10423      * start with an unnumbered move.  Does that break anything?
10424      */
10425     cm = lastLoadGameStart = EndOfFile;
10426     while (gn > 0) {
10427         yyboardindex = forwardMostMove;
10428         cm = (ChessMove) Myylex();
10429         switch (cm) {
10430           case EndOfFile:
10431             if (cmailMsgLoaded) {
10432                 nCmailGames = CMAIL_MAX_GAMES - gn;
10433             } else {
10434                 Reset(TRUE, TRUE);
10435                 DisplayError(_("Game not found in file"), 0);
10436             }
10437             return FALSE;
10438
10439           case GNUChessGame:
10440           case XBoardGame:
10441             gn--;
10442             lastLoadGameStart = cm;
10443             break;
10444
10445           case MoveNumberOne:
10446             switch (lastLoadGameStart) {
10447               case GNUChessGame:
10448               case XBoardGame:
10449               case PGNTag:
10450                 break;
10451               case MoveNumberOne:
10452               case EndOfFile:
10453                 gn--;           /* count this game */
10454                 lastLoadGameStart = cm;
10455                 break;
10456               default:
10457                 /* impossible */
10458                 break;
10459             }
10460             break;
10461
10462           case PGNTag:
10463             switch (lastLoadGameStart) {
10464               case GNUChessGame:
10465               case PGNTag:
10466               case MoveNumberOne:
10467               case EndOfFile:
10468                 gn--;           /* count this game */
10469                 lastLoadGameStart = cm;
10470                 break;
10471               case XBoardGame:
10472                 lastLoadGameStart = cm; /* game counted already */
10473                 break;
10474               default:
10475                 /* impossible */
10476                 break;
10477             }
10478             if (gn > 0) {
10479                 do {
10480                     yyboardindex = forwardMostMove;
10481                     cm = (ChessMove) Myylex();
10482                 } while (cm == PGNTag || cm == Comment);
10483             }
10484             break;
10485
10486           case WhiteWins:
10487           case BlackWins:
10488           case GameIsDrawn:
10489             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10490                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10491                     != CMAIL_OLD_RESULT) {
10492                     nCmailResults ++ ;
10493                     cmailResult[  CMAIL_MAX_GAMES
10494                                 - gn - 1] = CMAIL_OLD_RESULT;
10495                 }
10496             }
10497             break;
10498
10499           case NormalMove:
10500             /* Only a NormalMove can be at the start of a game
10501              * without a position diagram. */
10502             if (lastLoadGameStart == EndOfFile ) {
10503               gn--;
10504               lastLoadGameStart = MoveNumberOne;
10505             }
10506             break;
10507
10508           default:
10509             break;
10510         }
10511     }
10512
10513     if (appData.debugMode)
10514       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10515
10516     if (cm == XBoardGame) {
10517         /* Skip any header junk before position diagram and/or move 1 */
10518         for (;;) {
10519             yyboardindex = forwardMostMove;
10520             cm = (ChessMove) Myylex();
10521
10522             if (cm == EndOfFile ||
10523                 cm == GNUChessGame || cm == XBoardGame) {
10524                 /* Empty game; pretend end-of-file and handle later */
10525                 cm = EndOfFile;
10526                 break;
10527             }
10528
10529             if (cm == MoveNumberOne || cm == PositionDiagram ||
10530                 cm == PGNTag || cm == Comment)
10531               break;
10532         }
10533     } else if (cm == GNUChessGame) {
10534         if (gameInfo.event != NULL) {
10535             free(gameInfo.event);
10536         }
10537         gameInfo.event = StrSave(yy_text);
10538     }
10539
10540     startedFromSetupPosition = FALSE;
10541     while (cm == PGNTag) {
10542         if (appData.debugMode)
10543           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10544         err = ParsePGNTag(yy_text, &gameInfo);
10545         if (!err) numPGNTags++;
10546
10547         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10548         if(gameInfo.variant != oldVariant) {
10549             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10550             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10551             InitPosition(TRUE);
10552             oldVariant = gameInfo.variant;
10553             if (appData.debugMode)
10554               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10555         }
10556
10557
10558         if (gameInfo.fen != NULL) {
10559           Board initial_position;
10560           startedFromSetupPosition = TRUE;
10561           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10562             Reset(TRUE, TRUE);
10563             DisplayError(_("Bad FEN position in file"), 0);
10564             return FALSE;
10565           }
10566           CopyBoard(boards[0], initial_position);
10567           if (blackPlaysFirst) {
10568             currentMove = forwardMostMove = backwardMostMove = 1;
10569             CopyBoard(boards[1], initial_position);
10570             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10571             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10572             timeRemaining[0][1] = whiteTimeRemaining;
10573             timeRemaining[1][1] = blackTimeRemaining;
10574             if (commentList[0] != NULL) {
10575               commentList[1] = commentList[0];
10576               commentList[0] = NULL;
10577             }
10578           } else {
10579             currentMove = forwardMostMove = backwardMostMove = 0;
10580           }
10581           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10582           {   int i;
10583               initialRulePlies = FENrulePlies;
10584               for( i=0; i< nrCastlingRights; i++ )
10585                   initialRights[i] = initial_position[CASTLING][i];
10586           }
10587           yyboardindex = forwardMostMove;
10588           free(gameInfo.fen);
10589           gameInfo.fen = NULL;
10590         }
10591
10592         yyboardindex = forwardMostMove;
10593         cm = (ChessMove) Myylex();
10594
10595         /* Handle comments interspersed among the tags */
10596         while (cm == Comment) {
10597             char *p;
10598             if (appData.debugMode)
10599               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10600             p = yy_text;
10601             AppendComment(currentMove, p, FALSE);
10602             yyboardindex = forwardMostMove;
10603             cm = (ChessMove) Myylex();
10604         }
10605     }
10606
10607     /* don't rely on existence of Event tag since if game was
10608      * pasted from clipboard the Event tag may not exist
10609      */
10610     if (numPGNTags > 0){
10611         char *tags;
10612         if (gameInfo.variant == VariantNormal) {
10613           VariantClass v = StringToVariant(gameInfo.event);
10614           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10615           if(v < VariantShogi) gameInfo.variant = v;
10616         }
10617         if (!matchMode) {
10618           if( appData.autoDisplayTags ) {
10619             tags = PGNTags(&gameInfo);
10620             TagsPopUp(tags, CmailMsg());
10621             free(tags);
10622           }
10623         }
10624     } else {
10625         /* Make something up, but don't display it now */
10626         SetGameInfo();
10627         TagsPopDown();
10628     }
10629
10630     if (cm == PositionDiagram) {
10631         int i, j;
10632         char *p;
10633         Board initial_position;
10634
10635         if (appData.debugMode)
10636           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10637
10638         if (!startedFromSetupPosition) {
10639             p = yy_text;
10640             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10641               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10642                 switch (*p) {
10643                   case '{':
10644                   case '[':
10645                   case '-':
10646                   case ' ':
10647                   case '\t':
10648                   case '\n':
10649                   case '\r':
10650                     break;
10651                   default:
10652                     initial_position[i][j++] = CharToPiece(*p);
10653                     break;
10654                 }
10655             while (*p == ' ' || *p == '\t' ||
10656                    *p == '\n' || *p == '\r') p++;
10657
10658             if (strncmp(p, "black", strlen("black"))==0)
10659               blackPlaysFirst = TRUE;
10660             else
10661               blackPlaysFirst = FALSE;
10662             startedFromSetupPosition = TRUE;
10663
10664             CopyBoard(boards[0], initial_position);
10665             if (blackPlaysFirst) {
10666                 currentMove = forwardMostMove = backwardMostMove = 1;
10667                 CopyBoard(boards[1], initial_position);
10668                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10669                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10670                 timeRemaining[0][1] = whiteTimeRemaining;
10671                 timeRemaining[1][1] = blackTimeRemaining;
10672                 if (commentList[0] != NULL) {
10673                     commentList[1] = commentList[0];
10674                     commentList[0] = NULL;
10675                 }
10676             } else {
10677                 currentMove = forwardMostMove = backwardMostMove = 0;
10678             }
10679         }
10680         yyboardindex = forwardMostMove;
10681         cm = (ChessMove) Myylex();
10682     }
10683
10684     if (first.pr == NoProc) {
10685         StartChessProgram(&first);
10686     }
10687     InitChessProgram(&first, FALSE);
10688     SendToProgram("force\n", &first);
10689     if (startedFromSetupPosition) {
10690         SendBoard(&first, forwardMostMove);
10691     if (appData.debugMode) {
10692         fprintf(debugFP, "Load Game\n");
10693     }
10694         DisplayBothClocks();
10695     }
10696
10697     /* [HGM] server: flag to write setup moves in broadcast file as one */
10698     loadFlag = appData.suppressLoadMoves;
10699
10700     while (cm == Comment) {
10701         char *p;
10702         if (appData.debugMode)
10703           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10704         p = yy_text;
10705         AppendComment(currentMove, p, FALSE);
10706         yyboardindex = forwardMostMove;
10707         cm = (ChessMove) Myylex();
10708     }
10709
10710     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10711         cm == WhiteWins || cm == BlackWins ||
10712         cm == GameIsDrawn || cm == GameUnfinished) {
10713         DisplayMessage("", _("No moves in game"));
10714         if (cmailMsgLoaded) {
10715             if (appData.debugMode)
10716               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10717             ClearHighlights();
10718             flipView = FALSE;
10719         }
10720         DrawPosition(FALSE, boards[currentMove]);
10721         DisplayBothClocks();
10722         gameMode = EditGame;
10723         ModeHighlight();
10724         gameFileFP = NULL;
10725         cmailOldMove = 0;
10726         return TRUE;
10727     }
10728
10729     // [HGM] PV info: routine tests if comment empty
10730     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10731         DisplayComment(currentMove - 1, commentList[currentMove]);
10732     }
10733     if (!matchMode && appData.timeDelay != 0)
10734       DrawPosition(FALSE, boards[currentMove]);
10735
10736     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10737       programStats.ok_to_send = 1;
10738     }
10739
10740     /* if the first token after the PGN tags is a move
10741      * and not move number 1, retrieve it from the parser
10742      */
10743     if (cm != MoveNumberOne)
10744         LoadGameOneMove(cm);
10745
10746     /* load the remaining moves from the file */
10747     while (LoadGameOneMove(EndOfFile)) {
10748       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10749       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10750     }
10751
10752     /* rewind to the start of the game */
10753     currentMove = backwardMostMove;
10754
10755     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10756
10757     if (oldGameMode == AnalyzeFile ||
10758         oldGameMode == AnalyzeMode) {
10759       AnalyzeFileEvent();
10760     }
10761
10762     if (matchMode || appData.timeDelay == 0) {
10763       ToEndEvent();
10764       gameMode = EditGame;
10765       ModeHighlight();
10766     } else if (appData.timeDelay > 0) {
10767       AutoPlayGameLoop();
10768     }
10769
10770     if (appData.debugMode)
10771         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10772
10773     loadFlag = 0; /* [HGM] true game starts */
10774     return TRUE;
10775 }
10776
10777 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10778 int
10779 ReloadPosition(offset)
10780      int offset;
10781 {
10782     int positionNumber = lastLoadPositionNumber + offset;
10783     if (lastLoadPositionFP == NULL) {
10784         DisplayError(_("No position has been loaded yet"), 0);
10785         return FALSE;
10786     }
10787     if (positionNumber <= 0) {
10788         DisplayError(_("Can't back up any further"), 0);
10789         return FALSE;
10790     }
10791     return LoadPosition(lastLoadPositionFP, positionNumber,
10792                         lastLoadPositionTitle);
10793 }
10794
10795 /* Load the nth position from the given file */
10796 int
10797 LoadPositionFromFile(filename, n, title)
10798      char *filename;
10799      int n;
10800      char *title;
10801 {
10802     FILE *f;
10803     char buf[MSG_SIZ];
10804
10805     if (strcmp(filename, "-") == 0) {
10806         return LoadPosition(stdin, n, "stdin");
10807     } else {
10808         f = fopen(filename, "rb");
10809         if (f == NULL) {
10810             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10811             DisplayError(buf, errno);
10812             return FALSE;
10813         } else {
10814             return LoadPosition(f, n, title);
10815         }
10816     }
10817 }
10818
10819 /* Load the nth position from the given open file, and close it */
10820 int
10821 LoadPosition(f, positionNumber, title)
10822      FILE *f;
10823      int positionNumber;
10824      char *title;
10825 {
10826     char *p, line[MSG_SIZ];
10827     Board initial_position;
10828     int i, j, fenMode, pn;
10829
10830     if (gameMode == Training )
10831         SetTrainingModeOff();
10832
10833     if (gameMode != BeginningOfGame) {
10834         Reset(FALSE, TRUE);
10835     }
10836     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10837         fclose(lastLoadPositionFP);
10838     }
10839     if (positionNumber == 0) positionNumber = 1;
10840     lastLoadPositionFP = f;
10841     lastLoadPositionNumber = positionNumber;
10842     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10843     if (first.pr == NoProc) {
10844       StartChessProgram(&first);
10845       InitChessProgram(&first, FALSE);
10846     }
10847     pn = positionNumber;
10848     if (positionNumber < 0) {
10849         /* Negative position number means to seek to that byte offset */
10850         if (fseek(f, -positionNumber, 0) == -1) {
10851             DisplayError(_("Can't seek on position file"), 0);
10852             return FALSE;
10853         };
10854         pn = 1;
10855     } else {
10856         if (fseek(f, 0, 0) == -1) {
10857             if (f == lastLoadPositionFP ?
10858                 positionNumber == lastLoadPositionNumber + 1 :
10859                 positionNumber == 1) {
10860                 pn = 1;
10861             } else {
10862                 DisplayError(_("Can't seek on position file"), 0);
10863                 return FALSE;
10864             }
10865         }
10866     }
10867     /* See if this file is FEN or old-style xboard */
10868     if (fgets(line, MSG_SIZ, f) == NULL) {
10869         DisplayError(_("Position not found in file"), 0);
10870         return FALSE;
10871     }
10872     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10873     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10874
10875     if (pn >= 2) {
10876         if (fenMode || line[0] == '#') pn--;
10877         while (pn > 0) {
10878             /* skip positions before number pn */
10879             if (fgets(line, MSG_SIZ, f) == NULL) {
10880                 Reset(TRUE, TRUE);
10881                 DisplayError(_("Position not found in file"), 0);
10882                 return FALSE;
10883             }
10884             if (fenMode || line[0] == '#') pn--;
10885         }
10886     }
10887
10888     if (fenMode) {
10889         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10890             DisplayError(_("Bad FEN position in file"), 0);
10891             return FALSE;
10892         }
10893     } else {
10894         (void) fgets(line, MSG_SIZ, f);
10895         (void) fgets(line, MSG_SIZ, f);
10896
10897         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10898             (void) fgets(line, MSG_SIZ, f);
10899             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10900                 if (*p == ' ')
10901                   continue;
10902                 initial_position[i][j++] = CharToPiece(*p);
10903             }
10904         }
10905
10906         blackPlaysFirst = FALSE;
10907         if (!feof(f)) {
10908             (void) fgets(line, MSG_SIZ, f);
10909             if (strncmp(line, "black", strlen("black"))==0)
10910               blackPlaysFirst = TRUE;
10911         }
10912     }
10913     startedFromSetupPosition = TRUE;
10914
10915     SendToProgram("force\n", &first);
10916     CopyBoard(boards[0], initial_position);
10917     if (blackPlaysFirst) {
10918         currentMove = forwardMostMove = backwardMostMove = 1;
10919         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10920         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10921         CopyBoard(boards[1], initial_position);
10922         DisplayMessage("", _("Black to play"));
10923     } else {
10924         currentMove = forwardMostMove = backwardMostMove = 0;
10925         DisplayMessage("", _("White to play"));
10926     }
10927     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10928     SendBoard(&first, forwardMostMove);
10929     if (appData.debugMode) {
10930 int i, j;
10931   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10932   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10933         fprintf(debugFP, "Load Position\n");
10934     }
10935
10936     if (positionNumber > 1) {
10937       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10938         DisplayTitle(line);
10939     } else {
10940         DisplayTitle(title);
10941     }
10942     gameMode = EditGame;
10943     ModeHighlight();
10944     ResetClocks();
10945     timeRemaining[0][1] = whiteTimeRemaining;
10946     timeRemaining[1][1] = blackTimeRemaining;
10947     DrawPosition(FALSE, boards[currentMove]);
10948
10949     return TRUE;
10950 }
10951
10952
10953 void
10954 CopyPlayerNameIntoFileName(dest, src)
10955      char **dest, *src;
10956 {
10957     while (*src != NULLCHAR && *src != ',') {
10958         if (*src == ' ') {
10959             *(*dest)++ = '_';
10960             src++;
10961         } else {
10962             *(*dest)++ = *src++;
10963         }
10964     }
10965 }
10966
10967 char *DefaultFileName(ext)
10968      char *ext;
10969 {
10970     static char def[MSG_SIZ];
10971     char *p;
10972
10973     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10974         p = def;
10975         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10976         *p++ = '-';
10977         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10978         *p++ = '.';
10979         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10980     } else {
10981         def[0] = NULLCHAR;
10982     }
10983     return def;
10984 }
10985
10986 /* Save the current game to the given file */
10987 int
10988 SaveGameToFile(filename, append)
10989      char *filename;
10990      int append;
10991 {
10992     FILE *f;
10993     char buf[MSG_SIZ];
10994
10995     if (strcmp(filename, "-") == 0) {
10996         return SaveGame(stdout, 0, NULL);
10997     } else {
10998         f = fopen(filename, append ? "a" : "w");
10999         if (f == NULL) {
11000             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11001             DisplayError(buf, errno);
11002             return FALSE;
11003         } else {
11004             return SaveGame(f, 0, NULL);
11005         }
11006     }
11007 }
11008
11009 char *
11010 SavePart(str)
11011      char *str;
11012 {
11013     static char buf[MSG_SIZ];
11014     char *p;
11015
11016     p = strchr(str, ' ');
11017     if (p == NULL) return str;
11018     strncpy(buf, str, p - str);
11019     buf[p - str] = NULLCHAR;
11020     return buf;
11021 }
11022
11023 #define PGN_MAX_LINE 75
11024
11025 #define PGN_SIDE_WHITE  0
11026 #define PGN_SIDE_BLACK  1
11027
11028 /* [AS] */
11029 static int FindFirstMoveOutOfBook( int side )
11030 {
11031     int result = -1;
11032
11033     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11034         int index = backwardMostMove;
11035         int has_book_hit = 0;
11036
11037         if( (index % 2) != side ) {
11038             index++;
11039         }
11040
11041         while( index < forwardMostMove ) {
11042             /* Check to see if engine is in book */
11043             int depth = pvInfoList[index].depth;
11044             int score = pvInfoList[index].score;
11045             int in_book = 0;
11046
11047             if( depth <= 2 ) {
11048                 in_book = 1;
11049             }
11050             else if( score == 0 && depth == 63 ) {
11051                 in_book = 1; /* Zappa */
11052             }
11053             else if( score == 2 && depth == 99 ) {
11054                 in_book = 1; /* Abrok */
11055             }
11056
11057             has_book_hit += in_book;
11058
11059             if( ! in_book ) {
11060                 result = index;
11061
11062                 break;
11063             }
11064
11065             index += 2;
11066         }
11067     }
11068
11069     return result;
11070 }
11071
11072 /* [AS] */
11073 void GetOutOfBookInfo( char * buf )
11074 {
11075     int oob[2];
11076     int i;
11077     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11078
11079     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11080     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11081
11082     *buf = '\0';
11083
11084     if( oob[0] >= 0 || oob[1] >= 0 ) {
11085         for( i=0; i<2; i++ ) {
11086             int idx = oob[i];
11087
11088             if( idx >= 0 ) {
11089                 if( i > 0 && oob[0] >= 0 ) {
11090                     strcat( buf, "   " );
11091                 }
11092
11093                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11094                 sprintf( buf+strlen(buf), "%s%.2f",
11095                     pvInfoList[idx].score >= 0 ? "+" : "",
11096                     pvInfoList[idx].score / 100.0 );
11097             }
11098         }
11099     }
11100 }
11101
11102 /* Save game in PGN style and close the file */
11103 int
11104 SaveGamePGN(f)
11105      FILE *f;
11106 {
11107     int i, offset, linelen, newblock;
11108     time_t tm;
11109 //    char *movetext;
11110     char numtext[32];
11111     int movelen, numlen, blank;
11112     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11113
11114     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11115
11116     tm = time((time_t *) NULL);
11117
11118     PrintPGNTags(f, &gameInfo);
11119
11120     if (backwardMostMove > 0 || startedFromSetupPosition) {
11121         char *fen = PositionToFEN(backwardMostMove, NULL);
11122         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11123         fprintf(f, "\n{--------------\n");
11124         PrintPosition(f, backwardMostMove);
11125         fprintf(f, "--------------}\n");
11126         free(fen);
11127     }
11128     else {
11129         /* [AS] Out of book annotation */
11130         if( appData.saveOutOfBookInfo ) {
11131             char buf[64];
11132
11133             GetOutOfBookInfo( buf );
11134
11135             if( buf[0] != '\0' ) {
11136                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11137             }
11138         }
11139
11140         fprintf(f, "\n");
11141     }
11142
11143     i = backwardMostMove;
11144     linelen = 0;
11145     newblock = TRUE;
11146
11147     while (i < forwardMostMove) {
11148         /* Print comments preceding this move */
11149         if (commentList[i] != NULL) {
11150             if (linelen > 0) fprintf(f, "\n");
11151             fprintf(f, "%s", commentList[i]);
11152             linelen = 0;
11153             newblock = TRUE;
11154         }
11155
11156         /* Format move number */
11157         if ((i % 2) == 0)
11158           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11159         else
11160           if (newblock)
11161             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11162           else
11163             numtext[0] = NULLCHAR;
11164
11165         numlen = strlen(numtext);
11166         newblock = FALSE;
11167
11168         /* Print move number */
11169         blank = linelen > 0 && numlen > 0;
11170         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11171             fprintf(f, "\n");
11172             linelen = 0;
11173             blank = 0;
11174         }
11175         if (blank) {
11176             fprintf(f, " ");
11177             linelen++;
11178         }
11179         fprintf(f, "%s", numtext);
11180         linelen += numlen;
11181
11182         /* Get move */
11183         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11184         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11185
11186         /* Print move */
11187         blank = linelen > 0 && movelen > 0;
11188         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11189             fprintf(f, "\n");
11190             linelen = 0;
11191             blank = 0;
11192         }
11193         if (blank) {
11194             fprintf(f, " ");
11195             linelen++;
11196         }
11197         fprintf(f, "%s", move_buffer);
11198         linelen += movelen;
11199
11200         /* [AS] Add PV info if present */
11201         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11202             /* [HGM] add time */
11203             char buf[MSG_SIZ]; int seconds;
11204
11205             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11206
11207             if( seconds <= 0)
11208               buf[0] = 0;
11209             else
11210               if( seconds < 30 )
11211                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11212               else
11213                 {
11214                   seconds = (seconds + 4)/10; // round to full seconds
11215                   if( seconds < 60 )
11216                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11217                   else
11218                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11219                 }
11220
11221             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11222                       pvInfoList[i].score >= 0 ? "+" : "",
11223                       pvInfoList[i].score / 100.0,
11224                       pvInfoList[i].depth,
11225                       buf );
11226
11227             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11228
11229             /* Print score/depth */
11230             blank = linelen > 0 && movelen > 0;
11231             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11232                 fprintf(f, "\n");
11233                 linelen = 0;
11234                 blank = 0;
11235             }
11236             if (blank) {
11237                 fprintf(f, " ");
11238                 linelen++;
11239             }
11240             fprintf(f, "%s", move_buffer);
11241             linelen += movelen;
11242         }
11243
11244         i++;
11245     }
11246
11247     /* Start a new line */
11248     if (linelen > 0) fprintf(f, "\n");
11249
11250     /* Print comments after last move */
11251     if (commentList[i] != NULL) {
11252         fprintf(f, "%s\n", commentList[i]);
11253     }
11254
11255     /* Print result */
11256     if (gameInfo.resultDetails != NULL &&
11257         gameInfo.resultDetails[0] != NULLCHAR) {
11258         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11259                 PGNResult(gameInfo.result));
11260     } else {
11261         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11262     }
11263
11264     fclose(f);
11265     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11266     return TRUE;
11267 }
11268
11269 /* Save game in old style and close the file */
11270 int
11271 SaveGameOldStyle(f)
11272      FILE *f;
11273 {
11274     int i, offset;
11275     time_t tm;
11276
11277     tm = time((time_t *) NULL);
11278
11279     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11280     PrintOpponents(f);
11281
11282     if (backwardMostMove > 0 || startedFromSetupPosition) {
11283         fprintf(f, "\n[--------------\n");
11284         PrintPosition(f, backwardMostMove);
11285         fprintf(f, "--------------]\n");
11286     } else {
11287         fprintf(f, "\n");
11288     }
11289
11290     i = backwardMostMove;
11291     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11292
11293     while (i < forwardMostMove) {
11294         if (commentList[i] != NULL) {
11295             fprintf(f, "[%s]\n", commentList[i]);
11296         }
11297
11298         if ((i % 2) == 1) {
11299             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11300             i++;
11301         } else {
11302             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11303             i++;
11304             if (commentList[i] != NULL) {
11305                 fprintf(f, "\n");
11306                 continue;
11307             }
11308             if (i >= forwardMostMove) {
11309                 fprintf(f, "\n");
11310                 break;
11311             }
11312             fprintf(f, "%s\n", parseList[i]);
11313             i++;
11314         }
11315     }
11316
11317     if (commentList[i] != NULL) {
11318         fprintf(f, "[%s]\n", commentList[i]);
11319     }
11320
11321     /* This isn't really the old style, but it's close enough */
11322     if (gameInfo.resultDetails != NULL &&
11323         gameInfo.resultDetails[0] != NULLCHAR) {
11324         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11325                 gameInfo.resultDetails);
11326     } else {
11327         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11328     }
11329
11330     fclose(f);
11331     return TRUE;
11332 }
11333
11334 /* Save the current game to open file f and close the file */
11335 int
11336 SaveGame(f, dummy, dummy2)
11337      FILE *f;
11338      int dummy;
11339      char *dummy2;
11340 {
11341     if (gameMode == EditPosition) EditPositionDone(TRUE);
11342     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11343     if (appData.oldSaveStyle)
11344       return SaveGameOldStyle(f);
11345     else
11346       return SaveGamePGN(f);
11347 }
11348
11349 /* Save the current position to the given file */
11350 int
11351 SavePositionToFile(filename)
11352      char *filename;
11353 {
11354     FILE *f;
11355     char buf[MSG_SIZ];
11356
11357     if (strcmp(filename, "-") == 0) {
11358         return SavePosition(stdout, 0, NULL);
11359     } else {
11360         f = fopen(filename, "a");
11361         if (f == NULL) {
11362             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11363             DisplayError(buf, errno);
11364             return FALSE;
11365         } else {
11366             SavePosition(f, 0, NULL);
11367             return TRUE;
11368         }
11369     }
11370 }
11371
11372 /* Save the current position to the given open file and close the file */
11373 int
11374 SavePosition(f, dummy, dummy2)
11375      FILE *f;
11376      int dummy;
11377      char *dummy2;
11378 {
11379     time_t tm;
11380     char *fen;
11381
11382     if (gameMode == EditPosition) EditPositionDone(TRUE);
11383     if (appData.oldSaveStyle) {
11384         tm = time((time_t *) NULL);
11385
11386         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11387         PrintOpponents(f);
11388         fprintf(f, "[--------------\n");
11389         PrintPosition(f, currentMove);
11390         fprintf(f, "--------------]\n");
11391     } else {
11392         fen = PositionToFEN(currentMove, NULL);
11393         fprintf(f, "%s\n", fen);
11394         free(fen);
11395     }
11396     fclose(f);
11397     return TRUE;
11398 }
11399
11400 void
11401 ReloadCmailMsgEvent(unregister)
11402      int unregister;
11403 {
11404 #if !WIN32
11405     static char *inFilename = NULL;
11406     static char *outFilename;
11407     int i;
11408     struct stat inbuf, outbuf;
11409     int status;
11410
11411     /* Any registered moves are unregistered if unregister is set, */
11412     /* i.e. invoked by the signal handler */
11413     if (unregister) {
11414         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11415             cmailMoveRegistered[i] = FALSE;
11416             if (cmailCommentList[i] != NULL) {
11417                 free(cmailCommentList[i]);
11418                 cmailCommentList[i] = NULL;
11419             }
11420         }
11421         nCmailMovesRegistered = 0;
11422     }
11423
11424     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11425         cmailResult[i] = CMAIL_NOT_RESULT;
11426     }
11427     nCmailResults = 0;
11428
11429     if (inFilename == NULL) {
11430         /* Because the filenames are static they only get malloced once  */
11431         /* and they never get freed                                      */
11432         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11433         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11434
11435         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11436         sprintf(outFilename, "%s.out", appData.cmailGameName);
11437     }
11438
11439     status = stat(outFilename, &outbuf);
11440     if (status < 0) {
11441         cmailMailedMove = FALSE;
11442     } else {
11443         status = stat(inFilename, &inbuf);
11444         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11445     }
11446
11447     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11448        counts the games, notes how each one terminated, etc.
11449
11450        It would be nice to remove this kludge and instead gather all
11451        the information while building the game list.  (And to keep it
11452        in the game list nodes instead of having a bunch of fixed-size
11453        parallel arrays.)  Note this will require getting each game's
11454        termination from the PGN tags, as the game list builder does
11455        not process the game moves.  --mann
11456        */
11457     cmailMsgLoaded = TRUE;
11458     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11459
11460     /* Load first game in the file or popup game menu */
11461     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11462
11463 #endif /* !WIN32 */
11464     return;
11465 }
11466
11467 int
11468 RegisterMove()
11469 {
11470     FILE *f;
11471     char string[MSG_SIZ];
11472
11473     if (   cmailMailedMove
11474         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11475         return TRUE;            /* Allow free viewing  */
11476     }
11477
11478     /* Unregister move to ensure that we don't leave RegisterMove        */
11479     /* with the move registered when the conditions for registering no   */
11480     /* longer hold                                                       */
11481     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11482         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11483         nCmailMovesRegistered --;
11484
11485         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11486           {
11487               free(cmailCommentList[lastLoadGameNumber - 1]);
11488               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11489           }
11490     }
11491
11492     if (cmailOldMove == -1) {
11493         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11494         return FALSE;
11495     }
11496
11497     if (currentMove > cmailOldMove + 1) {
11498         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11499         return FALSE;
11500     }
11501
11502     if (currentMove < cmailOldMove) {
11503         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11504         return FALSE;
11505     }
11506
11507     if (forwardMostMove > currentMove) {
11508         /* Silently truncate extra moves */
11509         TruncateGame();
11510     }
11511
11512     if (   (currentMove == cmailOldMove + 1)
11513         || (   (currentMove == cmailOldMove)
11514             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11515                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11516         if (gameInfo.result != GameUnfinished) {
11517             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11518         }
11519
11520         if (commentList[currentMove] != NULL) {
11521             cmailCommentList[lastLoadGameNumber - 1]
11522               = StrSave(commentList[currentMove]);
11523         }
11524         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11525
11526         if (appData.debugMode)
11527           fprintf(debugFP, "Saving %s for game %d\n",
11528                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11529
11530         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11531
11532         f = fopen(string, "w");
11533         if (appData.oldSaveStyle) {
11534             SaveGameOldStyle(f); /* also closes the file */
11535
11536             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11537             f = fopen(string, "w");
11538             SavePosition(f, 0, NULL); /* also closes the file */
11539         } else {
11540             fprintf(f, "{--------------\n");
11541             PrintPosition(f, currentMove);
11542             fprintf(f, "--------------}\n\n");
11543
11544             SaveGame(f, 0, NULL); /* also closes the file*/
11545         }
11546
11547         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11548         nCmailMovesRegistered ++;
11549     } else if (nCmailGames == 1) {
11550         DisplayError(_("You have not made a move yet"), 0);
11551         return FALSE;
11552     }
11553
11554     return TRUE;
11555 }
11556
11557 void
11558 MailMoveEvent()
11559 {
11560 #if !WIN32
11561     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11562     FILE *commandOutput;
11563     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11564     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11565     int nBuffers;
11566     int i;
11567     int archived;
11568     char *arcDir;
11569
11570     if (! cmailMsgLoaded) {
11571         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11572         return;
11573     }
11574
11575     if (nCmailGames == nCmailResults) {
11576         DisplayError(_("No unfinished games"), 0);
11577         return;
11578     }
11579
11580 #if CMAIL_PROHIBIT_REMAIL
11581     if (cmailMailedMove) {
11582       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);
11583         DisplayError(msg, 0);
11584         return;
11585     }
11586 #endif
11587
11588     if (! (cmailMailedMove || RegisterMove())) return;
11589
11590     if (   cmailMailedMove
11591         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11592       snprintf(string, MSG_SIZ, partCommandString,
11593                appData.debugMode ? " -v" : "", appData.cmailGameName);
11594         commandOutput = popen(string, "r");
11595
11596         if (commandOutput == NULL) {
11597             DisplayError(_("Failed to invoke cmail"), 0);
11598         } else {
11599             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11600                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11601             }
11602             if (nBuffers > 1) {
11603                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11604                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11605                 nBytes = MSG_SIZ - 1;
11606             } else {
11607                 (void) memcpy(msg, buffer, nBytes);
11608             }
11609             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11610
11611             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11612                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11613
11614                 archived = TRUE;
11615                 for (i = 0; i < nCmailGames; i ++) {
11616                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11617                         archived = FALSE;
11618                     }
11619                 }
11620                 if (   archived
11621                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11622                         != NULL)) {
11623                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11624                            arcDir,
11625                            appData.cmailGameName,
11626                            gameInfo.date);
11627                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11628                     cmailMsgLoaded = FALSE;
11629                 }
11630             }
11631
11632             DisplayInformation(msg);
11633             pclose(commandOutput);
11634         }
11635     } else {
11636         if ((*cmailMsg) != '\0') {
11637             DisplayInformation(cmailMsg);
11638         }
11639     }
11640
11641     return;
11642 #endif /* !WIN32 */
11643 }
11644
11645 char *
11646 CmailMsg()
11647 {
11648 #if WIN32
11649     return NULL;
11650 #else
11651     int  prependComma = 0;
11652     char number[5];
11653     char string[MSG_SIZ];       /* Space for game-list */
11654     int  i;
11655
11656     if (!cmailMsgLoaded) return "";
11657
11658     if (cmailMailedMove) {
11659       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11660     } else {
11661         /* Create a list of games left */
11662       snprintf(string, MSG_SIZ, "[");
11663         for (i = 0; i < nCmailGames; i ++) {
11664             if (! (   cmailMoveRegistered[i]
11665                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11666                 if (prependComma) {
11667                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11668                 } else {
11669                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11670                     prependComma = 1;
11671                 }
11672
11673                 strcat(string, number);
11674             }
11675         }
11676         strcat(string, "]");
11677
11678         if (nCmailMovesRegistered + nCmailResults == 0) {
11679             switch (nCmailGames) {
11680               case 1:
11681                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11682                 break;
11683
11684               case 2:
11685                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11686                 break;
11687
11688               default:
11689                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11690                          nCmailGames);
11691                 break;
11692             }
11693         } else {
11694             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11695               case 1:
11696                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11697                          string);
11698                 break;
11699
11700               case 0:
11701                 if (nCmailResults == nCmailGames) {
11702                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11703                 } else {
11704                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11705                 }
11706                 break;
11707
11708               default:
11709                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11710                          string);
11711             }
11712         }
11713     }
11714     return cmailMsg;
11715 #endif /* WIN32 */
11716 }
11717
11718 void
11719 ResetGameEvent()
11720 {
11721     if (gameMode == Training)
11722       SetTrainingModeOff();
11723
11724     Reset(TRUE, TRUE);
11725     cmailMsgLoaded = FALSE;
11726     if (appData.icsActive) {
11727       SendToICS(ics_prefix);
11728       SendToICS("refresh\n");
11729     }
11730 }
11731
11732 void
11733 ExitEvent(status)
11734      int status;
11735 {
11736     exiting++;
11737     if (exiting > 2) {
11738       /* Give up on clean exit */
11739       exit(status);
11740     }
11741     if (exiting > 1) {
11742       /* Keep trying for clean exit */
11743       return;
11744     }
11745
11746     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11747
11748     if (telnetISR != NULL) {
11749       RemoveInputSource(telnetISR);
11750     }
11751     if (icsPR != NoProc) {
11752       DestroyChildProcess(icsPR, TRUE);
11753     }
11754
11755     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11756     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11757
11758     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11759     /* make sure this other one finishes before killing it!                  */
11760     if(endingGame) { int count = 0;
11761         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11762         while(endingGame && count++ < 10) DoSleep(1);
11763         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11764     }
11765
11766     /* Kill off chess programs */
11767     if (first.pr != NoProc) {
11768         ExitAnalyzeMode();
11769
11770         DoSleep( appData.delayBeforeQuit );
11771         SendToProgram("quit\n", &first);
11772         DoSleep( appData.delayAfterQuit );
11773         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11774     }
11775     if (second.pr != NoProc) {
11776         DoSleep( appData.delayBeforeQuit );
11777         SendToProgram("quit\n", &second);
11778         DoSleep( appData.delayAfterQuit );
11779         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11780     }
11781     if (first.isr != NULL) {
11782         RemoveInputSource(first.isr);
11783     }
11784     if (second.isr != NULL) {
11785         RemoveInputSource(second.isr);
11786     }
11787
11788     ShutDownFrontEnd();
11789     exit(status);
11790 }
11791
11792 void
11793 PauseEvent()
11794 {
11795     if (appData.debugMode)
11796         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11797     if (pausing) {
11798         pausing = FALSE;
11799         ModeHighlight();
11800         if (gameMode == MachinePlaysWhite ||
11801             gameMode == MachinePlaysBlack) {
11802             StartClocks();
11803         } else {
11804             DisplayBothClocks();
11805         }
11806         if (gameMode == PlayFromGameFile) {
11807             if (appData.timeDelay >= 0)
11808                 AutoPlayGameLoop();
11809         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11810             Reset(FALSE, TRUE);
11811             SendToICS(ics_prefix);
11812             SendToICS("refresh\n");
11813         } else if (currentMove < forwardMostMove) {
11814             ForwardInner(forwardMostMove);
11815         }
11816         pauseExamInvalid = FALSE;
11817     } else {
11818         switch (gameMode) {
11819           default:
11820             return;
11821           case IcsExamining:
11822             pauseExamForwardMostMove = forwardMostMove;
11823             pauseExamInvalid = FALSE;
11824             /* fall through */
11825           case IcsObserving:
11826           case IcsPlayingWhite:
11827           case IcsPlayingBlack:
11828             pausing = TRUE;
11829             ModeHighlight();
11830             return;
11831           case PlayFromGameFile:
11832             (void) StopLoadGameTimer();
11833             pausing = TRUE;
11834             ModeHighlight();
11835             break;
11836           case BeginningOfGame:
11837             if (appData.icsActive) return;
11838             /* else fall through */
11839           case MachinePlaysWhite:
11840           case MachinePlaysBlack:
11841           case TwoMachinesPlay:
11842             if (forwardMostMove == 0)
11843               return;           /* don't pause if no one has moved */
11844             if ((gameMode == MachinePlaysWhite &&
11845                  !WhiteOnMove(forwardMostMove)) ||
11846                 (gameMode == MachinePlaysBlack &&
11847                  WhiteOnMove(forwardMostMove))) {
11848                 StopClocks();
11849             }
11850             pausing = TRUE;
11851             ModeHighlight();
11852             break;
11853         }
11854     }
11855 }
11856
11857 void
11858 EditCommentEvent()
11859 {
11860     char title[MSG_SIZ];
11861
11862     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11863       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11864     } else {
11865       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11866                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11867                parseList[currentMove - 1]);
11868     }
11869
11870     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11871 }
11872
11873
11874 void
11875 EditTagsEvent()
11876 {
11877     char *tags = PGNTags(&gameInfo);
11878     EditTagsPopUp(tags, NULL);
11879     free(tags);
11880 }
11881
11882 void
11883 AnalyzeModeEvent()
11884 {
11885     if (appData.noChessProgram || gameMode == AnalyzeMode)
11886       return;
11887
11888     if (gameMode != AnalyzeFile) {
11889         if (!appData.icsEngineAnalyze) {
11890                EditGameEvent();
11891                if (gameMode != EditGame) return;
11892         }
11893         ResurrectChessProgram();
11894         SendToProgram("analyze\n", &first);
11895         first.analyzing = TRUE;
11896         /*first.maybeThinking = TRUE;*/
11897         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11898         EngineOutputPopUp();
11899     }
11900     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11901     pausing = FALSE;
11902     ModeHighlight();
11903     SetGameInfo();
11904
11905     StartAnalysisClock();
11906     GetTimeMark(&lastNodeCountTime);
11907     lastNodeCount = 0;
11908 }
11909
11910 void
11911 AnalyzeFileEvent()
11912 {
11913     if (appData.noChessProgram || gameMode == AnalyzeFile)
11914       return;
11915
11916     if (gameMode != AnalyzeMode) {
11917         EditGameEvent();
11918         if (gameMode != EditGame) return;
11919         ResurrectChessProgram();
11920         SendToProgram("analyze\n", &first);
11921         first.analyzing = TRUE;
11922         /*first.maybeThinking = TRUE;*/
11923         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11924         EngineOutputPopUp();
11925     }
11926     gameMode = AnalyzeFile;
11927     pausing = FALSE;
11928     ModeHighlight();
11929     SetGameInfo();
11930
11931     StartAnalysisClock();
11932     GetTimeMark(&lastNodeCountTime);
11933     lastNodeCount = 0;
11934 }
11935
11936 void
11937 MachineWhiteEvent()
11938 {
11939     char buf[MSG_SIZ];
11940     char *bookHit = NULL;
11941
11942     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11943       return;
11944
11945
11946     if (gameMode == PlayFromGameFile ||
11947         gameMode == TwoMachinesPlay  ||
11948         gameMode == Training         ||
11949         gameMode == AnalyzeMode      ||
11950         gameMode == EndOfGame)
11951         EditGameEvent();
11952
11953     if (gameMode == EditPosition)
11954         EditPositionDone(TRUE);
11955
11956     if (!WhiteOnMove(currentMove)) {
11957         DisplayError(_("It is not White's turn"), 0);
11958         return;
11959     }
11960
11961     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11962       ExitAnalyzeMode();
11963
11964     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11965         gameMode == AnalyzeFile)
11966         TruncateGame();
11967
11968     ResurrectChessProgram();    /* in case it isn't running */
11969     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11970         gameMode = MachinePlaysWhite;
11971         ResetClocks();
11972     } else
11973     gameMode = MachinePlaysWhite;
11974     pausing = FALSE;
11975     ModeHighlight();
11976     SetGameInfo();
11977     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11978     DisplayTitle(buf);
11979     if (first.sendName) {
11980       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11981       SendToProgram(buf, &first);
11982     }
11983     if (first.sendTime) {
11984       if (first.useColors) {
11985         SendToProgram("black\n", &first); /*gnu kludge*/
11986       }
11987       SendTimeRemaining(&first, TRUE);
11988     }
11989     if (first.useColors) {
11990       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11991     }
11992     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11993     SetMachineThinkingEnables();
11994     first.maybeThinking = TRUE;
11995     StartClocks();
11996     firstMove = FALSE;
11997
11998     if (appData.autoFlipView && !flipView) {
11999       flipView = !flipView;
12000       DrawPosition(FALSE, NULL);
12001       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12002     }
12003
12004     if(bookHit) { // [HGM] book: simulate book reply
12005         static char bookMove[MSG_SIZ]; // a bit generous?
12006
12007         programStats.nodes = programStats.depth = programStats.time =
12008         programStats.score = programStats.got_only_move = 0;
12009         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12010
12011         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12012         strcat(bookMove, bookHit);
12013         HandleMachineMove(bookMove, &first);
12014     }
12015 }
12016
12017 void
12018 MachineBlackEvent()
12019 {
12020   char buf[MSG_SIZ];
12021   char *bookHit = NULL;
12022
12023     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12024         return;
12025
12026
12027     if (gameMode == PlayFromGameFile ||
12028         gameMode == TwoMachinesPlay  ||
12029         gameMode == Training         ||
12030         gameMode == AnalyzeMode      ||
12031         gameMode == EndOfGame)
12032         EditGameEvent();
12033
12034     if (gameMode == EditPosition)
12035         EditPositionDone(TRUE);
12036
12037     if (WhiteOnMove(currentMove)) {
12038         DisplayError(_("It is not Black's turn"), 0);
12039         return;
12040     }
12041
12042     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12043       ExitAnalyzeMode();
12044
12045     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12046         gameMode == AnalyzeFile)
12047         TruncateGame();
12048
12049     ResurrectChessProgram();    /* in case it isn't running */
12050     gameMode = MachinePlaysBlack;
12051     pausing = FALSE;
12052     ModeHighlight();
12053     SetGameInfo();
12054     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12055     DisplayTitle(buf);
12056     if (first.sendName) {
12057       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12058       SendToProgram(buf, &first);
12059     }
12060     if (first.sendTime) {
12061       if (first.useColors) {
12062         SendToProgram("white\n", &first); /*gnu kludge*/
12063       }
12064       SendTimeRemaining(&first, FALSE);
12065     }
12066     if (first.useColors) {
12067       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12068     }
12069     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12070     SetMachineThinkingEnables();
12071     first.maybeThinking = TRUE;
12072     StartClocks();
12073
12074     if (appData.autoFlipView && flipView) {
12075       flipView = !flipView;
12076       DrawPosition(FALSE, NULL);
12077       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12078     }
12079     if(bookHit) { // [HGM] book: simulate book reply
12080         static char bookMove[MSG_SIZ]; // a bit generous?
12081
12082         programStats.nodes = programStats.depth = programStats.time =
12083         programStats.score = programStats.got_only_move = 0;
12084         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12085
12086         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12087         strcat(bookMove, bookHit);
12088         HandleMachineMove(bookMove, &first);
12089     }
12090 }
12091
12092
12093 void
12094 DisplayTwoMachinesTitle()
12095 {
12096     char buf[MSG_SIZ];
12097     if (appData.matchGames > 0) {
12098         if (first.twoMachinesColor[0] == 'w') {
12099           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12100                    gameInfo.white, gameInfo.black,
12101                    first.matchWins, second.matchWins,
12102                    matchGame - 1 - (first.matchWins + second.matchWins));
12103         } else {
12104           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12105                    gameInfo.white, gameInfo.black,
12106                    second.matchWins, first.matchWins,
12107                    matchGame - 1 - (first.matchWins + second.matchWins));
12108         }
12109     } else {
12110       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12111     }
12112     DisplayTitle(buf);
12113 }
12114
12115 void
12116 SettingsMenuIfReady()
12117 {
12118   if (second.lastPing != second.lastPong) {
12119     DisplayMessage("", _("Waiting for second chess program"));
12120     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12121     return;
12122   }
12123   ThawUI();
12124   DisplayMessage("", "");
12125   SettingsPopUp(&second);
12126 }
12127
12128 int
12129 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12130 {
12131     char buf[MSG_SIZ];
12132     if (cps->pr == NULL) {
12133         StartChessProgram(cps);
12134         if (cps->protocolVersion == 1) {
12135           retry();
12136         } else {
12137           /* kludge: allow timeout for initial "feature" command */
12138           FreezeUI();
12139           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12140           DisplayMessage("", buf);
12141           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12142         }
12143         return 1;
12144     }
12145     return 0;
12146 }
12147
12148 void
12149 TwoMachinesEvent P((void))
12150 {
12151     int i;
12152     char buf[MSG_SIZ];
12153     ChessProgramState *onmove;
12154     char *bookHit = NULL;
12155     static int stalling = 0;
12156
12157     if (appData.noChessProgram) return;
12158
12159     switch (gameMode) {
12160       case TwoMachinesPlay:
12161         return;
12162       case MachinePlaysWhite:
12163       case MachinePlaysBlack:
12164         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12165             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12166             return;
12167         }
12168         /* fall through */
12169       case BeginningOfGame:
12170       case PlayFromGameFile:
12171       case EndOfGame:
12172         EditGameEvent();
12173         if (gameMode != EditGame) return;
12174         break;
12175       case EditPosition:
12176         EditPositionDone(TRUE);
12177         break;
12178       case AnalyzeMode:
12179       case AnalyzeFile:
12180         ExitAnalyzeMode();
12181         break;
12182       case EditGame:
12183       default:
12184         break;
12185     }
12186
12187 //    forwardMostMove = currentMove;
12188     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12189     ResurrectChessProgram();    /* in case first program isn't running */
12190
12191     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
12192     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12193       DisplayMessage("", _("Waiting for first chess program"));
12194       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12195       return;
12196     }
12197     if(!stalling) {
12198       InitChessProgram(&second, FALSE);
12199       SendToProgram("force\n", &second);
12200     }
12201     if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12202       if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12203       stalling = 1;
12204       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12205       return;
12206     }
12207     stalling = 0;
12208     DisplayMessage("", "");
12209     if (startedFromSetupPosition) {
12210         SendBoard(&second, backwardMostMove);
12211     if (appData.debugMode) {
12212         fprintf(debugFP, "Two Machines\n");
12213     }
12214     }
12215     for (i = backwardMostMove; i < forwardMostMove; i++) {
12216         SendMoveToProgram(i, &second);
12217     }
12218
12219     gameMode = TwoMachinesPlay;
12220     pausing = FALSE;
12221     ModeHighlight();
12222     SetGameInfo();
12223     DisplayTwoMachinesTitle();
12224     firstMove = TRUE;
12225     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12226         onmove = &first;
12227     } else {
12228         onmove = &second;
12229     }
12230
12231     SendToProgram(first.computerString, &first);
12232     if (first.sendName) {
12233       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12234       SendToProgram(buf, &first);
12235     }
12236     SendToProgram(second.computerString, &second);
12237     if (second.sendName) {
12238       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12239       SendToProgram(buf, &second);
12240     }
12241
12242     ResetClocks();
12243     if (!first.sendTime || !second.sendTime) {
12244         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12245         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12246     }
12247     if (onmove->sendTime) {
12248       if (onmove->useColors) {
12249         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12250       }
12251       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12252     }
12253     if (onmove->useColors) {
12254       SendToProgram(onmove->twoMachinesColor, onmove);
12255     }
12256     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12257 //    SendToProgram("go\n", onmove);
12258     onmove->maybeThinking = TRUE;
12259     SetMachineThinkingEnables();
12260
12261     StartClocks();
12262
12263     if(bookHit) { // [HGM] book: simulate book reply
12264         static char bookMove[MSG_SIZ]; // a bit generous?
12265
12266         programStats.nodes = programStats.depth = programStats.time =
12267         programStats.score = programStats.got_only_move = 0;
12268         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12269
12270         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12271         strcat(bookMove, bookHit);
12272         savedMessage = bookMove; // args for deferred call
12273         savedState = onmove;
12274         ScheduleDelayedEvent(DeferredBookMove, 1);
12275     }
12276 }
12277
12278 void
12279 TrainingEvent()
12280 {
12281     if (gameMode == Training) {
12282       SetTrainingModeOff();
12283       gameMode = PlayFromGameFile;
12284       DisplayMessage("", _("Training mode off"));
12285     } else {
12286       gameMode = Training;
12287       animateTraining = appData.animate;
12288
12289       /* make sure we are not already at the end of the game */
12290       if (currentMove < forwardMostMove) {
12291         SetTrainingModeOn();
12292         DisplayMessage("", _("Training mode on"));
12293       } else {
12294         gameMode = PlayFromGameFile;
12295         DisplayError(_("Already at end of game"), 0);
12296       }
12297     }
12298     ModeHighlight();
12299 }
12300
12301 void
12302 IcsClientEvent()
12303 {
12304     if (!appData.icsActive) return;
12305     switch (gameMode) {
12306       case IcsPlayingWhite:
12307       case IcsPlayingBlack:
12308       case IcsObserving:
12309       case IcsIdle:
12310       case BeginningOfGame:
12311       case IcsExamining:
12312         return;
12313
12314       case EditGame:
12315         break;
12316
12317       case EditPosition:
12318         EditPositionDone(TRUE);
12319         break;
12320
12321       case AnalyzeMode:
12322       case AnalyzeFile:
12323         ExitAnalyzeMode();
12324         break;
12325
12326       default:
12327         EditGameEvent();
12328         break;
12329     }
12330
12331     gameMode = IcsIdle;
12332     ModeHighlight();
12333     return;
12334 }
12335
12336
12337 void
12338 EditGameEvent()
12339 {
12340     int i;
12341
12342     switch (gameMode) {
12343       case Training:
12344         SetTrainingModeOff();
12345         break;
12346       case MachinePlaysWhite:
12347       case MachinePlaysBlack:
12348       case BeginningOfGame:
12349         SendToProgram("force\n", &first);
12350         SetUserThinkingEnables();
12351         break;
12352       case PlayFromGameFile:
12353         (void) StopLoadGameTimer();
12354         if (gameFileFP != NULL) {
12355             gameFileFP = NULL;
12356         }
12357         break;
12358       case EditPosition:
12359         EditPositionDone(TRUE);
12360         break;
12361       case AnalyzeMode:
12362       case AnalyzeFile:
12363         ExitAnalyzeMode();
12364         SendToProgram("force\n", &first);
12365         break;
12366       case TwoMachinesPlay:
12367         GameEnds(EndOfFile, NULL, GE_PLAYER);
12368         ResurrectChessProgram();
12369         SetUserThinkingEnables();
12370         break;
12371       case EndOfGame:
12372         ResurrectChessProgram();
12373         break;
12374       case IcsPlayingBlack:
12375       case IcsPlayingWhite:
12376         DisplayError(_("Warning: You are still playing a game"), 0);
12377         break;
12378       case IcsObserving:
12379         DisplayError(_("Warning: You are still observing a game"), 0);
12380         break;
12381       case IcsExamining:
12382         DisplayError(_("Warning: You are still examining a game"), 0);
12383         break;
12384       case IcsIdle:
12385         break;
12386       case EditGame:
12387       default:
12388         return;
12389     }
12390
12391     pausing = FALSE;
12392     StopClocks();
12393     first.offeredDraw = second.offeredDraw = 0;
12394
12395     if (gameMode == PlayFromGameFile) {
12396         whiteTimeRemaining = timeRemaining[0][currentMove];
12397         blackTimeRemaining = timeRemaining[1][currentMove];
12398         DisplayTitle("");
12399     }
12400
12401     if (gameMode == MachinePlaysWhite ||
12402         gameMode == MachinePlaysBlack ||
12403         gameMode == TwoMachinesPlay ||
12404         gameMode == EndOfGame) {
12405         i = forwardMostMove;
12406         while (i > currentMove) {
12407             SendToProgram("undo\n", &first);
12408             i--;
12409         }
12410         whiteTimeRemaining = timeRemaining[0][currentMove];
12411         blackTimeRemaining = timeRemaining[1][currentMove];
12412         DisplayBothClocks();
12413         if (whiteFlag || blackFlag) {
12414             whiteFlag = blackFlag = 0;
12415         }
12416         DisplayTitle("");
12417     }
12418
12419     gameMode = EditGame;
12420     ModeHighlight();
12421     SetGameInfo();
12422 }
12423
12424
12425 void
12426 EditPositionEvent()
12427 {
12428     if (gameMode == EditPosition) {
12429         EditGameEvent();
12430         return;
12431     }
12432
12433     EditGameEvent();
12434     if (gameMode != EditGame) return;
12435
12436     gameMode = EditPosition;
12437     ModeHighlight();
12438     SetGameInfo();
12439     if (currentMove > 0)
12440       CopyBoard(boards[0], boards[currentMove]);
12441
12442     blackPlaysFirst = !WhiteOnMove(currentMove);
12443     ResetClocks();
12444     currentMove = forwardMostMove = backwardMostMove = 0;
12445     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12446     DisplayMove(-1);
12447 }
12448
12449 void
12450 ExitAnalyzeMode()
12451 {
12452     /* [DM] icsEngineAnalyze - possible call from other functions */
12453     if (appData.icsEngineAnalyze) {
12454         appData.icsEngineAnalyze = FALSE;
12455
12456         DisplayMessage("",_("Close ICS engine analyze..."));
12457     }
12458     if (first.analysisSupport && first.analyzing) {
12459       SendToProgram("exit\n", &first);
12460       first.analyzing = FALSE;
12461     }
12462     thinkOutput[0] = NULLCHAR;
12463 }
12464
12465 void
12466 EditPositionDone(Boolean fakeRights)
12467 {
12468     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12469
12470     startedFromSetupPosition = TRUE;
12471     InitChessProgram(&first, FALSE);
12472     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12473       boards[0][EP_STATUS] = EP_NONE;
12474       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12475     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12476         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12477         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12478       } else boards[0][CASTLING][2] = NoRights;
12479     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12480         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12481         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12482       } else boards[0][CASTLING][5] = NoRights;
12483     }
12484     SendToProgram("force\n", &first);
12485     if (blackPlaysFirst) {
12486         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12487         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12488         currentMove = forwardMostMove = backwardMostMove = 1;
12489         CopyBoard(boards[1], boards[0]);
12490     } else {
12491         currentMove = forwardMostMove = backwardMostMove = 0;
12492     }
12493     SendBoard(&first, forwardMostMove);
12494     if (appData.debugMode) {
12495         fprintf(debugFP, "EditPosDone\n");
12496     }
12497     DisplayTitle("");
12498     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12499     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12500     gameMode = EditGame;
12501     ModeHighlight();
12502     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12503     ClearHighlights(); /* [AS] */
12504 }
12505
12506 /* Pause for `ms' milliseconds */
12507 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12508 void
12509 TimeDelay(ms)
12510      long ms;
12511 {
12512     TimeMark m1, m2;
12513
12514     GetTimeMark(&m1);
12515     do {
12516         GetTimeMark(&m2);
12517     } while (SubtractTimeMarks(&m2, &m1) < ms);
12518 }
12519
12520 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12521 void
12522 SendMultiLineToICS(buf)
12523      char *buf;
12524 {
12525     char temp[MSG_SIZ+1], *p;
12526     int len;
12527
12528     len = strlen(buf);
12529     if (len > MSG_SIZ)
12530       len = MSG_SIZ;
12531
12532     strncpy(temp, buf, len);
12533     temp[len] = 0;
12534
12535     p = temp;
12536     while (*p) {
12537         if (*p == '\n' || *p == '\r')
12538           *p = ' ';
12539         ++p;
12540     }
12541
12542     strcat(temp, "\n");
12543     SendToICS(temp);
12544     SendToPlayer(temp, strlen(temp));
12545 }
12546
12547 void
12548 SetWhiteToPlayEvent()
12549 {
12550     if (gameMode == EditPosition) {
12551         blackPlaysFirst = FALSE;
12552         DisplayBothClocks();    /* works because currentMove is 0 */
12553     } else if (gameMode == IcsExamining) {
12554         SendToICS(ics_prefix);
12555         SendToICS("tomove white\n");
12556     }
12557 }
12558
12559 void
12560 SetBlackToPlayEvent()
12561 {
12562     if (gameMode == EditPosition) {
12563         blackPlaysFirst = TRUE;
12564         currentMove = 1;        /* kludge */
12565         DisplayBothClocks();
12566         currentMove = 0;
12567     } else if (gameMode == IcsExamining) {
12568         SendToICS(ics_prefix);
12569         SendToICS("tomove black\n");
12570     }
12571 }
12572
12573 void
12574 EditPositionMenuEvent(selection, x, y)
12575      ChessSquare selection;
12576      int x, y;
12577 {
12578     char buf[MSG_SIZ];
12579     ChessSquare piece = boards[0][y][x];
12580
12581     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12582
12583     switch (selection) {
12584       case ClearBoard:
12585         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12586             SendToICS(ics_prefix);
12587             SendToICS("bsetup clear\n");
12588         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12589             SendToICS(ics_prefix);
12590             SendToICS("clearboard\n");
12591         } else {
12592             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12593                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12594                 for (y = 0; y < BOARD_HEIGHT; y++) {
12595                     if (gameMode == IcsExamining) {
12596                         if (boards[currentMove][y][x] != EmptySquare) {
12597                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12598                                     AAA + x, ONE + y);
12599                             SendToICS(buf);
12600                         }
12601                     } else {
12602                         boards[0][y][x] = p;
12603                     }
12604                 }
12605             }
12606         }
12607         if (gameMode == EditPosition) {
12608             DrawPosition(FALSE, boards[0]);
12609         }
12610         break;
12611
12612       case WhitePlay:
12613         SetWhiteToPlayEvent();
12614         break;
12615
12616       case BlackPlay:
12617         SetBlackToPlayEvent();
12618         break;
12619
12620       case EmptySquare:
12621         if (gameMode == IcsExamining) {
12622             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12623             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12624             SendToICS(buf);
12625         } else {
12626             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12627                 if(x == BOARD_LEFT-2) {
12628                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12629                     boards[0][y][1] = 0;
12630                 } else
12631                 if(x == BOARD_RGHT+1) {
12632                     if(y >= gameInfo.holdingsSize) break;
12633                     boards[0][y][BOARD_WIDTH-2] = 0;
12634                 } else break;
12635             }
12636             boards[0][y][x] = EmptySquare;
12637             DrawPosition(FALSE, boards[0]);
12638         }
12639         break;
12640
12641       case PromotePiece:
12642         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12643            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12644             selection = (ChessSquare) (PROMOTED piece);
12645         } else if(piece == EmptySquare) selection = WhiteSilver;
12646         else selection = (ChessSquare)((int)piece - 1);
12647         goto defaultlabel;
12648
12649       case DemotePiece:
12650         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12651            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12652             selection = (ChessSquare) (DEMOTED piece);
12653         } else if(piece == EmptySquare) selection = BlackSilver;
12654         else selection = (ChessSquare)((int)piece + 1);
12655         goto defaultlabel;
12656
12657       case WhiteQueen:
12658       case BlackQueen:
12659         if(gameInfo.variant == VariantShatranj ||
12660            gameInfo.variant == VariantXiangqi  ||
12661            gameInfo.variant == VariantCourier  ||
12662            gameInfo.variant == VariantMakruk     )
12663             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12664         goto defaultlabel;
12665
12666       case WhiteKing:
12667       case BlackKing:
12668         if(gameInfo.variant == VariantXiangqi)
12669             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12670         if(gameInfo.variant == VariantKnightmate)
12671             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12672       default:
12673         defaultlabel:
12674         if (gameMode == IcsExamining) {
12675             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12676             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12677                      PieceToChar(selection), AAA + x, ONE + y);
12678             SendToICS(buf);
12679         } else {
12680             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12681                 int n;
12682                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12683                     n = PieceToNumber(selection - BlackPawn);
12684                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12685                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12686                     boards[0][BOARD_HEIGHT-1-n][1]++;
12687                 } else
12688                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12689                     n = PieceToNumber(selection);
12690                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12691                     boards[0][n][BOARD_WIDTH-1] = selection;
12692                     boards[0][n][BOARD_WIDTH-2]++;
12693                 }
12694             } else
12695             boards[0][y][x] = selection;
12696             DrawPosition(TRUE, boards[0]);
12697         }
12698         break;
12699     }
12700 }
12701
12702
12703 void
12704 DropMenuEvent(selection, x, y)
12705      ChessSquare selection;
12706      int x, y;
12707 {
12708     ChessMove moveType;
12709
12710     switch (gameMode) {
12711       case IcsPlayingWhite:
12712       case MachinePlaysBlack:
12713         if (!WhiteOnMove(currentMove)) {
12714             DisplayMoveError(_("It is Black's turn"));
12715             return;
12716         }
12717         moveType = WhiteDrop;
12718         break;
12719       case IcsPlayingBlack:
12720       case MachinePlaysWhite:
12721         if (WhiteOnMove(currentMove)) {
12722             DisplayMoveError(_("It is White's turn"));
12723             return;
12724         }
12725         moveType = BlackDrop;
12726         break;
12727       case EditGame:
12728         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12729         break;
12730       default:
12731         return;
12732     }
12733
12734     if (moveType == BlackDrop && selection < BlackPawn) {
12735       selection = (ChessSquare) ((int) selection
12736                                  + (int) BlackPawn - (int) WhitePawn);
12737     }
12738     if (boards[currentMove][y][x] != EmptySquare) {
12739         DisplayMoveError(_("That square is occupied"));
12740         return;
12741     }
12742
12743     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12744 }
12745
12746 void
12747 AcceptEvent()
12748 {
12749     /* Accept a pending offer of any kind from opponent */
12750
12751     if (appData.icsActive) {
12752         SendToICS(ics_prefix);
12753         SendToICS("accept\n");
12754     } else if (cmailMsgLoaded) {
12755         if (currentMove == cmailOldMove &&
12756             commentList[cmailOldMove] != NULL &&
12757             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12758                    "Black offers a draw" : "White offers a draw")) {
12759             TruncateGame();
12760             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12761             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12762         } else {
12763             DisplayError(_("There is no pending offer on this move"), 0);
12764             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12765         }
12766     } else {
12767         /* Not used for offers from chess program */
12768     }
12769 }
12770
12771 void
12772 DeclineEvent()
12773 {
12774     /* Decline a pending offer of any kind from opponent */
12775
12776     if (appData.icsActive) {
12777         SendToICS(ics_prefix);
12778         SendToICS("decline\n");
12779     } else if (cmailMsgLoaded) {
12780         if (currentMove == cmailOldMove &&
12781             commentList[cmailOldMove] != NULL &&
12782             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12783                    "Black offers a draw" : "White offers a draw")) {
12784 #ifdef NOTDEF
12785             AppendComment(cmailOldMove, "Draw declined", TRUE);
12786             DisplayComment(cmailOldMove - 1, "Draw declined");
12787 #endif /*NOTDEF*/
12788         } else {
12789             DisplayError(_("There is no pending offer on this move"), 0);
12790         }
12791     } else {
12792         /* Not used for offers from chess program */
12793     }
12794 }
12795
12796 void
12797 RematchEvent()
12798 {
12799     /* Issue ICS rematch command */
12800     if (appData.icsActive) {
12801         SendToICS(ics_prefix);
12802         SendToICS("rematch\n");
12803     }
12804 }
12805
12806 void
12807 CallFlagEvent()
12808 {
12809     /* Call your opponent's flag (claim a win on time) */
12810     if (appData.icsActive) {
12811         SendToICS(ics_prefix);
12812         SendToICS("flag\n");
12813     } else {
12814         switch (gameMode) {
12815           default:
12816             return;
12817           case MachinePlaysWhite:
12818             if (whiteFlag) {
12819                 if (blackFlag)
12820                   GameEnds(GameIsDrawn, "Both players ran out of time",
12821                            GE_PLAYER);
12822                 else
12823                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12824             } else {
12825                 DisplayError(_("Your opponent is not out of time"), 0);
12826             }
12827             break;
12828           case MachinePlaysBlack:
12829             if (blackFlag) {
12830                 if (whiteFlag)
12831                   GameEnds(GameIsDrawn, "Both players ran out of time",
12832                            GE_PLAYER);
12833                 else
12834                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12835             } else {
12836                 DisplayError(_("Your opponent is not out of time"), 0);
12837             }
12838             break;
12839         }
12840     }
12841 }
12842
12843 void
12844 ClockClick(int which)
12845 {       // [HGM] code moved to back-end from winboard.c
12846         if(which) { // black clock
12847           if (gameMode == EditPosition || gameMode == IcsExamining) {
12848             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12849             SetBlackToPlayEvent();
12850           } else if (gameMode == EditGame || shiftKey) {
12851             AdjustClock(which, -1);
12852           } else if (gameMode == IcsPlayingWhite ||
12853                      gameMode == MachinePlaysBlack) {
12854             CallFlagEvent();
12855           }
12856         } else { // white clock
12857           if (gameMode == EditPosition || gameMode == IcsExamining) {
12858             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12859             SetWhiteToPlayEvent();
12860           } else if (gameMode == EditGame || shiftKey) {
12861             AdjustClock(which, -1);
12862           } else if (gameMode == IcsPlayingBlack ||
12863                    gameMode == MachinePlaysWhite) {
12864             CallFlagEvent();
12865           }
12866         }
12867 }
12868
12869 void
12870 DrawEvent()
12871 {
12872     /* Offer draw or accept pending draw offer from opponent */
12873
12874     if (appData.icsActive) {
12875         /* Note: tournament rules require draw offers to be
12876            made after you make your move but before you punch
12877            your clock.  Currently ICS doesn't let you do that;
12878            instead, you immediately punch your clock after making
12879            a move, but you can offer a draw at any time. */
12880
12881         SendToICS(ics_prefix);
12882         SendToICS("draw\n");
12883         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12884     } else if (cmailMsgLoaded) {
12885         if (currentMove == cmailOldMove &&
12886             commentList[cmailOldMove] != NULL &&
12887             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12888                    "Black offers a draw" : "White offers a draw")) {
12889             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12890             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12891         } else if (currentMove == cmailOldMove + 1) {
12892             char *offer = WhiteOnMove(cmailOldMove) ?
12893               "White offers a draw" : "Black offers a draw";
12894             AppendComment(currentMove, offer, TRUE);
12895             DisplayComment(currentMove - 1, offer);
12896             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12897         } else {
12898             DisplayError(_("You must make your move before offering a draw"), 0);
12899             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12900         }
12901     } else if (first.offeredDraw) {
12902         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12903     } else {
12904         if (first.sendDrawOffers) {
12905             SendToProgram("draw\n", &first);
12906             userOfferedDraw = TRUE;
12907         }
12908     }
12909 }
12910
12911 void
12912 AdjournEvent()
12913 {
12914     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12915
12916     if (appData.icsActive) {
12917         SendToICS(ics_prefix);
12918         SendToICS("adjourn\n");
12919     } else {
12920         /* Currently GNU Chess doesn't offer or accept Adjourns */
12921     }
12922 }
12923
12924
12925 void
12926 AbortEvent()
12927 {
12928     /* Offer Abort or accept pending Abort offer from opponent */
12929
12930     if (appData.icsActive) {
12931         SendToICS(ics_prefix);
12932         SendToICS("abort\n");
12933     } else {
12934         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12935     }
12936 }
12937
12938 void
12939 ResignEvent()
12940 {
12941     /* Resign.  You can do this even if it's not your turn. */
12942
12943     if (appData.icsActive) {
12944         SendToICS(ics_prefix);
12945         SendToICS("resign\n");
12946     } else {
12947         switch (gameMode) {
12948           case MachinePlaysWhite:
12949             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12950             break;
12951           case MachinePlaysBlack:
12952             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12953             break;
12954           case EditGame:
12955             if (cmailMsgLoaded) {
12956                 TruncateGame();
12957                 if (WhiteOnMove(cmailOldMove)) {
12958                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12959                 } else {
12960                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12961                 }
12962                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12963             }
12964             break;
12965           default:
12966             break;
12967         }
12968     }
12969 }
12970
12971
12972 void
12973 StopObservingEvent()
12974 {
12975     /* Stop observing current games */
12976     SendToICS(ics_prefix);
12977     SendToICS("unobserve\n");
12978 }
12979
12980 void
12981 StopExaminingEvent()
12982 {
12983     /* Stop observing current game */
12984     SendToICS(ics_prefix);
12985     SendToICS("unexamine\n");
12986 }
12987
12988 void
12989 ForwardInner(target)
12990      int target;
12991 {
12992     int limit;
12993
12994     if (appData.debugMode)
12995         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12996                 target, currentMove, forwardMostMove);
12997
12998     if (gameMode == EditPosition)
12999       return;
13000
13001     if (gameMode == PlayFromGameFile && !pausing)
13002       PauseEvent();
13003
13004     if (gameMode == IcsExamining && pausing)
13005       limit = pauseExamForwardMostMove;
13006     else
13007       limit = forwardMostMove;
13008
13009     if (target > limit) target = limit;
13010
13011     if (target > 0 && moveList[target - 1][0]) {
13012         int fromX, fromY, toX, toY;
13013         toX = moveList[target - 1][2] - AAA;
13014         toY = moveList[target - 1][3] - ONE;
13015         if (moveList[target - 1][1] == '@') {
13016             if (appData.highlightLastMove) {
13017                 SetHighlights(-1, -1, toX, toY);
13018             }
13019         } else {
13020             fromX = moveList[target - 1][0] - AAA;
13021             fromY = moveList[target - 1][1] - ONE;
13022             if (target == currentMove + 1) {
13023                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13024             }
13025             if (appData.highlightLastMove) {
13026                 SetHighlights(fromX, fromY, toX, toY);
13027             }
13028         }
13029     }
13030     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13031         gameMode == Training || gameMode == PlayFromGameFile ||
13032         gameMode == AnalyzeFile) {
13033         while (currentMove < target) {
13034             SendMoveToProgram(currentMove++, &first);
13035         }
13036     } else {
13037         currentMove = target;
13038     }
13039
13040     if (gameMode == EditGame || gameMode == EndOfGame) {
13041         whiteTimeRemaining = timeRemaining[0][currentMove];
13042         blackTimeRemaining = timeRemaining[1][currentMove];
13043     }
13044     DisplayBothClocks();
13045     DisplayMove(currentMove - 1);
13046     DrawPosition(FALSE, boards[currentMove]);
13047     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13048     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13049         DisplayComment(currentMove - 1, commentList[currentMove]);
13050     }
13051 }
13052
13053
13054 void
13055 ForwardEvent()
13056 {
13057     if (gameMode == IcsExamining && !pausing) {
13058         SendToICS(ics_prefix);
13059         SendToICS("forward\n");
13060     } else {
13061         ForwardInner(currentMove + 1);
13062     }
13063 }
13064
13065 void
13066 ToEndEvent()
13067 {
13068     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13069         /* to optimze, we temporarily turn off analysis mode while we feed
13070          * the remaining moves to the engine. Otherwise we get analysis output
13071          * after each move.
13072          */
13073         if (first.analysisSupport) {
13074           SendToProgram("exit\nforce\n", &first);
13075           first.analyzing = FALSE;
13076         }
13077     }
13078
13079     if (gameMode == IcsExamining && !pausing) {
13080         SendToICS(ics_prefix);
13081         SendToICS("forward 999999\n");
13082     } else {
13083         ForwardInner(forwardMostMove);
13084     }
13085
13086     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13087         /* we have fed all the moves, so reactivate analysis mode */
13088         SendToProgram("analyze\n", &first);
13089         first.analyzing = TRUE;
13090         /*first.maybeThinking = TRUE;*/
13091         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13092     }
13093 }
13094
13095 void
13096 BackwardInner(target)
13097      int target;
13098 {
13099     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13100
13101     if (appData.debugMode)
13102         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13103                 target, currentMove, forwardMostMove);
13104
13105     if (gameMode == EditPosition) return;
13106     if (currentMove <= backwardMostMove) {
13107         ClearHighlights();
13108         DrawPosition(full_redraw, boards[currentMove]);
13109         return;
13110     }
13111     if (gameMode == PlayFromGameFile && !pausing)
13112       PauseEvent();
13113
13114     if (moveList[target][0]) {
13115         int fromX, fromY, toX, toY;
13116         toX = moveList[target][2] - AAA;
13117         toY = moveList[target][3] - ONE;
13118         if (moveList[target][1] == '@') {
13119             if (appData.highlightLastMove) {
13120                 SetHighlights(-1, -1, toX, toY);
13121             }
13122         } else {
13123             fromX = moveList[target][0] - AAA;
13124             fromY = moveList[target][1] - ONE;
13125             if (target == currentMove - 1) {
13126                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13127             }
13128             if (appData.highlightLastMove) {
13129                 SetHighlights(fromX, fromY, toX, toY);
13130             }
13131         }
13132     }
13133     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13134         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13135         while (currentMove > target) {
13136             SendToProgram("undo\n", &first);
13137             currentMove--;
13138         }
13139     } else {
13140         currentMove = target;
13141     }
13142
13143     if (gameMode == EditGame || gameMode == EndOfGame) {
13144         whiteTimeRemaining = timeRemaining[0][currentMove];
13145         blackTimeRemaining = timeRemaining[1][currentMove];
13146     }
13147     DisplayBothClocks();
13148     DisplayMove(currentMove - 1);
13149     DrawPosition(full_redraw, boards[currentMove]);
13150     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13151     // [HGM] PV info: routine tests if comment empty
13152     DisplayComment(currentMove - 1, commentList[currentMove]);
13153 }
13154
13155 void
13156 BackwardEvent()
13157 {
13158     if (gameMode == IcsExamining && !pausing) {
13159         SendToICS(ics_prefix);
13160         SendToICS("backward\n");
13161     } else {
13162         BackwardInner(currentMove - 1);
13163     }
13164 }
13165
13166 void
13167 ToStartEvent()
13168 {
13169     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13170         /* to optimize, we temporarily turn off analysis mode while we undo
13171          * all the moves. Otherwise we get analysis output after each undo.
13172          */
13173         if (first.analysisSupport) {
13174           SendToProgram("exit\nforce\n", &first);
13175           first.analyzing = FALSE;
13176         }
13177     }
13178
13179     if (gameMode == IcsExamining && !pausing) {
13180         SendToICS(ics_prefix);
13181         SendToICS("backward 999999\n");
13182     } else {
13183         BackwardInner(backwardMostMove);
13184     }
13185
13186     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13187         /* we have fed all the moves, so reactivate analysis mode */
13188         SendToProgram("analyze\n", &first);
13189         first.analyzing = TRUE;
13190         /*first.maybeThinking = TRUE;*/
13191         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13192     }
13193 }
13194
13195 void
13196 ToNrEvent(int to)
13197 {
13198   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13199   if (to >= forwardMostMove) to = forwardMostMove;
13200   if (to <= backwardMostMove) to = backwardMostMove;
13201   if (to < currentMove) {
13202     BackwardInner(to);
13203   } else {
13204     ForwardInner(to);
13205   }
13206 }
13207
13208 void
13209 RevertEvent(Boolean annotate)
13210 {
13211     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13212         return;
13213     }
13214     if (gameMode != IcsExamining) {
13215         DisplayError(_("You are not examining a game"), 0);
13216         return;
13217     }
13218     if (pausing) {
13219         DisplayError(_("You can't revert while pausing"), 0);
13220         return;
13221     }
13222     SendToICS(ics_prefix);
13223     SendToICS("revert\n");
13224 }
13225
13226 void
13227 RetractMoveEvent()
13228 {
13229     switch (gameMode) {
13230       case MachinePlaysWhite:
13231       case MachinePlaysBlack:
13232         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13233             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13234             return;
13235         }
13236         if (forwardMostMove < 2) return;
13237         currentMove = forwardMostMove = forwardMostMove - 2;
13238         whiteTimeRemaining = timeRemaining[0][currentMove];
13239         blackTimeRemaining = timeRemaining[1][currentMove];
13240         DisplayBothClocks();
13241         DisplayMove(currentMove - 1);
13242         ClearHighlights();/*!! could figure this out*/
13243         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13244         SendToProgram("remove\n", &first);
13245         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13246         break;
13247
13248       case BeginningOfGame:
13249       default:
13250         break;
13251
13252       case IcsPlayingWhite:
13253       case IcsPlayingBlack:
13254         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13255             SendToICS(ics_prefix);
13256             SendToICS("takeback 2\n");
13257         } else {
13258             SendToICS(ics_prefix);
13259             SendToICS("takeback 1\n");
13260         }
13261         break;
13262     }
13263 }
13264
13265 void
13266 MoveNowEvent()
13267 {
13268     ChessProgramState *cps;
13269
13270     switch (gameMode) {
13271       case MachinePlaysWhite:
13272         if (!WhiteOnMove(forwardMostMove)) {
13273             DisplayError(_("It is your turn"), 0);
13274             return;
13275         }
13276         cps = &first;
13277         break;
13278       case MachinePlaysBlack:
13279         if (WhiteOnMove(forwardMostMove)) {
13280             DisplayError(_("It is your turn"), 0);
13281             return;
13282         }
13283         cps = &first;
13284         break;
13285       case TwoMachinesPlay:
13286         if (WhiteOnMove(forwardMostMove) ==
13287             (first.twoMachinesColor[0] == 'w')) {
13288             cps = &first;
13289         } else {
13290             cps = &second;
13291         }
13292         break;
13293       case BeginningOfGame:
13294       default:
13295         return;
13296     }
13297     SendToProgram("?\n", cps);
13298 }
13299
13300 void
13301 TruncateGameEvent()
13302 {
13303     EditGameEvent();
13304     if (gameMode != EditGame) return;
13305     TruncateGame();
13306 }
13307
13308 void
13309 TruncateGame()
13310 {
13311     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13312     if (forwardMostMove > currentMove) {
13313         if (gameInfo.resultDetails != NULL) {
13314             free(gameInfo.resultDetails);
13315             gameInfo.resultDetails = NULL;
13316             gameInfo.result = GameUnfinished;
13317         }
13318         forwardMostMove = currentMove;
13319         HistorySet(parseList, backwardMostMove, forwardMostMove,
13320                    currentMove-1);
13321     }
13322 }
13323
13324 void
13325 HintEvent()
13326 {
13327     if (appData.noChessProgram) return;
13328     switch (gameMode) {
13329       case MachinePlaysWhite:
13330         if (WhiteOnMove(forwardMostMove)) {
13331             DisplayError(_("Wait until your turn"), 0);
13332             return;
13333         }
13334         break;
13335       case BeginningOfGame:
13336       case MachinePlaysBlack:
13337         if (!WhiteOnMove(forwardMostMove)) {
13338             DisplayError(_("Wait until your turn"), 0);
13339             return;
13340         }
13341         break;
13342       default:
13343         DisplayError(_("No hint available"), 0);
13344         return;
13345     }
13346     SendToProgram("hint\n", &first);
13347     hintRequested = TRUE;
13348 }
13349
13350 void
13351 BookEvent()
13352 {
13353     if (appData.noChessProgram) return;
13354     switch (gameMode) {
13355       case MachinePlaysWhite:
13356         if (WhiteOnMove(forwardMostMove)) {
13357             DisplayError(_("Wait until your turn"), 0);
13358             return;
13359         }
13360         break;
13361       case BeginningOfGame:
13362       case MachinePlaysBlack:
13363         if (!WhiteOnMove(forwardMostMove)) {
13364             DisplayError(_("Wait until your turn"), 0);
13365             return;
13366         }
13367         break;
13368       case EditPosition:
13369         EditPositionDone(TRUE);
13370         break;
13371       case TwoMachinesPlay:
13372         return;
13373       default:
13374         break;
13375     }
13376     SendToProgram("bk\n", &first);
13377     bookOutput[0] = NULLCHAR;
13378     bookRequested = TRUE;
13379 }
13380
13381 void
13382 AboutGameEvent()
13383 {
13384     char *tags = PGNTags(&gameInfo);
13385     TagsPopUp(tags, CmailMsg());
13386     free(tags);
13387 }
13388
13389 /* end button procedures */
13390
13391 void
13392 PrintPosition(fp, move)
13393      FILE *fp;
13394      int move;
13395 {
13396     int i, j;
13397
13398     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13399         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13400             char c = PieceToChar(boards[move][i][j]);
13401             fputc(c == 'x' ? '.' : c, fp);
13402             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13403         }
13404     }
13405     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13406       fprintf(fp, "white to play\n");
13407     else
13408       fprintf(fp, "black to play\n");
13409 }
13410
13411 void
13412 PrintOpponents(fp)
13413      FILE *fp;
13414 {
13415     if (gameInfo.white != NULL) {
13416         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13417     } else {
13418         fprintf(fp, "\n");
13419     }
13420 }
13421
13422 /* Find last component of program's own name, using some heuristics */
13423 void
13424 TidyProgramName(prog, host, buf)
13425      char *prog, *host, buf[MSG_SIZ];
13426 {
13427     char *p, *q;
13428     int local = (strcmp(host, "localhost") == 0);
13429     while (!local && (p = strchr(prog, ';')) != NULL) {
13430         p++;
13431         while (*p == ' ') p++;
13432         prog = p;
13433     }
13434     if (*prog == '"' || *prog == '\'') {
13435         q = strchr(prog + 1, *prog);
13436     } else {
13437         q = strchr(prog, ' ');
13438     }
13439     if (q == NULL) q = prog + strlen(prog);
13440     p = q;
13441     while (p >= prog && *p != '/' && *p != '\\') p--;
13442     p++;
13443     if(p == prog && *p == '"') p++;
13444     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13445     memcpy(buf, p, q - p);
13446     buf[q - p] = NULLCHAR;
13447     if (!local) {
13448         strcat(buf, "@");
13449         strcat(buf, host);
13450     }
13451 }
13452
13453 char *
13454 TimeControlTagValue()
13455 {
13456     char buf[MSG_SIZ];
13457     if (!appData.clockMode) {
13458       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13459     } else if (movesPerSession > 0) {
13460       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13461     } else if (timeIncrement == 0) {
13462       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13463     } else {
13464       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13465     }
13466     return StrSave(buf);
13467 }
13468
13469 void
13470 SetGameInfo()
13471 {
13472     /* This routine is used only for certain modes */
13473     VariantClass v = gameInfo.variant;
13474     ChessMove r = GameUnfinished;
13475     char *p = NULL;
13476
13477     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13478         r = gameInfo.result;
13479         p = gameInfo.resultDetails;
13480         gameInfo.resultDetails = NULL;
13481     }
13482     ClearGameInfo(&gameInfo);
13483     gameInfo.variant = v;
13484
13485     switch (gameMode) {
13486       case MachinePlaysWhite:
13487         gameInfo.event = StrSave( appData.pgnEventHeader );
13488         gameInfo.site = StrSave(HostName());
13489         gameInfo.date = PGNDate();
13490         gameInfo.round = StrSave("-");
13491         gameInfo.white = StrSave(first.tidy);
13492         gameInfo.black = StrSave(UserName());
13493         gameInfo.timeControl = TimeControlTagValue();
13494         break;
13495
13496       case MachinePlaysBlack:
13497         gameInfo.event = StrSave( appData.pgnEventHeader );
13498         gameInfo.site = StrSave(HostName());
13499         gameInfo.date = PGNDate();
13500         gameInfo.round = StrSave("-");
13501         gameInfo.white = StrSave(UserName());
13502         gameInfo.black = StrSave(first.tidy);
13503         gameInfo.timeControl = TimeControlTagValue();
13504         break;
13505
13506       case TwoMachinesPlay:
13507         gameInfo.event = StrSave( appData.pgnEventHeader );
13508         gameInfo.site = StrSave(HostName());
13509         gameInfo.date = PGNDate();
13510         if (matchGame > 0) {
13511             char buf[MSG_SIZ];
13512             snprintf(buf, MSG_SIZ, "%d", matchGame);
13513             gameInfo.round = StrSave(buf);
13514         } else {
13515             gameInfo.round = StrSave("-");
13516         }
13517         if (first.twoMachinesColor[0] == 'w') {
13518             gameInfo.white = StrSave(first.tidy);
13519             gameInfo.black = StrSave(second.tidy);
13520         } else {
13521             gameInfo.white = StrSave(second.tidy);
13522             gameInfo.black = StrSave(first.tidy);
13523         }
13524         gameInfo.timeControl = TimeControlTagValue();
13525         break;
13526
13527       case EditGame:
13528         gameInfo.event = StrSave("Edited game");
13529         gameInfo.site = StrSave(HostName());
13530         gameInfo.date = PGNDate();
13531         gameInfo.round = StrSave("-");
13532         gameInfo.white = StrSave("-");
13533         gameInfo.black = StrSave("-");
13534         gameInfo.result = r;
13535         gameInfo.resultDetails = p;
13536         break;
13537
13538       case EditPosition:
13539         gameInfo.event = StrSave("Edited position");
13540         gameInfo.site = StrSave(HostName());
13541         gameInfo.date = PGNDate();
13542         gameInfo.round = StrSave("-");
13543         gameInfo.white = StrSave("-");
13544         gameInfo.black = StrSave("-");
13545         break;
13546
13547       case IcsPlayingWhite:
13548       case IcsPlayingBlack:
13549       case IcsObserving:
13550       case IcsExamining:
13551         break;
13552
13553       case PlayFromGameFile:
13554         gameInfo.event = StrSave("Game from non-PGN file");
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       default:
13563         break;
13564     }
13565 }
13566
13567 void
13568 ReplaceComment(index, text)
13569      int index;
13570      char *text;
13571 {
13572     int len;
13573     char *p;
13574     float score;
13575
13576     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13577        pvInfoList[index-1].depth == len &&
13578        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13579        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13580     while (*text == '\n') text++;
13581     len = strlen(text);
13582     while (len > 0 && text[len - 1] == '\n') len--;
13583
13584     if (commentList[index] != NULL)
13585       free(commentList[index]);
13586
13587     if (len == 0) {
13588         commentList[index] = NULL;
13589         return;
13590     }
13591   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13592       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13593       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13594     commentList[index] = (char *) malloc(len + 2);
13595     strncpy(commentList[index], text, len);
13596     commentList[index][len] = '\n';
13597     commentList[index][len + 1] = NULLCHAR;
13598   } else {
13599     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13600     char *p;
13601     commentList[index] = (char *) malloc(len + 7);
13602     safeStrCpy(commentList[index], "{\n", 3);
13603     safeStrCpy(commentList[index]+2, text, len+1);
13604     commentList[index][len+2] = NULLCHAR;
13605     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13606     strcat(commentList[index], "\n}\n");
13607   }
13608 }
13609
13610 void
13611 CrushCRs(text)
13612      char *text;
13613 {
13614   char *p = text;
13615   char *q = text;
13616   char ch;
13617
13618   do {
13619     ch = *p++;
13620     if (ch == '\r') continue;
13621     *q++ = ch;
13622   } while (ch != '\0');
13623 }
13624
13625 void
13626 AppendComment(index, text, addBraces)
13627      int index;
13628      char *text;
13629      Boolean addBraces; // [HGM] braces: tells if we should add {}
13630 {
13631     int oldlen, len;
13632     char *old;
13633
13634 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13635     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13636
13637     CrushCRs(text);
13638     while (*text == '\n') text++;
13639     len = strlen(text);
13640     while (len > 0 && text[len - 1] == '\n') len--;
13641
13642     if (len == 0) return;
13643
13644     if (commentList[index] != NULL) {
13645         old = commentList[index];
13646         oldlen = strlen(old);
13647         while(commentList[index][oldlen-1] ==  '\n')
13648           commentList[index][--oldlen] = NULLCHAR;
13649         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13650         safeStrCpy(commentList[index], old, oldlen + len + 6);
13651         free(old);
13652         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13653         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13654           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13655           while (*text == '\n') { text++; len--; }
13656           commentList[index][--oldlen] = NULLCHAR;
13657       }
13658         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13659         else          strcat(commentList[index], "\n");
13660         strcat(commentList[index], text);
13661         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13662         else          strcat(commentList[index], "\n");
13663     } else {
13664         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13665         if(addBraces)
13666           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13667         else commentList[index][0] = NULLCHAR;
13668         strcat(commentList[index], text);
13669         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13670         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13671     }
13672 }
13673
13674 static char * FindStr( char * text, char * sub_text )
13675 {
13676     char * result = strstr( text, sub_text );
13677
13678     if( result != NULL ) {
13679         result += strlen( sub_text );
13680     }
13681
13682     return result;
13683 }
13684
13685 /* [AS] Try to extract PV info from PGN comment */
13686 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13687 char *GetInfoFromComment( int index, char * text )
13688 {
13689     char * sep = text, *p;
13690
13691     if( text != NULL && index > 0 ) {
13692         int score = 0;
13693         int depth = 0;
13694         int time = -1, sec = 0, deci;
13695         char * s_eval = FindStr( text, "[%eval " );
13696         char * s_emt = FindStr( text, "[%emt " );
13697
13698         if( s_eval != NULL || s_emt != NULL ) {
13699             /* New style */
13700             char delim;
13701
13702             if( s_eval != NULL ) {
13703                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13704                     return text;
13705                 }
13706
13707                 if( delim != ']' ) {
13708                     return text;
13709                 }
13710             }
13711
13712             if( s_emt != NULL ) {
13713             }
13714                 return text;
13715         }
13716         else {
13717             /* We expect something like: [+|-]nnn.nn/dd */
13718             int score_lo = 0;
13719
13720             if(*text != '{') return text; // [HGM] braces: must be normal comment
13721
13722             sep = strchr( text, '/' );
13723             if( sep == NULL || sep < (text+4) ) {
13724                 return text;
13725             }
13726
13727             p = text;
13728             if(p[1] == '(') { // comment starts with PV
13729                p = strchr(p, ')'); // locate end of PV
13730                if(p == NULL || sep < p+5) return text;
13731                // at this point we have something like "{(.*) +0.23/6 ..."
13732                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13733                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13734                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13735             }
13736             time = -1; sec = -1; deci = -1;
13737             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13738                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13739                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13740                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13741                 return text;
13742             }
13743
13744             if( score_lo < 0 || score_lo >= 100 ) {
13745                 return text;
13746             }
13747
13748             if(sec >= 0) time = 600*time + 10*sec; else
13749             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13750
13751             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13752
13753             /* [HGM] PV time: now locate end of PV info */
13754             while( *++sep >= '0' && *sep <= '9'); // strip depth
13755             if(time >= 0)
13756             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13757             if(sec >= 0)
13758             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13759             if(deci >= 0)
13760             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13761             while(*sep == ' ') sep++;
13762         }
13763
13764         if( depth <= 0 ) {
13765             return text;
13766         }
13767
13768         if( time < 0 ) {
13769             time = -1;
13770         }
13771
13772         pvInfoList[index-1].depth = depth;
13773         pvInfoList[index-1].score = score;
13774         pvInfoList[index-1].time  = 10*time; // centi-sec
13775         if(*sep == '}') *sep = 0; else *--sep = '{';
13776         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13777     }
13778     return sep;
13779 }
13780
13781 void
13782 SendToProgram(message, cps)
13783      char *message;
13784      ChessProgramState *cps;
13785 {
13786     int count, outCount, error;
13787     char buf[MSG_SIZ];
13788
13789     if (cps->pr == NULL) return;
13790     Attention(cps);
13791
13792     if (appData.debugMode) {
13793         TimeMark now;
13794         GetTimeMark(&now);
13795         fprintf(debugFP, "%ld >%-6s: %s",
13796                 SubtractTimeMarks(&now, &programStartTime),
13797                 cps->which, message);
13798     }
13799
13800     count = strlen(message);
13801     outCount = OutputToProcess(cps->pr, message, count, &error);
13802     if (outCount < count && !exiting
13803                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13804       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
13805       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13806         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13807             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13808                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13809                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13810             } else {
13811                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13812             }
13813             gameInfo.resultDetails = StrSave(buf);
13814         }
13815         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13816     }
13817 }
13818
13819 void
13820 ReceiveFromProgram(isr, closure, message, count, error)
13821      InputSourceRef isr;
13822      VOIDSTAR closure;
13823      char *message;
13824      int count;
13825      int error;
13826 {
13827     char *end_str;
13828     char buf[MSG_SIZ];
13829     ChessProgramState *cps = (ChessProgramState *)closure;
13830
13831     if (isr != cps->isr) return; /* Killed intentionally */
13832     if (count <= 0) {
13833         if (count == 0) {
13834             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13835                     _(cps->which), cps->program);
13836         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13837                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13838                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13839                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13840                 } else {
13841                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13842                 }
13843                 gameInfo.resultDetails = StrSave(buf);
13844             }
13845             RemoveInputSource(cps->isr);
13846             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13847         } else {
13848             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13849                     _(cps->which), cps->program);
13850             RemoveInputSource(cps->isr);
13851
13852             /* [AS] Program is misbehaving badly... kill it */
13853             if( count == -2 ) {
13854                 DestroyChildProcess( cps->pr, 9 );
13855                 cps->pr = NoProc;
13856             }
13857
13858             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13859         }
13860         return;
13861     }
13862
13863     if ((end_str = strchr(message, '\r')) != NULL)
13864       *end_str = NULLCHAR;
13865     if ((end_str = strchr(message, '\n')) != NULL)
13866       *end_str = NULLCHAR;
13867
13868     if (appData.debugMode) {
13869         TimeMark now; int print = 1;
13870         char *quote = ""; char c; int i;
13871
13872         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13873                 char start = message[0];
13874                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13875                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13876                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13877                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13878                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13879                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13880                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13881                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13882                    sscanf(message, "hint: %c", &c)!=1 && 
13883                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13884                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13885                     print = (appData.engineComments >= 2);
13886                 }
13887                 message[0] = start; // restore original message
13888         }
13889         if(print) {
13890                 GetTimeMark(&now);
13891                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13892                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13893                         quote,
13894                         message);
13895         }
13896     }
13897
13898     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13899     if (appData.icsEngineAnalyze) {
13900         if (strstr(message, "whisper") != NULL ||
13901              strstr(message, "kibitz") != NULL ||
13902             strstr(message, "tellics") != NULL) return;
13903     }
13904
13905     HandleMachineMove(message, cps);
13906 }
13907
13908
13909 void
13910 SendTimeControl(cps, mps, tc, inc, sd, st)
13911      ChessProgramState *cps;
13912      int mps, inc, sd, st;
13913      long tc;
13914 {
13915     char buf[MSG_SIZ];
13916     int seconds;
13917
13918     if( timeControl_2 > 0 ) {
13919         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13920             tc = timeControl_2;
13921         }
13922     }
13923     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13924     inc /= cps->timeOdds;
13925     st  /= cps->timeOdds;
13926
13927     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13928
13929     if (st > 0) {
13930       /* Set exact time per move, normally using st command */
13931       if (cps->stKludge) {
13932         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13933         seconds = st % 60;
13934         if (seconds == 0) {
13935           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13936         } else {
13937           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13938         }
13939       } else {
13940         snprintf(buf, MSG_SIZ, "st %d\n", st);
13941       }
13942     } else {
13943       /* Set conventional or incremental time control, using level command */
13944       if (seconds == 0) {
13945         /* Note old gnuchess bug -- minutes:seconds used to not work.
13946            Fixed in later versions, but still avoid :seconds
13947            when seconds is 0. */
13948         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13949       } else {
13950         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13951                  seconds, inc/1000.);
13952       }
13953     }
13954     SendToProgram(buf, cps);
13955
13956     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13957     /* Orthogonally, limit search to given depth */
13958     if (sd > 0) {
13959       if (cps->sdKludge) {
13960         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13961       } else {
13962         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13963       }
13964       SendToProgram(buf, cps);
13965     }
13966
13967     if(cps->nps >= 0) { /* [HGM] nps */
13968         if(cps->supportsNPS == FALSE)
13969           cps->nps = -1; // don't use if engine explicitly says not supported!
13970         else {
13971           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13972           SendToProgram(buf, cps);
13973         }
13974     }
13975 }
13976
13977 ChessProgramState *WhitePlayer()
13978 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13979 {
13980     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13981        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13982         return &second;
13983     return &first;
13984 }
13985
13986 void
13987 SendTimeRemaining(cps, machineWhite)
13988      ChessProgramState *cps;
13989      int /*boolean*/ machineWhite;
13990 {
13991     char message[MSG_SIZ];
13992     long time, otime;
13993
13994     /* Note: this routine must be called when the clocks are stopped
13995        or when they have *just* been set or switched; otherwise
13996        it will be off by the time since the current tick started.
13997     */
13998     if (machineWhite) {
13999         time = whiteTimeRemaining / 10;
14000         otime = blackTimeRemaining / 10;
14001     } else {
14002         time = blackTimeRemaining / 10;
14003         otime = whiteTimeRemaining / 10;
14004     }
14005     /* [HGM] translate opponent's time by time-odds factor */
14006     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14007     if (appData.debugMode) {
14008         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14009     }
14010
14011     if (time <= 0) time = 1;
14012     if (otime <= 0) otime = 1;
14013
14014     snprintf(message, MSG_SIZ, "time %ld\n", time);
14015     SendToProgram(message, cps);
14016
14017     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14018     SendToProgram(message, cps);
14019 }
14020
14021 int
14022 BoolFeature(p, name, loc, cps)
14023      char **p;
14024      char *name;
14025      int *loc;
14026      ChessProgramState *cps;
14027 {
14028   char buf[MSG_SIZ];
14029   int len = strlen(name);
14030   int val;
14031
14032   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14033     (*p) += len + 1;
14034     sscanf(*p, "%d", &val);
14035     *loc = (val != 0);
14036     while (**p && **p != ' ')
14037       (*p)++;
14038     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14039     SendToProgram(buf, cps);
14040     return TRUE;
14041   }
14042   return FALSE;
14043 }
14044
14045 int
14046 IntFeature(p, name, loc, cps)
14047      char **p;
14048      char *name;
14049      int *loc;
14050      ChessProgramState *cps;
14051 {
14052   char buf[MSG_SIZ];
14053   int len = strlen(name);
14054   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14055     (*p) += len + 1;
14056     sscanf(*p, "%d", loc);
14057     while (**p && **p != ' ') (*p)++;
14058     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14059     SendToProgram(buf, cps);
14060     return TRUE;
14061   }
14062   return FALSE;
14063 }
14064
14065 int
14066 StringFeature(p, name, loc, cps)
14067      char **p;
14068      char *name;
14069      char loc[];
14070      ChessProgramState *cps;
14071 {
14072   char buf[MSG_SIZ];
14073   int len = strlen(name);
14074   if (strncmp((*p), name, len) == 0
14075       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14076     (*p) += len + 2;
14077     sscanf(*p, "%[^\"]", loc);
14078     while (**p && **p != '\"') (*p)++;
14079     if (**p == '\"') (*p)++;
14080     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14081     SendToProgram(buf, cps);
14082     return TRUE;
14083   }
14084   return FALSE;
14085 }
14086
14087 int
14088 ParseOption(Option *opt, ChessProgramState *cps)
14089 // [HGM] options: process the string that defines an engine option, and determine
14090 // name, type, default value, and allowed value range
14091 {
14092         char *p, *q, buf[MSG_SIZ];
14093         int n, min = (-1)<<31, max = 1<<31, def;
14094
14095         if(p = strstr(opt->name, " -spin ")) {
14096             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14097             if(max < min) max = min; // enforce consistency
14098             if(def < min) def = min;
14099             if(def > max) def = max;
14100             opt->value = def;
14101             opt->min = min;
14102             opt->max = max;
14103             opt->type = Spin;
14104         } else if((p = strstr(opt->name, " -slider "))) {
14105             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14106             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14107             if(max < min) max = min; // enforce consistency
14108             if(def < min) def = min;
14109             if(def > max) def = max;
14110             opt->value = def;
14111             opt->min = min;
14112             opt->max = max;
14113             opt->type = Spin; // Slider;
14114         } else if((p = strstr(opt->name, " -string "))) {
14115             opt->textValue = p+9;
14116             opt->type = TextBox;
14117         } else if((p = strstr(opt->name, " -file "))) {
14118             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14119             opt->textValue = p+7;
14120             opt->type = FileName; // FileName;
14121         } else if((p = strstr(opt->name, " -path "))) {
14122             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14123             opt->textValue = p+7;
14124             opt->type = PathName; // PathName;
14125         } else if(p = strstr(opt->name, " -check ")) {
14126             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14127             opt->value = (def != 0);
14128             opt->type = CheckBox;
14129         } else if(p = strstr(opt->name, " -combo ")) {
14130             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14131             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14132             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14133             opt->value = n = 0;
14134             while(q = StrStr(q, " /// ")) {
14135                 n++; *q = 0;    // count choices, and null-terminate each of them
14136                 q += 5;
14137                 if(*q == '*') { // remember default, which is marked with * prefix
14138                     q++;
14139                     opt->value = n;
14140                 }
14141                 cps->comboList[cps->comboCnt++] = q;
14142             }
14143             cps->comboList[cps->comboCnt++] = NULL;
14144             opt->max = n + 1;
14145             opt->type = ComboBox;
14146         } else if(p = strstr(opt->name, " -button")) {
14147             opt->type = Button;
14148         } else if(p = strstr(opt->name, " -save")) {
14149             opt->type = SaveButton;
14150         } else return FALSE;
14151         *p = 0; // terminate option name
14152         // now look if the command-line options define a setting for this engine option.
14153         if(cps->optionSettings && cps->optionSettings[0])
14154             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14155         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14156           snprintf(buf, MSG_SIZ, "option %s", p);
14157                 if(p = strstr(buf, ",")) *p = 0;
14158                 if(q = strchr(buf, '=')) switch(opt->type) {
14159                     case ComboBox:
14160                         for(n=0; n<opt->max; n++)
14161                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14162                         break;
14163                     case TextBox:
14164                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14165                         break;
14166                     case Spin:
14167                     case CheckBox:
14168                         opt->value = atoi(q+1);
14169                     default:
14170                         break;
14171                 }
14172                 strcat(buf, "\n");
14173                 SendToProgram(buf, cps);
14174         }
14175         return TRUE;
14176 }
14177
14178 void
14179 FeatureDone(cps, val)
14180      ChessProgramState* cps;
14181      int val;
14182 {
14183   DelayedEventCallback cb = GetDelayedEvent();
14184   if ((cb == InitBackEnd3 && cps == &first) ||
14185       (cb == SettingsMenuIfReady && cps == &second) ||
14186       (cb == LoadEngine) ||
14187       (cb == TwoMachinesEventIfReady && cps == &second)) {
14188     CancelDelayedEvent();
14189     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14190   }
14191   cps->initDone = val;
14192 }
14193
14194 /* Parse feature command from engine */
14195 void
14196 ParseFeatures(args, cps)
14197      char* args;
14198      ChessProgramState *cps;
14199 {
14200   char *p = args;
14201   char *q;
14202   int val;
14203   char buf[MSG_SIZ];
14204
14205   for (;;) {
14206     while (*p == ' ') p++;
14207     if (*p == NULLCHAR) return;
14208
14209     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14210     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14211     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14212     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14213     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14214     if (BoolFeature(&p, "reuse", &val, cps)) {
14215       /* Engine can disable reuse, but can't enable it if user said no */
14216       if (!val) cps->reuse = FALSE;
14217       continue;
14218     }
14219     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14220     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14221       if (gameMode == TwoMachinesPlay) {
14222         DisplayTwoMachinesTitle();
14223       } else {
14224         DisplayTitle("");
14225       }
14226       continue;
14227     }
14228     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14229     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14230     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14231     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14232     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14233     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14234     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14235     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14236     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14237     if (IntFeature(&p, "done", &val, cps)) {
14238       FeatureDone(cps, val);
14239       continue;
14240     }
14241     /* Added by Tord: */
14242     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14243     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14244     /* End of additions by Tord */
14245
14246     /* [HGM] added features: */
14247     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14248     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14249     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14250     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14251     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14252     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14253     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14254         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14255           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14256             SendToProgram(buf, cps);
14257             continue;
14258         }
14259         if(cps->nrOptions >= MAX_OPTIONS) {
14260             cps->nrOptions--;
14261             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14262             DisplayError(buf, 0);
14263         }
14264         continue;
14265     }
14266     /* End of additions by HGM */
14267
14268     /* unknown feature: complain and skip */
14269     q = p;
14270     while (*q && *q != '=') q++;
14271     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14272     SendToProgram(buf, cps);
14273     p = q;
14274     if (*p == '=') {
14275       p++;
14276       if (*p == '\"') {
14277         p++;
14278         while (*p && *p != '\"') p++;
14279         if (*p == '\"') p++;
14280       } else {
14281         while (*p && *p != ' ') p++;
14282       }
14283     }
14284   }
14285
14286 }
14287
14288 void
14289 PeriodicUpdatesEvent(newState)
14290      int newState;
14291 {
14292     if (newState == appData.periodicUpdates)
14293       return;
14294
14295     appData.periodicUpdates=newState;
14296
14297     /* Display type changes, so update it now */
14298 //    DisplayAnalysis();
14299
14300     /* Get the ball rolling again... */
14301     if (newState) {
14302         AnalysisPeriodicEvent(1);
14303         StartAnalysisClock();
14304     }
14305 }
14306
14307 void
14308 PonderNextMoveEvent(newState)
14309      int newState;
14310 {
14311     if (newState == appData.ponderNextMove) return;
14312     if (gameMode == EditPosition) EditPositionDone(TRUE);
14313     if (newState) {
14314         SendToProgram("hard\n", &first);
14315         if (gameMode == TwoMachinesPlay) {
14316             SendToProgram("hard\n", &second);
14317         }
14318     } else {
14319         SendToProgram("easy\n", &first);
14320         thinkOutput[0] = NULLCHAR;
14321         if (gameMode == TwoMachinesPlay) {
14322             SendToProgram("easy\n", &second);
14323         }
14324     }
14325     appData.ponderNextMove = newState;
14326 }
14327
14328 void
14329 NewSettingEvent(option, feature, command, value)
14330      char *command;
14331      int option, value, *feature;
14332 {
14333     char buf[MSG_SIZ];
14334
14335     if (gameMode == EditPosition) EditPositionDone(TRUE);
14336     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14337     if(feature == NULL || *feature) SendToProgram(buf, &first);
14338     if (gameMode == TwoMachinesPlay) {
14339         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14340     }
14341 }
14342
14343 void
14344 ShowThinkingEvent()
14345 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14346 {
14347     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14348     int newState = appData.showThinking
14349         // [HGM] thinking: other features now need thinking output as well
14350         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14351
14352     if (oldState == newState) return;
14353     oldState = newState;
14354     if (gameMode == EditPosition) EditPositionDone(TRUE);
14355     if (oldState) {
14356         SendToProgram("post\n", &first);
14357         if (gameMode == TwoMachinesPlay) {
14358             SendToProgram("post\n", &second);
14359         }
14360     } else {
14361         SendToProgram("nopost\n", &first);
14362         thinkOutput[0] = NULLCHAR;
14363         if (gameMode == TwoMachinesPlay) {
14364             SendToProgram("nopost\n", &second);
14365         }
14366     }
14367 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14368 }
14369
14370 void
14371 AskQuestionEvent(title, question, replyPrefix, which)
14372      char *title; char *question; char *replyPrefix; char *which;
14373 {
14374   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14375   if (pr == NoProc) return;
14376   AskQuestion(title, question, replyPrefix, pr);
14377 }
14378
14379 void
14380 TypeInEvent(char firstChar)
14381 {
14382     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14383         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14384         gameMode == AnalyzeMode || gameMode == EditGame || \r
14385         gameMode == EditPosition || gameMode == IcsExamining ||\r
14386         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14387         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14388                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14389                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14390         gameMode == Training) PopUpMoveDialog(firstChar);
14391 }
14392
14393 void
14394 TypeInDoneEvent(char *move)
14395 {
14396         Board board;
14397         int n, fromX, fromY, toX, toY;
14398         char promoChar;
14399         ChessMove moveType;\r
14400
14401         // [HGM] FENedit\r
14402         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14403                 EditPositionPasteFEN(move);\r
14404                 return;\r
14405         }\r
14406         // [HGM] movenum: allow move number to be typed in any mode\r
14407         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14408           ToNrEvent(2*n-1);\r
14409           return;\r
14410         }\r
14411
14412       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14413         gameMode != Training) {\r
14414         DisplayMoveError(_("Displayed move is not current"));\r
14415       } else {\r
14416         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14417           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14418         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14419         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14420           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14421           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14422         } else {\r
14423           DisplayMoveError(_("Could not parse move"));\r
14424         }
14425       }\r
14426 }\r
14427
14428 void
14429 DisplayMove(moveNumber)
14430      int moveNumber;
14431 {
14432     char message[MSG_SIZ];
14433     char res[MSG_SIZ];
14434     char cpThinkOutput[MSG_SIZ];
14435
14436     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14437
14438     if (moveNumber == forwardMostMove - 1 ||
14439         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14440
14441         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14442
14443         if (strchr(cpThinkOutput, '\n')) {
14444             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14445         }
14446     } else {
14447         *cpThinkOutput = NULLCHAR;
14448     }
14449
14450     /* [AS] Hide thinking from human user */
14451     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14452         *cpThinkOutput = NULLCHAR;
14453         if( thinkOutput[0] != NULLCHAR ) {
14454             int i;
14455
14456             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14457                 cpThinkOutput[i] = '.';
14458             }
14459             cpThinkOutput[i] = NULLCHAR;
14460             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14461         }
14462     }
14463
14464     if (moveNumber == forwardMostMove - 1 &&
14465         gameInfo.resultDetails != NULL) {
14466         if (gameInfo.resultDetails[0] == NULLCHAR) {
14467           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14468         } else {
14469           snprintf(res, MSG_SIZ, " {%s} %s",
14470                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14471         }
14472     } else {
14473         res[0] = NULLCHAR;
14474     }
14475
14476     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14477         DisplayMessage(res, cpThinkOutput);
14478     } else {
14479       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14480                 WhiteOnMove(moveNumber) ? " " : ".. ",
14481                 parseList[moveNumber], res);
14482         DisplayMessage(message, cpThinkOutput);
14483     }
14484 }
14485
14486 void
14487 DisplayComment(moveNumber, text)
14488      int moveNumber;
14489      char *text;
14490 {
14491     char title[MSG_SIZ];
14492     char buf[8000]; // comment can be long!
14493     int score, depth;
14494
14495     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14496       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14497     } else {
14498       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14499               WhiteOnMove(moveNumber) ? " " : ".. ",
14500               parseList[moveNumber]);
14501     }
14502     // [HGM] PV info: display PV info together with (or as) comment
14503     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14504       if(text == NULL) text = "";
14505       score = pvInfoList[moveNumber].score;
14506       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14507               depth, (pvInfoList[moveNumber].time+50)/100, text);
14508       text = buf;
14509     }
14510     if (text != NULL && (appData.autoDisplayComment || commentUp))
14511         CommentPopUp(title, text);
14512 }
14513
14514 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14515  * might be busy thinking or pondering.  It can be omitted if your
14516  * gnuchess is configured to stop thinking immediately on any user
14517  * input.  However, that gnuchess feature depends on the FIONREAD
14518  * ioctl, which does not work properly on some flavors of Unix.
14519  */
14520 void
14521 Attention(cps)
14522      ChessProgramState *cps;
14523 {
14524 #if ATTENTION
14525     if (!cps->useSigint) return;
14526     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14527     switch (gameMode) {
14528       case MachinePlaysWhite:
14529       case MachinePlaysBlack:
14530       case TwoMachinesPlay:
14531       case IcsPlayingWhite:
14532       case IcsPlayingBlack:
14533       case AnalyzeMode:
14534       case AnalyzeFile:
14535         /* Skip if we know it isn't thinking */
14536         if (!cps->maybeThinking) return;
14537         if (appData.debugMode)
14538           fprintf(debugFP, "Interrupting %s\n", cps->which);
14539         InterruptChildProcess(cps->pr);
14540         cps->maybeThinking = FALSE;
14541         break;
14542       default:
14543         break;
14544     }
14545 #endif /*ATTENTION*/
14546 }
14547
14548 int
14549 CheckFlags()
14550 {
14551     if (whiteTimeRemaining <= 0) {
14552         if (!whiteFlag) {
14553             whiteFlag = TRUE;
14554             if (appData.icsActive) {
14555                 if (appData.autoCallFlag &&
14556                     gameMode == IcsPlayingBlack && !blackFlag) {
14557                   SendToICS(ics_prefix);
14558                   SendToICS("flag\n");
14559                 }
14560             } else {
14561                 if (blackFlag) {
14562                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14563                 } else {
14564                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14565                     if (appData.autoCallFlag) {
14566                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14567                         return TRUE;
14568                     }
14569                 }
14570             }
14571         }
14572     }
14573     if (blackTimeRemaining <= 0) {
14574         if (!blackFlag) {
14575             blackFlag = TRUE;
14576             if (appData.icsActive) {
14577                 if (appData.autoCallFlag &&
14578                     gameMode == IcsPlayingWhite && !whiteFlag) {
14579                   SendToICS(ics_prefix);
14580                   SendToICS("flag\n");
14581                 }
14582             } else {
14583                 if (whiteFlag) {
14584                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14585                 } else {
14586                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14587                     if (appData.autoCallFlag) {
14588                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14589                         return TRUE;
14590                     }
14591                 }
14592             }
14593         }
14594     }
14595     return FALSE;
14596 }
14597
14598 void
14599 CheckTimeControl()
14600 {
14601     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14602         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14603
14604     /*
14605      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14606      */
14607     if ( !WhiteOnMove(forwardMostMove) ) {
14608         /* White made time control */
14609         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14610         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14611         /* [HGM] time odds: correct new time quota for time odds! */
14612                                             / WhitePlayer()->timeOdds;
14613         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14614     } else {
14615         lastBlack -= blackTimeRemaining;
14616         /* Black made time control */
14617         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14618                                             / WhitePlayer()->other->timeOdds;
14619         lastWhite = whiteTimeRemaining;
14620     }
14621 }
14622
14623 void
14624 DisplayBothClocks()
14625 {
14626     int wom = gameMode == EditPosition ?
14627       !blackPlaysFirst : WhiteOnMove(currentMove);
14628     DisplayWhiteClock(whiteTimeRemaining, wom);
14629     DisplayBlackClock(blackTimeRemaining, !wom);
14630 }
14631
14632
14633 /* Timekeeping seems to be a portability nightmare.  I think everyone
14634    has ftime(), but I'm really not sure, so I'm including some ifdefs
14635    to use other calls if you don't.  Clocks will be less accurate if
14636    you have neither ftime nor gettimeofday.
14637 */
14638
14639 /* VS 2008 requires the #include outside of the function */
14640 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14641 #include <sys/timeb.h>
14642 #endif
14643
14644 /* Get the current time as a TimeMark */
14645 void
14646 GetTimeMark(tm)
14647      TimeMark *tm;
14648 {
14649 #if HAVE_GETTIMEOFDAY
14650
14651     struct timeval timeVal;
14652     struct timezone timeZone;
14653
14654     gettimeofday(&timeVal, &timeZone);
14655     tm->sec = (long) timeVal.tv_sec;
14656     tm->ms = (int) (timeVal.tv_usec / 1000L);
14657
14658 #else /*!HAVE_GETTIMEOFDAY*/
14659 #if HAVE_FTIME
14660
14661 // include <sys/timeb.h> / moved to just above start of function
14662     struct timeb timeB;
14663
14664     ftime(&timeB);
14665     tm->sec = (long) timeB.time;
14666     tm->ms = (int) timeB.millitm;
14667
14668 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14669     tm->sec = (long) time(NULL);
14670     tm->ms = 0;
14671 #endif
14672 #endif
14673 }
14674
14675 /* Return the difference in milliseconds between two
14676    time marks.  We assume the difference will fit in a long!
14677 */
14678 long
14679 SubtractTimeMarks(tm2, tm1)
14680      TimeMark *tm2, *tm1;
14681 {
14682     return 1000L*(tm2->sec - tm1->sec) +
14683            (long) (tm2->ms - tm1->ms);
14684 }
14685
14686
14687 /*
14688  * Code to manage the game clocks.
14689  *
14690  * In tournament play, black starts the clock and then white makes a move.
14691  * We give the human user a slight advantage if he is playing white---the
14692  * clocks don't run until he makes his first move, so it takes zero time.
14693  * Also, we don't account for network lag, so we could get out of sync
14694  * with GNU Chess's clock -- but then, referees are always right.
14695  */
14696
14697 static TimeMark tickStartTM;
14698 static long intendedTickLength;
14699
14700 long
14701 NextTickLength(timeRemaining)
14702      long timeRemaining;
14703 {
14704     long nominalTickLength, nextTickLength;
14705
14706     if (timeRemaining > 0L && timeRemaining <= 10000L)
14707       nominalTickLength = 100L;
14708     else
14709       nominalTickLength = 1000L;
14710     nextTickLength = timeRemaining % nominalTickLength;
14711     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14712
14713     return nextTickLength;
14714 }
14715
14716 /* Adjust clock one minute up or down */
14717 void
14718 AdjustClock(Boolean which, int dir)
14719 {
14720     if(which) blackTimeRemaining += 60000*dir;
14721     else      whiteTimeRemaining += 60000*dir;
14722     DisplayBothClocks();
14723 }
14724
14725 /* Stop clocks and reset to a fresh time control */
14726 void
14727 ResetClocks()
14728 {
14729     (void) StopClockTimer();
14730     if (appData.icsActive) {
14731         whiteTimeRemaining = blackTimeRemaining = 0;
14732     } else if (searchTime) {
14733         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14734         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14735     } else { /* [HGM] correct new time quote for time odds */
14736         whiteTC = blackTC = fullTimeControlString;
14737         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14738         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14739     }
14740     if (whiteFlag || blackFlag) {
14741         DisplayTitle("");
14742         whiteFlag = blackFlag = FALSE;
14743     }
14744     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14745     DisplayBothClocks();
14746 }
14747
14748 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14749
14750 /* Decrement running clock by amount of time that has passed */
14751 void
14752 DecrementClocks()
14753 {
14754     long timeRemaining;
14755     long lastTickLength, fudge;
14756     TimeMark now;
14757
14758     if (!appData.clockMode) return;
14759     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14760
14761     GetTimeMark(&now);
14762
14763     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14764
14765     /* Fudge if we woke up a little too soon */
14766     fudge = intendedTickLength - lastTickLength;
14767     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14768
14769     if (WhiteOnMove(forwardMostMove)) {
14770         if(whiteNPS >= 0) lastTickLength = 0;
14771         timeRemaining = whiteTimeRemaining -= lastTickLength;
14772         if(timeRemaining < 0 && !appData.icsActive) {
14773             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14774             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14775                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14776                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14777             }
14778         }
14779         DisplayWhiteClock(whiteTimeRemaining - fudge,
14780                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14781     } else {
14782         if(blackNPS >= 0) lastTickLength = 0;
14783         timeRemaining = blackTimeRemaining -= lastTickLength;
14784         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14785             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14786             if(suddenDeath) {
14787                 blackStartMove = forwardMostMove;
14788                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14789             }
14790         }
14791         DisplayBlackClock(blackTimeRemaining - fudge,
14792                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14793     }
14794     if (CheckFlags()) return;
14795
14796     tickStartTM = now;
14797     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14798     StartClockTimer(intendedTickLength);
14799
14800     /* if the time remaining has fallen below the alarm threshold, sound the
14801      * alarm. if the alarm has sounded and (due to a takeback or time control
14802      * with increment) the time remaining has increased to a level above the
14803      * threshold, reset the alarm so it can sound again.
14804      */
14805
14806     if (appData.icsActive && appData.icsAlarm) {
14807
14808         /* make sure we are dealing with the user's clock */
14809         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14810                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14811            )) return;
14812
14813         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14814             alarmSounded = FALSE;
14815         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14816             PlayAlarmSound();
14817             alarmSounded = TRUE;
14818         }
14819     }
14820 }
14821
14822
14823 /* A player has just moved, so stop the previously running
14824    clock and (if in clock mode) start the other one.
14825    We redisplay both clocks in case we're in ICS mode, because
14826    ICS gives us an update to both clocks after every move.
14827    Note that this routine is called *after* forwardMostMove
14828    is updated, so the last fractional tick must be subtracted
14829    from the color that is *not* on move now.
14830 */
14831 void
14832 SwitchClocks(int newMoveNr)
14833 {
14834     long lastTickLength;
14835     TimeMark now;
14836     int flagged = FALSE;
14837
14838     GetTimeMark(&now);
14839
14840     if (StopClockTimer() && appData.clockMode) {
14841         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14842         if (!WhiteOnMove(forwardMostMove)) {
14843             if(blackNPS >= 0) lastTickLength = 0;
14844             blackTimeRemaining -= lastTickLength;
14845            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14846 //         if(pvInfoList[forwardMostMove].time == -1)
14847                  pvInfoList[forwardMostMove].time =               // use GUI time
14848                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14849         } else {
14850            if(whiteNPS >= 0) lastTickLength = 0;
14851            whiteTimeRemaining -= lastTickLength;
14852            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14853 //         if(pvInfoList[forwardMostMove].time == -1)
14854                  pvInfoList[forwardMostMove].time =
14855                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14856         }
14857         flagged = CheckFlags();
14858     }
14859     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14860     CheckTimeControl();
14861
14862     if (flagged || !appData.clockMode) return;
14863
14864     switch (gameMode) {
14865       case MachinePlaysBlack:
14866       case MachinePlaysWhite:
14867       case BeginningOfGame:
14868         if (pausing) return;
14869         break;
14870
14871       case EditGame:
14872       case PlayFromGameFile:
14873       case IcsExamining:
14874         return;
14875
14876       default:
14877         break;
14878     }
14879
14880     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14881         if(WhiteOnMove(forwardMostMove))
14882              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14883         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14884     }
14885
14886     tickStartTM = now;
14887     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14888       whiteTimeRemaining : blackTimeRemaining);
14889     StartClockTimer(intendedTickLength);
14890 }
14891
14892
14893 /* Stop both clocks */
14894 void
14895 StopClocks()
14896 {
14897     long lastTickLength;
14898     TimeMark now;
14899
14900     if (!StopClockTimer()) return;
14901     if (!appData.clockMode) return;
14902
14903     GetTimeMark(&now);
14904
14905     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14906     if (WhiteOnMove(forwardMostMove)) {
14907         if(whiteNPS >= 0) lastTickLength = 0;
14908         whiteTimeRemaining -= lastTickLength;
14909         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14910     } else {
14911         if(blackNPS >= 0) lastTickLength = 0;
14912         blackTimeRemaining -= lastTickLength;
14913         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14914     }
14915     CheckFlags();
14916 }
14917
14918 /* Start clock of player on move.  Time may have been reset, so
14919    if clock is already running, stop and restart it. */
14920 void
14921 StartClocks()
14922 {
14923     (void) StopClockTimer(); /* in case it was running already */
14924     DisplayBothClocks();
14925     if (CheckFlags()) return;
14926
14927     if (!appData.clockMode) return;
14928     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14929
14930     GetTimeMark(&tickStartTM);
14931     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14932       whiteTimeRemaining : blackTimeRemaining);
14933
14934    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14935     whiteNPS = blackNPS = -1;
14936     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14937        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14938         whiteNPS = first.nps;
14939     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14940        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14941         blackNPS = first.nps;
14942     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14943         whiteNPS = second.nps;
14944     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14945         blackNPS = second.nps;
14946     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14947
14948     StartClockTimer(intendedTickLength);
14949 }
14950
14951 char *
14952 TimeString(ms)
14953      long ms;
14954 {
14955     long second, minute, hour, day;
14956     char *sign = "";
14957     static char buf[32];
14958
14959     if (ms > 0 && ms <= 9900) {
14960       /* convert milliseconds to tenths, rounding up */
14961       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14962
14963       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14964       return buf;
14965     }
14966
14967     /* convert milliseconds to seconds, rounding up */
14968     /* use floating point to avoid strangeness of integer division
14969        with negative dividends on many machines */
14970     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14971
14972     if (second < 0) {
14973         sign = "-";
14974         second = -second;
14975     }
14976
14977     day = second / (60 * 60 * 24);
14978     second = second % (60 * 60 * 24);
14979     hour = second / (60 * 60);
14980     second = second % (60 * 60);
14981     minute = second / 60;
14982     second = second % 60;
14983
14984     if (day > 0)
14985       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14986               sign, day, hour, minute, second);
14987     else if (hour > 0)
14988       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14989     else
14990       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14991
14992     return buf;
14993 }
14994
14995
14996 /*
14997  * This is necessary because some C libraries aren't ANSI C compliant yet.
14998  */
14999 char *
15000 StrStr(string, match)
15001      char *string, *match;
15002 {
15003     int i, length;
15004
15005     length = strlen(match);
15006
15007     for (i = strlen(string) - length; i >= 0; i--, string++)
15008       if (!strncmp(match, string, length))
15009         return string;
15010
15011     return NULL;
15012 }
15013
15014 char *
15015 StrCaseStr(string, match)
15016      char *string, *match;
15017 {
15018     int i, j, length;
15019
15020     length = strlen(match);
15021
15022     for (i = strlen(string) - length; i >= 0; i--, string++) {
15023         for (j = 0; j < length; j++) {
15024             if (ToLower(match[j]) != ToLower(string[j]))
15025               break;
15026         }
15027         if (j == length) return string;
15028     }
15029
15030     return NULL;
15031 }
15032
15033 #ifndef _amigados
15034 int
15035 StrCaseCmp(s1, s2)
15036      char *s1, *s2;
15037 {
15038     char c1, c2;
15039
15040     for (;;) {
15041         c1 = ToLower(*s1++);
15042         c2 = ToLower(*s2++);
15043         if (c1 > c2) return 1;
15044         if (c1 < c2) return -1;
15045         if (c1 == NULLCHAR) return 0;
15046     }
15047 }
15048
15049
15050 int
15051 ToLower(c)
15052      int c;
15053 {
15054     return isupper(c) ? tolower(c) : c;
15055 }
15056
15057
15058 int
15059 ToUpper(c)
15060      int c;
15061 {
15062     return islower(c) ? toupper(c) : c;
15063 }
15064 #endif /* !_amigados    */
15065
15066 char *
15067 StrSave(s)
15068      char *s;
15069 {
15070   char *ret;
15071
15072   if ((ret = (char *) malloc(strlen(s) + 1)))
15073     {
15074       safeStrCpy(ret, s, strlen(s)+1);
15075     }
15076   return ret;
15077 }
15078
15079 char *
15080 StrSavePtr(s, savePtr)
15081      char *s, **savePtr;
15082 {
15083     if (*savePtr) {
15084         free(*savePtr);
15085     }
15086     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15087       safeStrCpy(*savePtr, s, strlen(s)+1);
15088     }
15089     return(*savePtr);
15090 }
15091
15092 char *
15093 PGNDate()
15094 {
15095     time_t clock;
15096     struct tm *tm;
15097     char buf[MSG_SIZ];
15098
15099     clock = time((time_t *)NULL);
15100     tm = localtime(&clock);
15101     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15102             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15103     return StrSave(buf);
15104 }
15105
15106
15107 char *
15108 PositionToFEN(move, overrideCastling)
15109      int move;
15110      char *overrideCastling;
15111 {
15112     int i, j, fromX, fromY, toX, toY;
15113     int whiteToPlay;
15114     char buf[128];
15115     char *p, *q;
15116     int emptycount;
15117     ChessSquare piece;
15118
15119     whiteToPlay = (gameMode == EditPosition) ?
15120       !blackPlaysFirst : (move % 2 == 0);
15121     p = buf;
15122
15123     /* Piece placement data */
15124     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15125         emptycount = 0;
15126         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15127             if (boards[move][i][j] == EmptySquare) {
15128                 emptycount++;
15129             } else { ChessSquare piece = boards[move][i][j];
15130                 if (emptycount > 0) {
15131                     if(emptycount<10) /* [HGM] can be >= 10 */
15132                         *p++ = '0' + emptycount;
15133                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15134                     emptycount = 0;
15135                 }
15136                 if(PieceToChar(piece) == '+') {
15137                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15138                     *p++ = '+';
15139                     piece = (ChessSquare)(DEMOTED piece);
15140                 }
15141                 *p++ = PieceToChar(piece);
15142                 if(p[-1] == '~') {
15143                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15144                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15145                     *p++ = '~';
15146                 }
15147             }
15148         }
15149         if (emptycount > 0) {
15150             if(emptycount<10) /* [HGM] can be >= 10 */
15151                 *p++ = '0' + emptycount;
15152             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15153             emptycount = 0;
15154         }
15155         *p++ = '/';
15156     }
15157     *(p - 1) = ' ';
15158
15159     /* [HGM] print Crazyhouse or Shogi holdings */
15160     if( gameInfo.holdingsWidth ) {
15161         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15162         q = p;
15163         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15164             piece = boards[move][i][BOARD_WIDTH-1];
15165             if( piece != EmptySquare )
15166               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15167                   *p++ = PieceToChar(piece);
15168         }
15169         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15170             piece = boards[move][BOARD_HEIGHT-i-1][0];
15171             if( piece != EmptySquare )
15172               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15173                   *p++ = PieceToChar(piece);
15174         }
15175
15176         if( q == p ) *p++ = '-';
15177         *p++ = ']';
15178         *p++ = ' ';
15179     }
15180
15181     /* Active color */
15182     *p++ = whiteToPlay ? 'w' : 'b';
15183     *p++ = ' ';
15184
15185   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15186     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15187   } else {
15188   if(nrCastlingRights) {
15189      q = p;
15190      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15191        /* [HGM] write directly from rights */
15192            if(boards[move][CASTLING][2] != NoRights &&
15193               boards[move][CASTLING][0] != NoRights   )
15194                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15195            if(boards[move][CASTLING][2] != NoRights &&
15196               boards[move][CASTLING][1] != NoRights   )
15197                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15198            if(boards[move][CASTLING][5] != NoRights &&
15199               boards[move][CASTLING][3] != NoRights   )
15200                 *p++ = boards[move][CASTLING][3] + AAA;
15201            if(boards[move][CASTLING][5] != NoRights &&
15202               boards[move][CASTLING][4] != NoRights   )
15203                 *p++ = boards[move][CASTLING][4] + AAA;
15204      } else {
15205
15206         /* [HGM] write true castling rights */
15207         if( nrCastlingRights == 6 ) {
15208             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15209                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15210             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15211                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15212             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15213                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15214             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15215                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15216         }
15217      }
15218      if (q == p) *p++ = '-'; /* No castling rights */
15219      *p++ = ' ';
15220   }
15221
15222   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15223      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15224     /* En passant target square */
15225     if (move > backwardMostMove) {
15226         fromX = moveList[move - 1][0] - AAA;
15227         fromY = moveList[move - 1][1] - ONE;
15228         toX = moveList[move - 1][2] - AAA;
15229         toY = moveList[move - 1][3] - ONE;
15230         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15231             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15232             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15233             fromX == toX) {
15234             /* 2-square pawn move just happened */
15235             *p++ = toX + AAA;
15236             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15237         } else {
15238             *p++ = '-';
15239         }
15240     } else if(move == backwardMostMove) {
15241         // [HGM] perhaps we should always do it like this, and forget the above?
15242         if((signed char)boards[move][EP_STATUS] >= 0) {
15243             *p++ = boards[move][EP_STATUS] + AAA;
15244             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15245         } else {
15246             *p++ = '-';
15247         }
15248     } else {
15249         *p++ = '-';
15250     }
15251     *p++ = ' ';
15252   }
15253   }
15254
15255     /* [HGM] find reversible plies */
15256     {   int i = 0, j=move;
15257
15258         if (appData.debugMode) { int k;
15259             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15260             for(k=backwardMostMove; k<=forwardMostMove; k++)
15261                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15262
15263         }
15264
15265         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15266         if( j == backwardMostMove ) i += initialRulePlies;
15267         sprintf(p, "%d ", i);
15268         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15269     }
15270     /* Fullmove number */
15271     sprintf(p, "%d", (move / 2) + 1);
15272
15273     return StrSave(buf);
15274 }
15275
15276 Boolean
15277 ParseFEN(board, blackPlaysFirst, fen)
15278     Board board;
15279      int *blackPlaysFirst;
15280      char *fen;
15281 {
15282     int i, j;
15283     char *p, c;
15284     int emptycount;
15285     ChessSquare piece;
15286
15287     p = fen;
15288
15289     /* [HGM] by default clear Crazyhouse holdings, if present */
15290     if(gameInfo.holdingsWidth) {
15291        for(i=0; i<BOARD_HEIGHT; i++) {
15292            board[i][0]             = EmptySquare; /* black holdings */
15293            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15294            board[i][1]             = (ChessSquare) 0; /* black counts */
15295            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15296        }
15297     }
15298
15299     /* Piece placement data */
15300     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15301         j = 0;
15302         for (;;) {
15303             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15304                 if (*p == '/') p++;
15305                 emptycount = gameInfo.boardWidth - j;
15306                 while (emptycount--)
15307                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15308                 break;
15309 #if(BOARD_FILES >= 10)
15310             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15311                 p++; emptycount=10;
15312                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15313                 while (emptycount--)
15314                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15315 #endif
15316             } else if (isdigit(*p)) {
15317                 emptycount = *p++ - '0';
15318                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15319                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15320                 while (emptycount--)
15321                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15322             } else if (*p == '+' || isalpha(*p)) {
15323                 if (j >= gameInfo.boardWidth) return FALSE;
15324                 if(*p=='+') {
15325                     piece = CharToPiece(*++p);
15326                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15327                     piece = (ChessSquare) (PROMOTED piece ); p++;
15328                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15329                 } else piece = CharToPiece(*p++);
15330
15331                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15332                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15333                     piece = (ChessSquare) (PROMOTED piece);
15334                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15335                     p++;
15336                 }
15337                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15338             } else {
15339                 return FALSE;
15340             }
15341         }
15342     }
15343     while (*p == '/' || *p == ' ') p++;
15344
15345     /* [HGM] look for Crazyhouse holdings here */
15346     while(*p==' ') p++;
15347     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15348         if(*p == '[') p++;
15349         if(*p == '-' ) p++; /* empty holdings */ else {
15350             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15351             /* if we would allow FEN reading to set board size, we would   */
15352             /* have to add holdings and shift the board read so far here   */
15353             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15354                 p++;
15355                 if((int) piece >= (int) BlackPawn ) {
15356                     i = (int)piece - (int)BlackPawn;
15357                     i = PieceToNumber((ChessSquare)i);
15358                     if( i >= gameInfo.holdingsSize ) return FALSE;
15359                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15360                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15361                 } else {
15362                     i = (int)piece - (int)WhitePawn;
15363                     i = PieceToNumber((ChessSquare)i);
15364                     if( i >= gameInfo.holdingsSize ) return FALSE;
15365                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15366                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15367                 }
15368             }
15369         }
15370         if(*p == ']') p++;
15371     }
15372
15373     while(*p == ' ') p++;
15374
15375     /* Active color */
15376     c = *p++;
15377     if(appData.colorNickNames) {
15378       if( c == appData.colorNickNames[0] ) c = 'w'; else
15379       if( c == appData.colorNickNames[1] ) c = 'b';
15380     }
15381     switch (c) {
15382       case 'w':
15383         *blackPlaysFirst = FALSE;
15384         break;
15385       case 'b':
15386         *blackPlaysFirst = TRUE;
15387         break;
15388       default:
15389         return FALSE;
15390     }
15391
15392     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15393     /* return the extra info in global variiables             */
15394
15395     /* set defaults in case FEN is incomplete */
15396     board[EP_STATUS] = EP_UNKNOWN;
15397     for(i=0; i<nrCastlingRights; i++ ) {
15398         board[CASTLING][i] =
15399             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15400     }   /* assume possible unless obviously impossible */
15401     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15402     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15403     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15404                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15405     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15406     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15407     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15408                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15409     FENrulePlies = 0;
15410
15411     while(*p==' ') p++;
15412     if(nrCastlingRights) {
15413       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15414           /* castling indicator present, so default becomes no castlings */
15415           for(i=0; i<nrCastlingRights; i++ ) {
15416                  board[CASTLING][i] = NoRights;
15417           }
15418       }
15419       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15420              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15421              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15422              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15423         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15424
15425         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15426             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15427             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15428         }
15429         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15430             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15431         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15432                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15433         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15434                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15435         switch(c) {
15436           case'K':
15437               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15438               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15439               board[CASTLING][2] = whiteKingFile;
15440               break;
15441           case'Q':
15442               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15443               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15444               board[CASTLING][2] = whiteKingFile;
15445               break;
15446           case'k':
15447               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15448               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15449               board[CASTLING][5] = blackKingFile;
15450               break;
15451           case'q':
15452               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15453               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15454               board[CASTLING][5] = blackKingFile;
15455           case '-':
15456               break;
15457           default: /* FRC castlings */
15458               if(c >= 'a') { /* black rights */
15459                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15460                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15461                   if(i == BOARD_RGHT) break;
15462                   board[CASTLING][5] = i;
15463                   c -= AAA;
15464                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15465                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15466                   if(c > i)
15467                       board[CASTLING][3] = c;
15468                   else
15469                       board[CASTLING][4] = c;
15470               } else { /* white rights */
15471                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15472                     if(board[0][i] == WhiteKing) break;
15473                   if(i == BOARD_RGHT) break;
15474                   board[CASTLING][2] = i;
15475                   c -= AAA - 'a' + 'A';
15476                   if(board[0][c] >= WhiteKing) break;
15477                   if(c > i)
15478                       board[CASTLING][0] = c;
15479                   else
15480                       board[CASTLING][1] = c;
15481               }
15482         }
15483       }
15484       for(i=0; i<nrCastlingRights; i++)
15485         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15486     if (appData.debugMode) {
15487         fprintf(debugFP, "FEN castling rights:");
15488         for(i=0; i<nrCastlingRights; i++)
15489         fprintf(debugFP, " %d", board[CASTLING][i]);
15490         fprintf(debugFP, "\n");
15491     }
15492
15493       while(*p==' ') p++;
15494     }
15495
15496     /* read e.p. field in games that know e.p. capture */
15497     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15498        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15499       if(*p=='-') {
15500         p++; board[EP_STATUS] = EP_NONE;
15501       } else {
15502          char c = *p++ - AAA;
15503
15504          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15505          if(*p >= '0' && *p <='9') p++;
15506          board[EP_STATUS] = c;
15507       }
15508     }
15509
15510
15511     if(sscanf(p, "%d", &i) == 1) {
15512         FENrulePlies = i; /* 50-move ply counter */
15513         /* (The move number is still ignored)    */
15514     }
15515
15516     return TRUE;
15517 }
15518
15519 void
15520 EditPositionPasteFEN(char *fen)
15521 {
15522   if (fen != NULL) {
15523     Board initial_position;
15524
15525     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15526       DisplayError(_("Bad FEN position in clipboard"), 0);
15527       return ;
15528     } else {
15529       int savedBlackPlaysFirst = blackPlaysFirst;
15530       EditPositionEvent();
15531       blackPlaysFirst = savedBlackPlaysFirst;
15532       CopyBoard(boards[0], initial_position);
15533       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15534       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15535       DisplayBothClocks();
15536       DrawPosition(FALSE, boards[currentMove]);
15537     }
15538   }
15539 }
15540
15541 static char cseq[12] = "\\   ";
15542
15543 Boolean set_cont_sequence(char *new_seq)
15544 {
15545     int len;
15546     Boolean ret;
15547
15548     // handle bad attempts to set the sequence
15549         if (!new_seq)
15550                 return 0; // acceptable error - no debug
15551
15552     len = strlen(new_seq);
15553     ret = (len > 0) && (len < sizeof(cseq));
15554     if (ret)
15555       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15556     else if (appData.debugMode)
15557       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15558     return ret;
15559 }
15560
15561 /*
15562     reformat a source message so words don't cross the width boundary.  internal
15563     newlines are not removed.  returns the wrapped size (no null character unless
15564     included in source message).  If dest is NULL, only calculate the size required
15565     for the dest buffer.  lp argument indicats line position upon entry, and it's
15566     passed back upon exit.
15567 */
15568 int wrap(char *dest, char *src, int count, int width, int *lp)
15569 {
15570     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15571
15572     cseq_len = strlen(cseq);
15573     old_line = line = *lp;
15574     ansi = len = clen = 0;
15575
15576     for (i=0; i < count; i++)
15577     {
15578         if (src[i] == '\033')
15579             ansi = 1;
15580
15581         // if we hit the width, back up
15582         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15583         {
15584             // store i & len in case the word is too long
15585             old_i = i, old_len = len;
15586
15587             // find the end of the last word
15588             while (i && src[i] != ' ' && src[i] != '\n')
15589             {
15590                 i--;
15591                 len--;
15592             }
15593
15594             // word too long?  restore i & len before splitting it
15595             if ((old_i-i+clen) >= width)
15596             {
15597                 i = old_i;
15598                 len = old_len;
15599             }
15600
15601             // extra space?
15602             if (i && src[i-1] == ' ')
15603                 len--;
15604
15605             if (src[i] != ' ' && src[i] != '\n')
15606             {
15607                 i--;
15608                 if (len)
15609                     len--;
15610             }
15611
15612             // now append the newline and continuation sequence
15613             if (dest)
15614                 dest[len] = '\n';
15615             len++;
15616             if (dest)
15617                 strncpy(dest+len, cseq, cseq_len);
15618             len += cseq_len;
15619             line = cseq_len;
15620             clen = cseq_len;
15621             continue;
15622         }
15623
15624         if (dest)
15625             dest[len] = src[i];
15626         len++;
15627         if (!ansi)
15628             line++;
15629         if (src[i] == '\n')
15630             line = 0;
15631         if (src[i] == 'm')
15632             ansi = 0;
15633     }
15634     if (dest && appData.debugMode)
15635     {
15636         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15637             count, width, line, len, *lp);
15638         show_bytes(debugFP, src, count);
15639         fprintf(debugFP, "\ndest: ");
15640         show_bytes(debugFP, dest, len);
15641         fprintf(debugFP, "\n");
15642     }
15643     *lp = dest ? line : old_line;
15644
15645     return len;
15646 }
15647
15648 // [HGM] vari: routines for shelving variations
15649
15650 void
15651 PushTail(int firstMove, int lastMove)
15652 {
15653         int i, j, nrMoves = lastMove - firstMove;
15654
15655         if(appData.icsActive) { // only in local mode
15656                 forwardMostMove = currentMove; // mimic old ICS behavior
15657                 return;
15658         }
15659         if(storedGames >= MAX_VARIATIONS-1) return;
15660
15661         // push current tail of game on stack
15662         savedResult[storedGames] = gameInfo.result;
15663         savedDetails[storedGames] = gameInfo.resultDetails;
15664         gameInfo.resultDetails = NULL;
15665         savedFirst[storedGames] = firstMove;
15666         savedLast [storedGames] = lastMove;
15667         savedFramePtr[storedGames] = framePtr;
15668         framePtr -= nrMoves; // reserve space for the boards
15669         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15670             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15671             for(j=0; j<MOVE_LEN; j++)
15672                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15673             for(j=0; j<2*MOVE_LEN; j++)
15674                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15675             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15676             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15677             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15678             pvInfoList[firstMove+i-1].depth = 0;
15679             commentList[framePtr+i] = commentList[firstMove+i];
15680             commentList[firstMove+i] = NULL;
15681         }
15682
15683         storedGames++;
15684         forwardMostMove = firstMove; // truncate game so we can start variation
15685         if(storedGames == 1) GreyRevert(FALSE);
15686 }
15687
15688 Boolean
15689 PopTail(Boolean annotate)
15690 {
15691         int i, j, nrMoves;
15692         char buf[8000], moveBuf[20];
15693
15694         if(appData.icsActive) return FALSE; // only in local mode
15695         if(!storedGames) return FALSE; // sanity
15696         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15697
15698         storedGames--;
15699         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15700         nrMoves = savedLast[storedGames] - currentMove;
15701         if(annotate) {
15702                 int cnt = 10;
15703                 if(!WhiteOnMove(currentMove))
15704                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15705                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15706                 for(i=currentMove; i<forwardMostMove; i++) {
15707                         if(WhiteOnMove(i))
15708                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15709                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15710                         strcat(buf, moveBuf);
15711                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15712                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15713                 }
15714                 strcat(buf, ")");
15715         }
15716         for(i=1; i<=nrMoves; i++) { // copy last variation back
15717             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15718             for(j=0; j<MOVE_LEN; j++)
15719                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15720             for(j=0; j<2*MOVE_LEN; j++)
15721                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15722             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15723             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15724             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15725             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15726             commentList[currentMove+i] = commentList[framePtr+i];
15727             commentList[framePtr+i] = NULL;
15728         }
15729         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15730         framePtr = savedFramePtr[storedGames];
15731         gameInfo.result = savedResult[storedGames];
15732         if(gameInfo.resultDetails != NULL) {
15733             free(gameInfo.resultDetails);
15734       }
15735         gameInfo.resultDetails = savedDetails[storedGames];
15736         forwardMostMove = currentMove + nrMoves;
15737         if(storedGames == 0) GreyRevert(TRUE);
15738         return TRUE;
15739 }
15740
15741 void
15742 CleanupTail()
15743 {       // remove all shelved variations
15744         int i;
15745         for(i=0; i<storedGames; i++) {
15746             if(savedDetails[i])
15747                 free(savedDetails[i]);
15748             savedDetails[i] = NULL;
15749         }
15750         for(i=framePtr; i<MAX_MOVES; i++) {
15751                 if(commentList[i]) free(commentList[i]);
15752                 commentList[i] = NULL;
15753         }
15754         framePtr = MAX_MOVES-1;
15755         storedGames = 0;
15756 }
15757
15758 void
15759 LoadVariation(int index, char *text)
15760 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15761         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15762         int level = 0, move;
15763
15764         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15765         // first find outermost bracketing variation
15766         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15767             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15768                 if(*p == '{') wait = '}'; else
15769                 if(*p == '[') wait = ']'; else
15770                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15771                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15772             }
15773             if(*p == wait) wait = NULLCHAR; // closing ]} found
15774             p++;
15775         }
15776         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15777         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15778         end[1] = NULLCHAR; // clip off comment beyond variation
15779         ToNrEvent(currentMove-1);
15780         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15781         // kludge: use ParsePV() to append variation to game
15782         move = currentMove;
15783         ParsePV(start, TRUE);
15784         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15785         ClearPremoveHighlights();
15786         CommentPopDown();
15787         ToNrEvent(currentMove+1);
15788 }
15789