Make book-edit function WB
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236
237 #ifdef WIN32
238        extern void ConsoleCreate();
239 #endif
240
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
244
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
252 Boolean abortMatch;
253
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
257 int endPV = -1;
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
261 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
265 Boolean partnerUp;
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
277 int chattingPartner;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
283
284 /* States for ics_getting_history */
285 #define H_FALSE 0
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
291
292 /* whosays values for GameEnds */
293 #define GE_ICS 0
294 #define GE_ENGINE 1
295 #define GE_PLAYER 2
296 #define GE_FILE 3
297 #define GE_XBOARD 4
298 #define GE_ENGINE1 5
299 #define GE_ENGINE2 6
300
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
303
304 /* Different types of move when calling RegisterMove */
305 #define CMAIL_MOVE   0
306 #define CMAIL_RESIGN 1
307 #define CMAIL_DRAW   2
308 #define CMAIL_ACCEPT 3
309
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
314
315 /* Telnet protocol constants */
316 #define TN_WILL 0373
317 #define TN_WONT 0374
318 #define TN_DO   0375
319 #define TN_DONT 0376
320 #define TN_IAC  0377
321 #define TN_ECHO 0001
322 #define TN_SGA  0003
323 #define TN_PORT 23
324
325 char*
326 safeStrCpy( char *dst, const char *src, size_t count )
327 { // [HGM] made safe
328   int i;
329   assert( dst != NULL );
330   assert( src != NULL );
331   assert( count > 0 );
332
333   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334   if(  i == count && dst[count-1] != NULLCHAR)
335     {
336       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337       if(appData.debugMode)
338       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339     }
340
341   return dst;
342 }
343
344 /* Some compiler can't cast u64 to double
345  * This function do the job for us:
346
347  * We use the highest bit for cast, this only
348  * works if the highest bit is not
349  * in use (This should not happen)
350  *
351  * We used this for all compiler
352  */
353 double
354 u64ToDouble(u64 value)
355 {
356   double r;
357   u64 tmp = value & u64Const(0x7fffffffffffffff);
358   r = (double)(s64)tmp;
359   if (value & u64Const(0x8000000000000000))
360        r +=  9.2233720368547758080e18; /* 2^63 */
361  return r;
362 }
363
364 /* Fake up flags for now, as we aren't keeping track of castling
365    availability yet. [HGM] Change of logic: the flag now only
366    indicates the type of castlings allowed by the rule of the game.
367    The actual rights themselves are maintained in the array
368    castlingRights, as part of the game history, and are not probed
369    by this function.
370  */
371 int
372 PosFlags(index)
373 {
374   int flags = F_ALL_CASTLE_OK;
375   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376   switch (gameInfo.variant) {
377   case VariantSuicide:
378     flags &= ~F_ALL_CASTLE_OK;
379   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380     flags |= F_IGNORE_CHECK;
381   case VariantLosers:
382     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
383     break;
384   case VariantAtomic:
385     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
386     break;
387   case VariantKriegspiel:
388     flags |= F_KRIEGSPIEL_CAPTURE;
389     break;
390   case VariantCapaRandom:
391   case VariantFischeRandom:
392     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393   case VariantNoCastle:
394   case VariantShatranj:
395   case VariantCourier:
396   case VariantMakruk:
397     flags &= ~F_ALL_CASTLE_OK;
398     break;
399   default:
400     break;
401   }
402   return flags;
403 }
404
405 FILE *gameFileFP, *debugFP;
406
407 /*
408     [AS] Note: sometimes, the sscanf() function is used to parse the input
409     into a fixed-size buffer. Because of this, we must be prepared to
410     receive strings as long as the size of the input buffer, which is currently
411     set to 4K for Windows and 8K for the rest.
412     So, we must either allocate sufficiently large buffers here, or
413     reduce the size of the input buffer in the input reading part.
414 */
415
416 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
417 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
418 char thinkOutput1[MSG_SIZ*10];
419
420 ChessProgramState first, second;
421
422 /* premove variables */
423 int premoveToX = 0;
424 int premoveToY = 0;
425 int premoveFromX = 0;
426 int premoveFromY = 0;
427 int premovePromoChar = 0;
428 int gotPremove = 0;
429 Boolean alarmSounded;
430 /* end premove variables */
431
432 char *ics_prefix = "$";
433 int ics_type = ICS_GENERIC;
434
435 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
436 int pauseExamForwardMostMove = 0;
437 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
438 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
439 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
440 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
441 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
442 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
443 int whiteFlag = FALSE, blackFlag = FALSE;
444 int userOfferedDraw = FALSE;
445 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
446 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
447 int cmailMoveType[CMAIL_MAX_GAMES];
448 long ics_clock_paused = 0;
449 ProcRef icsPR = NoProc, cmailPR = NoProc;
450 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
451 GameMode gameMode = BeginningOfGame;
452 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
453 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
454 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
455 int hiddenThinkOutputState = 0; /* [AS] */
456 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
457 int adjudicateLossPlies = 6;
458 char white_holding[64], black_holding[64];
459 TimeMark lastNodeCountTime;
460 long lastNodeCount=0;
461 int shiftKey; // [HGM] set by mouse handler
462
463 int have_sent_ICS_logon = 0;
464 int movesPerSession;
465 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
466 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
475
476 /* animateTraining preserves the state of appData.animate
477  * when Training mode is activated. This allows the
478  * response to be animated when appData.animate == TRUE and
479  * appData.animateDragging == TRUE.
480  */
481 Boolean animateTraining;
482
483 GameInfo gameInfo;
484
485 AppData appData;
486
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char  initialRights[BOARD_FILES];
491 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int   initialRulePlies, FENrulePlies;
493 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 int loadFlag = 0;
495 int shuffleOpenings;
496 int mute; // mute all sounds
497
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int storedGames = 0;
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
507
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
513
514 ChessSquare  FIDEArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackBishop, BlackKnight, BlackRook }
519 };
520
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525         BlackKing, BlackKing, BlackKnight, BlackRook }
526 };
527
528 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531     { BlackRook, BlackMan, BlackBishop, BlackQueen,
532         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
533 };
534
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
540 };
541
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
547 };
548
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 };
555
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackMan, BlackFerz,
560         BlackKing, BlackMan, BlackKnight, BlackRook }
561 };
562
563
564 #if (BOARD_FILES>=10)
565 ChessSquare ShogiArray[2][BOARD_FILES] = {
566     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
567         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
568     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
569         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
570 };
571
572 ChessSquare XiangqiArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
574         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
576         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
577 };
578
579 ChessSquare CapablancaArray[2][BOARD_FILES] = {
580     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
581         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
583         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
584 };
585
586 ChessSquare GreatArray[2][BOARD_FILES] = {
587     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
588         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
589     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
590         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
591 };
592
593 ChessSquare JanusArray[2][BOARD_FILES] = {
594     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
595         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
596     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
597         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
614         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
616         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating(str)
649   char *str;
650 {
651   while(*str && !isdigit(*str)) ++str;
652   if (!*str)
653     return 0;   /* One of the special "no rating" cases */
654   else
655     return atoi(str);
656 }
657
658 void
659 ClearProgramStats()
660 {
661     /* Init programStats */
662     programStats.movelist[0] = 0;
663     programStats.depth = 0;
664     programStats.nr_moves = 0;
665     programStats.moves_left = 0;
666     programStats.nodes = 0;
667     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
668     programStats.score = 0;
669     programStats.got_only_move = 0;
670     programStats.got_fail = 0;
671     programStats.line_is_book = 0;
672 }
673
674 void
675 CommonEngineInit()
676 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677     if (appData.firstPlaysBlack) {
678         first.twoMachinesColor = "black\n";
679         second.twoMachinesColor = "white\n";
680     } else {
681         first.twoMachinesColor = "white\n";
682         second.twoMachinesColor = "black\n";
683     }
684
685     first.other = &second;
686     second.other = &first;
687
688     { float norm = 1;
689         if(appData.timeOddsMode) {
690             norm = appData.timeOdds[0];
691             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692         }
693         first.timeOdds  = appData.timeOdds[0]/norm;
694         second.timeOdds = appData.timeOdds[1]/norm;
695     }
696
697     if(programVersion) free(programVersion);
698     if (appData.noChessProgram) {
699         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700         sprintf(programVersion, "%s", PACKAGE_STRING);
701     } else {
702       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
705     }
706 }
707
708 void
709 UnloadEngine(ChessProgramState *cps)
710 {
711         /* Kill off first chess program */
712         if (cps->isr != NULL)
713           RemoveInputSource(cps->isr);
714         cps->isr = NULL;
715
716         if (cps->pr != NoProc) {
717             ExitAnalyzeMode();
718             DoSleep( appData.delayBeforeQuit );
719             SendToProgram("quit\n", cps);
720             DoSleep( appData.delayAfterQuit );
721             DestroyChildProcess(cps->pr, cps->useSigterm);
722         }
723         cps->pr = NoProc;
724         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 }
726
727 void
728 ClearOptions(ChessProgramState *cps)
729 {
730     int i;
731     cps->nrOptions = cps->comboCnt = 0;
732     for(i=0; i<MAX_OPTIONS; i++) {
733         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734         cps->option[i].textValue = 0;
735     }
736 }
737
738 char *engineNames[] = {
739 "first",
740 "second"
741 };
742
743 void
744 InitEngine(ChessProgramState *cps, int n)
745 {   // [HGM] all engine initialiation put in a function that does one engine
746
747     ClearOptions(cps);
748
749     cps->which = engineNames[n];
750     cps->maybeThinking = FALSE;
751     cps->pr = NoProc;
752     cps->isr = NULL;
753     cps->sendTime = 2;
754     cps->sendDrawOffers = 1;
755
756     cps->program = appData.chessProgram[n];
757     cps->host = appData.host[n];
758     cps->dir = appData.directory[n];
759     cps->initString = appData.engInitString[n];
760     cps->computerString = appData.computerString[n];
761     cps->useSigint  = TRUE;
762     cps->useSigterm = TRUE;
763     cps->reuse = appData.reuse[n];
764     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
765     cps->useSetboard = FALSE;
766     cps->useSAN = FALSE;
767     cps->usePing = FALSE;
768     cps->lastPing = 0;
769     cps->lastPong = 0;
770     cps->usePlayother = FALSE;
771     cps->useColors = TRUE;
772     cps->useUsermove = FALSE;
773     cps->sendICS = FALSE;
774     cps->sendName = appData.icsActive;
775     cps->sdKludge = FALSE;
776     cps->stKludge = FALSE;
777     TidyProgramName(cps->program, cps->host, cps->tidy);
778     cps->matchWins = 0;
779     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
780     cps->analysisSupport = 2; /* detect */
781     cps->analyzing = FALSE;
782     cps->initDone = FALSE;
783
784     /* New features added by Tord: */
785     cps->useFEN960 = FALSE;
786     cps->useOOCastle = TRUE;
787     /* End of new features added by Tord. */
788     cps->fenOverride  = appData.fenOverride[n];
789
790     /* [HGM] time odds: set factor for each machine */
791     cps->timeOdds  = appData.timeOdds[n];
792
793     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
794     cps->accumulateTC = appData.accumulateTC[n];
795     cps->maxNrOfSessions = 1;
796
797     /* [HGM] debug */
798     cps->debug = FALSE;
799     cps->supportsNPS = UNKNOWN;
800
801     /* [HGM] options */
802     cps->optionSettings  = appData.engOptions[n];
803
804     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
805     cps->isUCI = appData.isUCI[n]; /* [AS] */
806     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
807
808     if (appData.protocolVersion[n] > PROTOVER
809         || appData.protocolVersion[n] < 1)
810       {
811         char buf[MSG_SIZ];
812         int len;
813
814         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
815                        appData.protocolVersion[n]);
816         if( (len > MSG_SIZ) && appData.debugMode )
817           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
818
819         DisplayFatalError(buf, 0, 2);
820       }
821     else
822       {
823         cps->protocolVersion = appData.protocolVersion[n];
824       }
825
826     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
827 }
828
829 ChessProgramState *savCps;
830
831 void
832 LoadEngine()
833 {
834     int i;
835     if(WaitForEngine(savCps, LoadEngine)) return;
836     CommonEngineInit(); // recalculate time odds
837     if(gameInfo.variant != StringToVariant(appData.variant)) {
838         // we changed variant when loading the engine; this forces us to reset
839         Reset(TRUE, savCps != &first);
840         EditGameEvent(); // for consistency with other path, as Reset changes mode
841     }
842     InitChessProgram(savCps, FALSE);
843     SendToProgram("force\n", savCps);
844     DisplayMessage("", "");
845     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
846     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
847     ThawUI();
848     SetGNUMode();
849 }
850
851 void
852 ReplaceEngine(ChessProgramState *cps, int n)
853 {
854     EditGameEvent();
855     UnloadEngine(cps);
856     appData.noChessProgram = FALSE;
857     appData.clockMode = TRUE;
858     InitEngine(cps, n);
859     if(n) return; // only startup first engine immediately; second can wait
860     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
861     LoadEngine();
862 }
863
864 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
865 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
866
867 static char resetOptions[] = 
868         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
869         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
870
871 void
872 Load(ChessProgramState *cps, int i)
873 {
874     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
875     if(engineLine[0]) { // an engine was selected from the combo box
876         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
877         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
878         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
879         ParseArgsFromString(buf);
880         SwapEngines(i);
881         ReplaceEngine(cps, i);
882         return;
883     }
884     p = engineName;
885     while(q = strchr(p, SLASH)) p = q+1;
886     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
887     if(engineDir[0] != NULLCHAR)
888         appData.directory[i] = engineDir;
889     else if(p != engineName) { // derive directory from engine path, when not given
890         p[-1] = 0;
891         appData.directory[i] = strdup(engineName);
892         p[-1] = SLASH;
893     } else appData.directory[i] = ".";
894     if(params[0]) {
895         snprintf(command, MSG_SIZ, "%s %s", p, params);
896         p = command;
897     }
898     appData.chessProgram[i] = strdup(p);
899     appData.isUCI[i] = isUCI;
900     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
901     appData.hasOwnBookUCI[i] = hasBook;
902     if(!nickName[0]) useNick = FALSE;
903     if(useNick) ASSIGN(appData.pgnName[i], nickName);
904     if(addToList) {
905         int len;
906         q = firstChessProgramNames;
907         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
908         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
909                         useNick ? " -fn \"" : "",
910                         useNick ? nickName : "",
911                         useNick ? "\"" : "",
912                         v1 ? " -firstProtocolVersion 1" : "",
913                         hasBook ? "" : " -fNoOwnBookUCI",
914                         isUCI ? " -fUCI" : "",
915                         storeVariant ? " -variant " : "",
916                         storeVariant ? VariantName(gameInfo.variant) : "");
917         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
918         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
919         if(q)   free(q);
920     }
921     ReplaceEngine(cps, i);
922 }
923
924 void
925 InitTimeControls()
926 {
927     int matched, min, sec;
928     /*
929      * Parse timeControl resource
930      */
931     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
932                           appData.movesPerSession)) {
933         char buf[MSG_SIZ];
934         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
935         DisplayFatalError(buf, 0, 2);
936     }
937
938     /*
939      * Parse searchTime resource
940      */
941     if (*appData.searchTime != NULLCHAR) {
942         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
943         if (matched == 1) {
944             searchTime = min * 60;
945         } else if (matched == 2) {
946             searchTime = min * 60 + sec;
947         } else {
948             char buf[MSG_SIZ];
949             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
950             DisplayFatalError(buf, 0, 2);
951         }
952     }
953 }
954
955 void
956 InitBackEnd1()
957 {
958
959     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
960     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
961
962     GetTimeMark(&programStartTime);
963     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
964     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
965
966     ClearProgramStats();
967     programStats.ok_to_send = 1;
968     programStats.seen_stat = 0;
969
970     /*
971      * Initialize game list
972      */
973     ListNew(&gameList);
974
975
976     /*
977      * Internet chess server status
978      */
979     if (appData.icsActive) {
980         appData.matchMode = FALSE;
981         appData.matchGames = 0;
982 #if ZIPPY
983         appData.noChessProgram = !appData.zippyPlay;
984 #else
985         appData.zippyPlay = FALSE;
986         appData.zippyTalk = FALSE;
987         appData.noChessProgram = TRUE;
988 #endif
989         if (*appData.icsHelper != NULLCHAR) {
990             appData.useTelnet = TRUE;
991             appData.telnetProgram = appData.icsHelper;
992         }
993     } else {
994         appData.zippyTalk = appData.zippyPlay = FALSE;
995     }
996
997     /* [AS] Initialize pv info list [HGM] and game state */
998     {
999         int i, j;
1000
1001         for( i=0; i<=framePtr; i++ ) {
1002             pvInfoList[i].depth = -1;
1003             boards[i][EP_STATUS] = EP_NONE;
1004             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1005         }
1006     }
1007
1008     InitTimeControls();
1009
1010     /* [AS] Adjudication threshold */
1011     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1012
1013     InitEngine(&first, 0);
1014     InitEngine(&second, 1);
1015     CommonEngineInit();
1016
1017     if (appData.icsActive) {
1018         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1019     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1020         appData.clockMode = FALSE;
1021         first.sendTime = second.sendTime = 0;
1022     }
1023
1024 #if ZIPPY
1025     /* Override some settings from environment variables, for backward
1026        compatibility.  Unfortunately it's not feasible to have the env
1027        vars just set defaults, at least in xboard.  Ugh.
1028     */
1029     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1030       ZippyInit();
1031     }
1032 #endif
1033
1034     if (!appData.icsActive) {
1035       char buf[MSG_SIZ];
1036       int len;
1037
1038       /* Check for variants that are supported only in ICS mode,
1039          or not at all.  Some that are accepted here nevertheless
1040          have bugs; see comments below.
1041       */
1042       VariantClass variant = StringToVariant(appData.variant);
1043       switch (variant) {
1044       case VariantBughouse:     /* need four players and two boards */
1045       case VariantKriegspiel:   /* need to hide pieces and move details */
1046         /* case VariantFischeRandom: (Fabien: moved below) */
1047         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1048         if( (len > MSG_SIZ) && appData.debugMode )
1049           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1050
1051         DisplayFatalError(buf, 0, 2);
1052         return;
1053
1054       case VariantUnknown:
1055       case VariantLoadable:
1056       case Variant29:
1057       case Variant30:
1058       case Variant31:
1059       case Variant32:
1060       case Variant33:
1061       case Variant34:
1062       case Variant35:
1063       case Variant36:
1064       default:
1065         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1066         if( (len > MSG_SIZ) && appData.debugMode )
1067           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1068
1069         DisplayFatalError(buf, 0, 2);
1070         return;
1071
1072       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1073       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1074       case VariantGothic:     /* [HGM] should work */
1075       case VariantCapablanca: /* [HGM] should work */
1076       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1077       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1078       case VariantKnightmate: /* [HGM] should work */
1079       case VariantCylinder:   /* [HGM] untested */
1080       case VariantFalcon:     /* [HGM] untested */
1081       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1082                                  offboard interposition not understood */
1083       case VariantNormal:     /* definitely works! */
1084       case VariantWildCastle: /* pieces not automatically shuffled */
1085       case VariantNoCastle:   /* pieces not automatically shuffled */
1086       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1087       case VariantLosers:     /* should work except for win condition,
1088                                  and doesn't know captures are mandatory */
1089       case VariantSuicide:    /* should work except for win condition,
1090                                  and doesn't know captures are mandatory */
1091       case VariantGiveaway:   /* should work except for win condition,
1092                                  and doesn't know captures are mandatory */
1093       case VariantTwoKings:   /* should work */
1094       case VariantAtomic:     /* should work except for win condition */
1095       case Variant3Check:     /* should work except for win condition */
1096       case VariantShatranj:   /* should work except for all win conditions */
1097       case VariantMakruk:     /* should work except for daw countdown */
1098       case VariantBerolina:   /* might work if TestLegality is off */
1099       case VariantCapaRandom: /* should work */
1100       case VariantJanus:      /* should work */
1101       case VariantSuper:      /* experimental */
1102       case VariantGreat:      /* experimental, requires legality testing to be off */
1103       case VariantSChess:     /* S-Chess, should work */
1104       case VariantSpartan:    /* should work */
1105         break;
1106       }
1107     }
1108
1109 }
1110
1111 int NextIntegerFromString( char ** str, long * value )
1112 {
1113     int result = -1;
1114     char * s = *str;
1115
1116     while( *s == ' ' || *s == '\t' ) {
1117         s++;
1118     }
1119
1120     *value = 0;
1121
1122     if( *s >= '0' && *s <= '9' ) {
1123         while( *s >= '0' && *s <= '9' ) {
1124             *value = *value * 10 + (*s - '0');
1125             s++;
1126         }
1127
1128         result = 0;
1129     }
1130
1131     *str = s;
1132
1133     return result;
1134 }
1135
1136 int NextTimeControlFromString( char ** str, long * value )
1137 {
1138     long temp;
1139     int result = NextIntegerFromString( str, &temp );
1140
1141     if( result == 0 ) {
1142         *value = temp * 60; /* Minutes */
1143         if( **str == ':' ) {
1144             (*str)++;
1145             result = NextIntegerFromString( str, &temp );
1146             *value += temp; /* Seconds */
1147         }
1148     }
1149
1150     return result;
1151 }
1152
1153 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1154 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1155     int result = -1, type = 0; long temp, temp2;
1156
1157     if(**str != ':') return -1; // old params remain in force!
1158     (*str)++;
1159     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1160     if( NextIntegerFromString( str, &temp ) ) return -1;
1161     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1162
1163     if(**str != '/') {
1164         /* time only: incremental or sudden-death time control */
1165         if(**str == '+') { /* increment follows; read it */
1166             (*str)++;
1167             if(**str == '!') type = *(*str)++; // Bronstein TC
1168             if(result = NextIntegerFromString( str, &temp2)) return -1;
1169             *inc = temp2 * 1000;
1170             if(**str == '.') { // read fraction of increment
1171                 char *start = ++(*str);
1172                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1173                 temp2 *= 1000;
1174                 while(start++ < *str) temp2 /= 10;
1175                 *inc += temp2;
1176             }
1177         } else *inc = 0;
1178         *moves = 0; *tc = temp * 1000; *incType = type;
1179         return 0;
1180     }
1181
1182     (*str)++; /* classical time control */
1183     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1184
1185     if(result == 0) {
1186         *moves = temp;
1187         *tc    = temp2 * 1000;
1188         *inc   = 0;
1189         *incType = type;
1190     }
1191     return result;
1192 }
1193
1194 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1195 {   /* [HGM] get time to add from the multi-session time-control string */
1196     int incType, moves=1; /* kludge to force reading of first session */
1197     long time, increment;
1198     char *s = tcString;
1199
1200     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1201     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1202     do {
1203         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1204         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1205         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1206         if(movenr == -1) return time;    /* last move before new session     */
1207         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1208         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1209         if(!moves) return increment;     /* current session is incremental   */
1210         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1211     } while(movenr >= -1);               /* try again for next session       */
1212
1213     return 0; // no new time quota on this move
1214 }
1215
1216 int
1217 ParseTimeControl(tc, ti, mps)
1218      char *tc;
1219      float ti;
1220      int mps;
1221 {
1222   long tc1;
1223   long tc2;
1224   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1225   int min, sec=0;
1226
1227   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1228   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1229       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1230   if(ti > 0) {
1231
1232     if(mps)
1233       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1234     else 
1235       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1236   } else {
1237     if(mps)
1238       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1239     else 
1240       snprintf(buf, MSG_SIZ, ":%s", mytc);
1241   }
1242   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1243   
1244   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1245     return FALSE;
1246   }
1247
1248   if( *tc == '/' ) {
1249     /* Parse second time control */
1250     tc++;
1251
1252     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1253       return FALSE;
1254     }
1255
1256     if( tc2 == 0 ) {
1257       return FALSE;
1258     }
1259
1260     timeControl_2 = tc2 * 1000;
1261   }
1262   else {
1263     timeControl_2 = 0;
1264   }
1265
1266   if( tc1 == 0 ) {
1267     return FALSE;
1268   }
1269
1270   timeControl = tc1 * 1000;
1271
1272   if (ti >= 0) {
1273     timeIncrement = ti * 1000;  /* convert to ms */
1274     movesPerSession = 0;
1275   } else {
1276     timeIncrement = 0;
1277     movesPerSession = mps;
1278   }
1279   return TRUE;
1280 }
1281
1282 void
1283 InitBackEnd2()
1284 {
1285     if (appData.debugMode) {
1286         fprintf(debugFP, "%s\n", programVersion);
1287     }
1288
1289     set_cont_sequence(appData.wrapContSeq);
1290     if (appData.matchGames > 0) {
1291         appData.matchMode = TRUE;
1292     } else if (appData.matchMode) {
1293         appData.matchGames = 1;
1294     }
1295     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1296         appData.matchGames = appData.sameColorGames;
1297     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1298         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1299         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1300     }
1301     Reset(TRUE, FALSE);
1302     if (appData.noChessProgram || first.protocolVersion == 1) {
1303       InitBackEnd3();
1304     } else {
1305       /* kludge: allow timeout for initial "feature" commands */
1306       FreezeUI();
1307       DisplayMessage("", _("Starting chess program"));
1308       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1309     }
1310 }
1311
1312 int
1313 CalculateIndex(int index, int gameNr)
1314 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1315     int res;
1316     if(index > 0) return index; // fixed nmber
1317     if(index == 0) return 1;
1318     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1319     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1320     return res;
1321 }
1322
1323 int
1324 LoadGameOrPosition(int gameNr)
1325 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1326     if (*appData.loadGameFile != NULLCHAR) {
1327         if (!LoadGameFromFile(appData.loadGameFile,
1328                 CalculateIndex(appData.loadGameIndex, gameNr),
1329                               appData.loadGameFile, FALSE)) {
1330             DisplayFatalError(_("Bad game file"), 0, 1);
1331             return 0;
1332         }
1333     } else if (*appData.loadPositionFile != NULLCHAR) {
1334         if (!LoadPositionFromFile(appData.loadPositionFile,
1335                 CalculateIndex(appData.loadPositionIndex, gameNr),
1336                                   appData.loadPositionFile)) {
1337             DisplayFatalError(_("Bad position file"), 0, 1);
1338             return 0;
1339         }
1340     }
1341     return 1;
1342 }
1343
1344 void
1345 ReserveGame(int gameNr, char resChar)
1346 {
1347     FILE *tf = fopen(appData.tourneyFile, "r+");
1348     char *p, *q, c, buf[MSG_SIZ];
1349     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1350     safeStrCpy(buf, lastMsg, MSG_SIZ);
1351     DisplayMessage(_("Pick new game"), "");
1352     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1353     ParseArgsFromFile(tf);
1354     p = q = appData.results;
1355     if(appData.debugMode) {
1356       char *r = appData.participants;
1357       fprintf(debugFP, "results = '%s'\n", p);
1358       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1359       fprintf(debugFP, "\n");
1360     }
1361     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1362     nextGame = q - p;
1363     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1364     safeStrCpy(q, p, strlen(p) + 2);
1365     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1366     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1367     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1368         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1369         q[nextGame] = '*';
1370     }
1371     fseek(tf, -(strlen(p)+4), SEEK_END);
1372     c = fgetc(tf);
1373     if(c != '"') // depending on DOS or Unix line endings we can be one off
1374          fseek(tf, -(strlen(p)+2), SEEK_END);
1375     else fseek(tf, -(strlen(p)+3), SEEK_END);
1376     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1377     DisplayMessage(buf, "");
1378     free(p); appData.results = q;
1379     if(nextGame <= appData.matchGames && resChar != ' ' &&
1380        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1381         UnloadEngine(&first);  // next game belongs to other pairing;
1382         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1383     }
1384 }
1385
1386 void
1387 MatchEvent(int mode)
1388 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1389         int dummy;
1390         if(matchMode) { // already in match mode: switch it off
1391             abortMatch = TRUE;
1392             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1393             ModeHighlight(); // kludgey way to remove checkmark...
1394             return;
1395         }
1396 //      if(gameMode != BeginningOfGame) {
1397 //          DisplayError(_("You can only start a match from the initial position."), 0);
1398 //          return;
1399 //      }
1400         abortMatch = FALSE;
1401         appData.matchGames = appData.defaultMatchGames;
1402         /* Set up machine vs. machine match */
1403         nextGame = 0;
1404         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1405         if(appData.tourneyFile[0]) {
1406             ReserveGame(-1, 0);
1407             if(nextGame > appData.matchGames) {
1408                 char buf[MSG_SIZ];
1409                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1410                 DisplayError(buf, 0);
1411                 appData.tourneyFile[0] = 0;
1412                 return;
1413             }
1414         } else
1415         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1416             DisplayFatalError(_("Can't have a match with no chess programs"),
1417                               0, 2);
1418             return;
1419         }
1420         matchMode = mode;
1421         matchGame = roundNr = 1;
1422         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1423         NextMatchGame();
1424 }
1425
1426 void
1427 InitBackEnd3 P((void))
1428 {
1429     GameMode initialMode;
1430     char buf[MSG_SIZ];
1431     int err, len;
1432
1433     InitChessProgram(&first, startedFromSetupPosition);
1434
1435     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1436         free(programVersion);
1437         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1438         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1439     }
1440
1441     if (appData.icsActive) {
1442 #ifdef WIN32
1443         /* [DM] Make a console window if needed [HGM] merged ifs */
1444         ConsoleCreate();
1445 #endif
1446         err = establish();
1447         if (err != 0)
1448           {
1449             if (*appData.icsCommPort != NULLCHAR)
1450               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1451                              appData.icsCommPort);
1452             else
1453               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1454                         appData.icsHost, appData.icsPort);
1455
1456             if( (len > MSG_SIZ) && appData.debugMode )
1457               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1458
1459             DisplayFatalError(buf, err, 1);
1460             return;
1461         }
1462         SetICSMode();
1463         telnetISR =
1464           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1465         fromUserISR =
1466           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1467         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1468             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1469     } else if (appData.noChessProgram) {
1470         SetNCPMode();
1471     } else {
1472         SetGNUMode();
1473     }
1474
1475     if (*appData.cmailGameName != NULLCHAR) {
1476         SetCmailMode();
1477         OpenLoopback(&cmailPR);
1478         cmailISR =
1479           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1480     }
1481
1482     ThawUI();
1483     DisplayMessage("", "");
1484     if (StrCaseCmp(appData.initialMode, "") == 0) {
1485       initialMode = BeginningOfGame;
1486       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1487         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1488         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1489         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1490         ModeHighlight();
1491       }
1492     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1493       initialMode = TwoMachinesPlay;
1494     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1495       initialMode = AnalyzeFile;
1496     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1497       initialMode = AnalyzeMode;
1498     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1499       initialMode = MachinePlaysWhite;
1500     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1501       initialMode = MachinePlaysBlack;
1502     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1503       initialMode = EditGame;
1504     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1505       initialMode = EditPosition;
1506     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1507       initialMode = Training;
1508     } else {
1509       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1510       if( (len > MSG_SIZ) && appData.debugMode )
1511         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1512
1513       DisplayFatalError(buf, 0, 2);
1514       return;
1515     }
1516
1517     if (appData.matchMode) {
1518         if(appData.tourneyFile[0]) { // start tourney from command line
1519             FILE *f;
1520             if(f = fopen(appData.tourneyFile, "r")) {
1521                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1522                 fclose(f);
1523             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1524         }
1525         MatchEvent(TRUE);
1526     } else if (*appData.cmailGameName != NULLCHAR) {
1527         /* Set up cmail mode */
1528         ReloadCmailMsgEvent(TRUE);
1529     } else {
1530         /* Set up other modes */
1531         if (initialMode == AnalyzeFile) {
1532           if (*appData.loadGameFile == NULLCHAR) {
1533             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1534             return;
1535           }
1536         }
1537         if (*appData.loadGameFile != NULLCHAR) {
1538             (void) LoadGameFromFile(appData.loadGameFile,
1539                                     appData.loadGameIndex,
1540                                     appData.loadGameFile, TRUE);
1541         } else if (*appData.loadPositionFile != NULLCHAR) {
1542             (void) LoadPositionFromFile(appData.loadPositionFile,
1543                                         appData.loadPositionIndex,
1544                                         appData.loadPositionFile);
1545             /* [HGM] try to make self-starting even after FEN load */
1546             /* to allow automatic setup of fairy variants with wtm */
1547             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1548                 gameMode = BeginningOfGame;
1549                 setboardSpoiledMachineBlack = 1;
1550             }
1551             /* [HGM] loadPos: make that every new game uses the setup */
1552             /* from file as long as we do not switch variant          */
1553             if(!blackPlaysFirst) {
1554                 startedFromPositionFile = TRUE;
1555                 CopyBoard(filePosition, boards[0]);
1556             }
1557         }
1558         if (initialMode == AnalyzeMode) {
1559           if (appData.noChessProgram) {
1560             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1561             return;
1562           }
1563           if (appData.icsActive) {
1564             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1565             return;
1566           }
1567           AnalyzeModeEvent();
1568         } else if (initialMode == AnalyzeFile) {
1569           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1570           ShowThinkingEvent();
1571           AnalyzeFileEvent();
1572           AnalysisPeriodicEvent(1);
1573         } else if (initialMode == MachinePlaysWhite) {
1574           if (appData.noChessProgram) {
1575             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1576                               0, 2);
1577             return;
1578           }
1579           if (appData.icsActive) {
1580             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1581                               0, 2);
1582             return;
1583           }
1584           MachineWhiteEvent();
1585         } else if (initialMode == MachinePlaysBlack) {
1586           if (appData.noChessProgram) {
1587             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1588                               0, 2);
1589             return;
1590           }
1591           if (appData.icsActive) {
1592             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1593                               0, 2);
1594             return;
1595           }
1596           MachineBlackEvent();
1597         } else if (initialMode == TwoMachinesPlay) {
1598           if (appData.noChessProgram) {
1599             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1600                               0, 2);
1601             return;
1602           }
1603           if (appData.icsActive) {
1604             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1605                               0, 2);
1606             return;
1607           }
1608           TwoMachinesEvent();
1609         } else if (initialMode == EditGame) {
1610           EditGameEvent();
1611         } else if (initialMode == EditPosition) {
1612           EditPositionEvent();
1613         } else if (initialMode == Training) {
1614           if (*appData.loadGameFile == NULLCHAR) {
1615             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1616             return;
1617           }
1618           TrainingEvent();
1619         }
1620     }
1621 }
1622
1623 /*
1624  * Establish will establish a contact to a remote host.port.
1625  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1626  *  used to talk to the host.
1627  * Returns 0 if okay, error code if not.
1628  */
1629 int
1630 establish()
1631 {
1632     char buf[MSG_SIZ];
1633
1634     if (*appData.icsCommPort != NULLCHAR) {
1635         /* Talk to the host through a serial comm port */
1636         return OpenCommPort(appData.icsCommPort, &icsPR);
1637
1638     } else if (*appData.gateway != NULLCHAR) {
1639         if (*appData.remoteShell == NULLCHAR) {
1640             /* Use the rcmd protocol to run telnet program on a gateway host */
1641             snprintf(buf, sizeof(buf), "%s %s %s",
1642                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1643             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1644
1645         } else {
1646             /* Use the rsh program to run telnet program on a gateway host */
1647             if (*appData.remoteUser == NULLCHAR) {
1648                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1649                         appData.gateway, appData.telnetProgram,
1650                         appData.icsHost, appData.icsPort);
1651             } else {
1652                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1653                         appData.remoteShell, appData.gateway,
1654                         appData.remoteUser, appData.telnetProgram,
1655                         appData.icsHost, appData.icsPort);
1656             }
1657             return StartChildProcess(buf, "", &icsPR);
1658
1659         }
1660     } else if (appData.useTelnet) {
1661         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1662
1663     } else {
1664         /* TCP socket interface differs somewhat between
1665            Unix and NT; handle details in the front end.
1666            */
1667         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1668     }
1669 }
1670
1671 void EscapeExpand(char *p, char *q)
1672 {       // [HGM] initstring: routine to shape up string arguments
1673         while(*p++ = *q++) if(p[-1] == '\\')
1674             switch(*q++) {
1675                 case 'n': p[-1] = '\n'; break;
1676                 case 'r': p[-1] = '\r'; break;
1677                 case 't': p[-1] = '\t'; break;
1678                 case '\\': p[-1] = '\\'; break;
1679                 case 0: *p = 0; return;
1680                 default: p[-1] = q[-1]; break;
1681             }
1682 }
1683
1684 void
1685 show_bytes(fp, buf, count)
1686      FILE *fp;
1687      char *buf;
1688      int count;
1689 {
1690     while (count--) {
1691         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1692             fprintf(fp, "\\%03o", *buf & 0xff);
1693         } else {
1694             putc(*buf, fp);
1695         }
1696         buf++;
1697     }
1698     fflush(fp);
1699 }
1700
1701 /* Returns an errno value */
1702 int
1703 OutputMaybeTelnet(pr, message, count, outError)
1704      ProcRef pr;
1705      char *message;
1706      int count;
1707      int *outError;
1708 {
1709     char buf[8192], *p, *q, *buflim;
1710     int left, newcount, outcount;
1711
1712     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1713         *appData.gateway != NULLCHAR) {
1714         if (appData.debugMode) {
1715             fprintf(debugFP, ">ICS: ");
1716             show_bytes(debugFP, message, count);
1717             fprintf(debugFP, "\n");
1718         }
1719         return OutputToProcess(pr, message, count, outError);
1720     }
1721
1722     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1723     p = message;
1724     q = buf;
1725     left = count;
1726     newcount = 0;
1727     while (left) {
1728         if (q >= buflim) {
1729             if (appData.debugMode) {
1730                 fprintf(debugFP, ">ICS: ");
1731                 show_bytes(debugFP, buf, newcount);
1732                 fprintf(debugFP, "\n");
1733             }
1734             outcount = OutputToProcess(pr, buf, newcount, outError);
1735             if (outcount < newcount) return -1; /* to be sure */
1736             q = buf;
1737             newcount = 0;
1738         }
1739         if (*p == '\n') {
1740             *q++ = '\r';
1741             newcount++;
1742         } else if (((unsigned char) *p) == TN_IAC) {
1743             *q++ = (char) TN_IAC;
1744             newcount ++;
1745         }
1746         *q++ = *p++;
1747         newcount++;
1748         left--;
1749     }
1750     if (appData.debugMode) {
1751         fprintf(debugFP, ">ICS: ");
1752         show_bytes(debugFP, buf, newcount);
1753         fprintf(debugFP, "\n");
1754     }
1755     outcount = OutputToProcess(pr, buf, newcount, outError);
1756     if (outcount < newcount) return -1; /* to be sure */
1757     return count;
1758 }
1759
1760 void
1761 read_from_player(isr, closure, message, count, error)
1762      InputSourceRef isr;
1763      VOIDSTAR closure;
1764      char *message;
1765      int count;
1766      int error;
1767 {
1768     int outError, outCount;
1769     static int gotEof = 0;
1770
1771     /* Pass data read from player on to ICS */
1772     if (count > 0) {
1773         gotEof = 0;
1774         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1775         if (outCount < count) {
1776             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1777         }
1778     } else if (count < 0) {
1779         RemoveInputSource(isr);
1780         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1781     } else if (gotEof++ > 0) {
1782         RemoveInputSource(isr);
1783         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1784     }
1785 }
1786
1787 void
1788 KeepAlive()
1789 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1790     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1791     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1792     SendToICS("date\n");
1793     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1794 }
1795
1796 /* added routine for printf style output to ics */
1797 void ics_printf(char *format, ...)
1798 {
1799     char buffer[MSG_SIZ];
1800     va_list args;
1801
1802     va_start(args, format);
1803     vsnprintf(buffer, sizeof(buffer), format, args);
1804     buffer[sizeof(buffer)-1] = '\0';
1805     SendToICS(buffer);
1806     va_end(args);
1807 }
1808
1809 void
1810 SendToICS(s)
1811      char *s;
1812 {
1813     int count, outCount, outError;
1814
1815     if (icsPR == NULL) return;
1816
1817     count = strlen(s);
1818     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1819     if (outCount < count) {
1820         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1821     }
1822 }
1823
1824 /* This is used for sending logon scripts to the ICS. Sending
1825    without a delay causes problems when using timestamp on ICC
1826    (at least on my machine). */
1827 void
1828 SendToICSDelayed(s,msdelay)
1829      char *s;
1830      long msdelay;
1831 {
1832     int count, outCount, outError;
1833
1834     if (icsPR == NULL) return;
1835
1836     count = strlen(s);
1837     if (appData.debugMode) {
1838         fprintf(debugFP, ">ICS: ");
1839         show_bytes(debugFP, s, count);
1840         fprintf(debugFP, "\n");
1841     }
1842     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1843                                       msdelay);
1844     if (outCount < count) {
1845         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1846     }
1847 }
1848
1849
1850 /* Remove all highlighting escape sequences in s
1851    Also deletes any suffix starting with '('
1852    */
1853 char *
1854 StripHighlightAndTitle(s)
1855      char *s;
1856 {
1857     static char retbuf[MSG_SIZ];
1858     char *p = retbuf;
1859
1860     while (*s != NULLCHAR) {
1861         while (*s == '\033') {
1862             while (*s != NULLCHAR && !isalpha(*s)) s++;
1863             if (*s != NULLCHAR) s++;
1864         }
1865         while (*s != NULLCHAR && *s != '\033') {
1866             if (*s == '(' || *s == '[') {
1867                 *p = NULLCHAR;
1868                 return retbuf;
1869             }
1870             *p++ = *s++;
1871         }
1872     }
1873     *p = NULLCHAR;
1874     return retbuf;
1875 }
1876
1877 /* Remove all highlighting escape sequences in s */
1878 char *
1879 StripHighlight(s)
1880      char *s;
1881 {
1882     static char retbuf[MSG_SIZ];
1883     char *p = retbuf;
1884
1885     while (*s != NULLCHAR) {
1886         while (*s == '\033') {
1887             while (*s != NULLCHAR && !isalpha(*s)) s++;
1888             if (*s != NULLCHAR) s++;
1889         }
1890         while (*s != NULLCHAR && *s != '\033') {
1891             *p++ = *s++;
1892         }
1893     }
1894     *p = NULLCHAR;
1895     return retbuf;
1896 }
1897
1898 char *variantNames[] = VARIANT_NAMES;
1899 char *
1900 VariantName(v)
1901      VariantClass v;
1902 {
1903     return variantNames[v];
1904 }
1905
1906
1907 /* Identify a variant from the strings the chess servers use or the
1908    PGN Variant tag names we use. */
1909 VariantClass
1910 StringToVariant(e)
1911      char *e;
1912 {
1913     char *p;
1914     int wnum = -1;
1915     VariantClass v = VariantNormal;
1916     int i, found = FALSE;
1917     char buf[MSG_SIZ];
1918     int len;
1919
1920     if (!e) return v;
1921
1922     /* [HGM] skip over optional board-size prefixes */
1923     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1924         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1925         while( *e++ != '_');
1926     }
1927
1928     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1929         v = VariantNormal;
1930         found = TRUE;
1931     } else
1932     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1933       if (StrCaseStr(e, variantNames[i])) {
1934         v = (VariantClass) i;
1935         found = TRUE;
1936         break;
1937       }
1938     }
1939
1940     if (!found) {
1941       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1942           || StrCaseStr(e, "wild/fr")
1943           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1944         v = VariantFischeRandom;
1945       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1946                  (i = 1, p = StrCaseStr(e, "w"))) {
1947         p += i;
1948         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1949         if (isdigit(*p)) {
1950           wnum = atoi(p);
1951         } else {
1952           wnum = -1;
1953         }
1954         switch (wnum) {
1955         case 0: /* FICS only, actually */
1956         case 1:
1957           /* Castling legal even if K starts on d-file */
1958           v = VariantWildCastle;
1959           break;
1960         case 2:
1961         case 3:
1962         case 4:
1963           /* Castling illegal even if K & R happen to start in
1964              normal positions. */
1965           v = VariantNoCastle;
1966           break;
1967         case 5:
1968         case 7:
1969         case 8:
1970         case 10:
1971         case 11:
1972         case 12:
1973         case 13:
1974         case 14:
1975         case 15:
1976         case 18:
1977         case 19:
1978           /* Castling legal iff K & R start in normal positions */
1979           v = VariantNormal;
1980           break;
1981         case 6:
1982         case 20:
1983         case 21:
1984           /* Special wilds for position setup; unclear what to do here */
1985           v = VariantLoadable;
1986           break;
1987         case 9:
1988           /* Bizarre ICC game */
1989           v = VariantTwoKings;
1990           break;
1991         case 16:
1992           v = VariantKriegspiel;
1993           break;
1994         case 17:
1995           v = VariantLosers;
1996           break;
1997         case 22:
1998           v = VariantFischeRandom;
1999           break;
2000         case 23:
2001           v = VariantCrazyhouse;
2002           break;
2003         case 24:
2004           v = VariantBughouse;
2005           break;
2006         case 25:
2007           v = Variant3Check;
2008           break;
2009         case 26:
2010           /* Not quite the same as FICS suicide! */
2011           v = VariantGiveaway;
2012           break;
2013         case 27:
2014           v = VariantAtomic;
2015           break;
2016         case 28:
2017           v = VariantShatranj;
2018           break;
2019
2020         /* Temporary names for future ICC types.  The name *will* change in
2021            the next xboard/WinBoard release after ICC defines it. */
2022         case 29:
2023           v = Variant29;
2024           break;
2025         case 30:
2026           v = Variant30;
2027           break;
2028         case 31:
2029           v = Variant31;
2030           break;
2031         case 32:
2032           v = Variant32;
2033           break;
2034         case 33:
2035           v = Variant33;
2036           break;
2037         case 34:
2038           v = Variant34;
2039           break;
2040         case 35:
2041           v = Variant35;
2042           break;
2043         case 36:
2044           v = Variant36;
2045           break;
2046         case 37:
2047           v = VariantShogi;
2048           break;
2049         case 38:
2050           v = VariantXiangqi;
2051           break;
2052         case 39:
2053           v = VariantCourier;
2054           break;
2055         case 40:
2056           v = VariantGothic;
2057           break;
2058         case 41:
2059           v = VariantCapablanca;
2060           break;
2061         case 42:
2062           v = VariantKnightmate;
2063           break;
2064         case 43:
2065           v = VariantFairy;
2066           break;
2067         case 44:
2068           v = VariantCylinder;
2069           break;
2070         case 45:
2071           v = VariantFalcon;
2072           break;
2073         case 46:
2074           v = VariantCapaRandom;
2075           break;
2076         case 47:
2077           v = VariantBerolina;
2078           break;
2079         case 48:
2080           v = VariantJanus;
2081           break;
2082         case 49:
2083           v = VariantSuper;
2084           break;
2085         case 50:
2086           v = VariantGreat;
2087           break;
2088         case -1:
2089           /* Found "wild" or "w" in the string but no number;
2090              must assume it's normal chess. */
2091           v = VariantNormal;
2092           break;
2093         default:
2094           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2095           if( (len > MSG_SIZ) && appData.debugMode )
2096             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2097
2098           DisplayError(buf, 0);
2099           v = VariantUnknown;
2100           break;
2101         }
2102       }
2103     }
2104     if (appData.debugMode) {
2105       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2106               e, wnum, VariantName(v));
2107     }
2108     return v;
2109 }
2110
2111 static int leftover_start = 0, leftover_len = 0;
2112 char star_match[STAR_MATCH_N][MSG_SIZ];
2113
2114 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2115    advance *index beyond it, and set leftover_start to the new value of
2116    *index; else return FALSE.  If pattern contains the character '*', it
2117    matches any sequence of characters not containing '\r', '\n', or the
2118    character following the '*' (if any), and the matched sequence(s) are
2119    copied into star_match.
2120    */
2121 int
2122 looking_at(buf, index, pattern)
2123      char *buf;
2124      int *index;
2125      char *pattern;
2126 {
2127     char *bufp = &buf[*index], *patternp = pattern;
2128     int star_count = 0;
2129     char *matchp = star_match[0];
2130
2131     for (;;) {
2132         if (*patternp == NULLCHAR) {
2133             *index = leftover_start = bufp - buf;
2134             *matchp = NULLCHAR;
2135             return TRUE;
2136         }
2137         if (*bufp == NULLCHAR) return FALSE;
2138         if (*patternp == '*') {
2139             if (*bufp == *(patternp + 1)) {
2140                 *matchp = NULLCHAR;
2141                 matchp = star_match[++star_count];
2142                 patternp += 2;
2143                 bufp++;
2144                 continue;
2145             } else if (*bufp == '\n' || *bufp == '\r') {
2146                 patternp++;
2147                 if (*patternp == NULLCHAR)
2148                   continue;
2149                 else
2150                   return FALSE;
2151             } else {
2152                 *matchp++ = *bufp++;
2153                 continue;
2154             }
2155         }
2156         if (*patternp != *bufp) return FALSE;
2157         patternp++;
2158         bufp++;
2159     }
2160 }
2161
2162 void
2163 SendToPlayer(data, length)
2164      char *data;
2165      int length;
2166 {
2167     int error, outCount;
2168     outCount = OutputToProcess(NoProc, data, length, &error);
2169     if (outCount < length) {
2170         DisplayFatalError(_("Error writing to display"), error, 1);
2171     }
2172 }
2173
2174 void
2175 PackHolding(packed, holding)
2176      char packed[];
2177      char *holding;
2178 {
2179     char *p = holding;
2180     char *q = packed;
2181     int runlength = 0;
2182     int curr = 9999;
2183     do {
2184         if (*p == curr) {
2185             runlength++;
2186         } else {
2187             switch (runlength) {
2188               case 0:
2189                 break;
2190               case 1:
2191                 *q++ = curr;
2192                 break;
2193               case 2:
2194                 *q++ = curr;
2195                 *q++ = curr;
2196                 break;
2197               default:
2198                 sprintf(q, "%d", runlength);
2199                 while (*q) q++;
2200                 *q++ = curr;
2201                 break;
2202             }
2203             runlength = 1;
2204             curr = *p;
2205         }
2206     } while (*p++);
2207     *q = NULLCHAR;
2208 }
2209
2210 /* Telnet protocol requests from the front end */
2211 void
2212 TelnetRequest(ddww, option)
2213      unsigned char ddww, option;
2214 {
2215     unsigned char msg[3];
2216     int outCount, outError;
2217
2218     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2219
2220     if (appData.debugMode) {
2221         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2222         switch (ddww) {
2223           case TN_DO:
2224             ddwwStr = "DO";
2225             break;
2226           case TN_DONT:
2227             ddwwStr = "DONT";
2228             break;
2229           case TN_WILL:
2230             ddwwStr = "WILL";
2231             break;
2232           case TN_WONT:
2233             ddwwStr = "WONT";
2234             break;
2235           default:
2236             ddwwStr = buf1;
2237             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2238             break;
2239         }
2240         switch (option) {
2241           case TN_ECHO:
2242             optionStr = "ECHO";
2243             break;
2244           default:
2245             optionStr = buf2;
2246             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2247             break;
2248         }
2249         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2250     }
2251     msg[0] = TN_IAC;
2252     msg[1] = ddww;
2253     msg[2] = option;
2254     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2255     if (outCount < 3) {
2256         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2257     }
2258 }
2259
2260 void
2261 DoEcho()
2262 {
2263     if (!appData.icsActive) return;
2264     TelnetRequest(TN_DO, TN_ECHO);
2265 }
2266
2267 void
2268 DontEcho()
2269 {
2270     if (!appData.icsActive) return;
2271     TelnetRequest(TN_DONT, TN_ECHO);
2272 }
2273
2274 void
2275 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2276 {
2277     /* put the holdings sent to us by the server on the board holdings area */
2278     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2279     char p;
2280     ChessSquare piece;
2281
2282     if(gameInfo.holdingsWidth < 2)  return;
2283     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2284         return; // prevent overwriting by pre-board holdings
2285
2286     if( (int)lowestPiece >= BlackPawn ) {
2287         holdingsColumn = 0;
2288         countsColumn = 1;
2289         holdingsStartRow = BOARD_HEIGHT-1;
2290         direction = -1;
2291     } else {
2292         holdingsColumn = BOARD_WIDTH-1;
2293         countsColumn = BOARD_WIDTH-2;
2294         holdingsStartRow = 0;
2295         direction = 1;
2296     }
2297
2298     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2299         board[i][holdingsColumn] = EmptySquare;
2300         board[i][countsColumn]   = (ChessSquare) 0;
2301     }
2302     while( (p=*holdings++) != NULLCHAR ) {
2303         piece = CharToPiece( ToUpper(p) );
2304         if(piece == EmptySquare) continue;
2305         /*j = (int) piece - (int) WhitePawn;*/
2306         j = PieceToNumber(piece);
2307         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2308         if(j < 0) continue;               /* should not happen */
2309         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2310         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2311         board[holdingsStartRow+j*direction][countsColumn]++;
2312     }
2313 }
2314
2315
2316 void
2317 VariantSwitch(Board board, VariantClass newVariant)
2318 {
2319    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2320    static Board oldBoard;
2321
2322    startedFromPositionFile = FALSE;
2323    if(gameInfo.variant == newVariant) return;
2324
2325    /* [HGM] This routine is called each time an assignment is made to
2326     * gameInfo.variant during a game, to make sure the board sizes
2327     * are set to match the new variant. If that means adding or deleting
2328     * holdings, we shift the playing board accordingly
2329     * This kludge is needed because in ICS observe mode, we get boards
2330     * of an ongoing game without knowing the variant, and learn about the
2331     * latter only later. This can be because of the move list we requested,
2332     * in which case the game history is refilled from the beginning anyway,
2333     * but also when receiving holdings of a crazyhouse game. In the latter
2334     * case we want to add those holdings to the already received position.
2335     */
2336
2337
2338    if (appData.debugMode) {
2339      fprintf(debugFP, "Switch board from %s to %s\n",
2340              VariantName(gameInfo.variant), VariantName(newVariant));
2341      setbuf(debugFP, NULL);
2342    }
2343    shuffleOpenings = 0;       /* [HGM] shuffle */
2344    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2345    switch(newVariant)
2346      {
2347      case VariantShogi:
2348        newWidth = 9;  newHeight = 9;
2349        gameInfo.holdingsSize = 7;
2350      case VariantBughouse:
2351      case VariantCrazyhouse:
2352        newHoldingsWidth = 2; break;
2353      case VariantGreat:
2354        newWidth = 10;
2355      case VariantSuper:
2356        newHoldingsWidth = 2;
2357        gameInfo.holdingsSize = 8;
2358        break;
2359      case VariantGothic:
2360      case VariantCapablanca:
2361      case VariantCapaRandom:
2362        newWidth = 10;
2363      default:
2364        newHoldingsWidth = gameInfo.holdingsSize = 0;
2365      };
2366
2367    if(newWidth  != gameInfo.boardWidth  ||
2368       newHeight != gameInfo.boardHeight ||
2369       newHoldingsWidth != gameInfo.holdingsWidth ) {
2370
2371      /* shift position to new playing area, if needed */
2372      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2373        for(i=0; i<BOARD_HEIGHT; i++)
2374          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2375            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2376              board[i][j];
2377        for(i=0; i<newHeight; i++) {
2378          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2379          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2380        }
2381      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2382        for(i=0; i<BOARD_HEIGHT; i++)
2383          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2384            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2385              board[i][j];
2386      }
2387      gameInfo.boardWidth  = newWidth;
2388      gameInfo.boardHeight = newHeight;
2389      gameInfo.holdingsWidth = newHoldingsWidth;
2390      gameInfo.variant = newVariant;
2391      InitDrawingSizes(-2, 0);
2392    } else gameInfo.variant = newVariant;
2393    CopyBoard(oldBoard, board);   // remember correctly formatted board
2394      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2395    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2396 }
2397
2398 static int loggedOn = FALSE;
2399
2400 /*-- Game start info cache: --*/
2401 int gs_gamenum;
2402 char gs_kind[MSG_SIZ];
2403 static char player1Name[128] = "";
2404 static char player2Name[128] = "";
2405 static char cont_seq[] = "\n\\   ";
2406 static int player1Rating = -1;
2407 static int player2Rating = -1;
2408 /*----------------------------*/
2409
2410 ColorClass curColor = ColorNormal;
2411 int suppressKibitz = 0;
2412
2413 // [HGM] seekgraph
2414 Boolean soughtPending = FALSE;
2415 Boolean seekGraphUp;
2416 #define MAX_SEEK_ADS 200
2417 #define SQUARE 0x80
2418 char *seekAdList[MAX_SEEK_ADS];
2419 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2420 float tcList[MAX_SEEK_ADS];
2421 char colorList[MAX_SEEK_ADS];
2422 int nrOfSeekAds = 0;
2423 int minRating = 1010, maxRating = 2800;
2424 int hMargin = 10, vMargin = 20, h, w;
2425 extern int squareSize, lineGap;
2426
2427 void
2428 PlotSeekAd(int i)
2429 {
2430         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2431         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2432         if(r < minRating+100 && r >=0 ) r = minRating+100;
2433         if(r > maxRating) r = maxRating;
2434         if(tc < 1.) tc = 1.;
2435         if(tc > 95.) tc = 95.;
2436         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2437         y = ((double)r - minRating)/(maxRating - minRating)
2438             * (h-vMargin-squareSize/8-1) + vMargin;
2439         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2440         if(strstr(seekAdList[i], " u ")) color = 1;
2441         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2442            !strstr(seekAdList[i], "bullet") &&
2443            !strstr(seekAdList[i], "blitz") &&
2444            !strstr(seekAdList[i], "standard") ) color = 2;
2445         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2446         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2447 }
2448
2449 void
2450 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2451 {
2452         char buf[MSG_SIZ], *ext = "";
2453         VariantClass v = StringToVariant(type);
2454         if(strstr(type, "wild")) {
2455             ext = type + 4; // append wild number
2456             if(v == VariantFischeRandom) type = "chess960"; else
2457             if(v == VariantLoadable) type = "setup"; else
2458             type = VariantName(v);
2459         }
2460         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2461         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2462             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2463             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2464             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2465             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2466             seekNrList[nrOfSeekAds] = nr;
2467             zList[nrOfSeekAds] = 0;
2468             seekAdList[nrOfSeekAds++] = StrSave(buf);
2469             if(plot) PlotSeekAd(nrOfSeekAds-1);
2470         }
2471 }
2472
2473 void
2474 EraseSeekDot(int i)
2475 {
2476     int x = xList[i], y = yList[i], d=squareSize/4, k;
2477     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2478     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2479     // now replot every dot that overlapped
2480     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2481         int xx = xList[k], yy = yList[k];
2482         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2483             DrawSeekDot(xx, yy, colorList[k]);
2484     }
2485 }
2486
2487 void
2488 RemoveSeekAd(int nr)
2489 {
2490         int i;
2491         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2492             EraseSeekDot(i);
2493             if(seekAdList[i]) free(seekAdList[i]);
2494             seekAdList[i] = seekAdList[--nrOfSeekAds];
2495             seekNrList[i] = seekNrList[nrOfSeekAds];
2496             ratingList[i] = ratingList[nrOfSeekAds];
2497             colorList[i]  = colorList[nrOfSeekAds];
2498             tcList[i] = tcList[nrOfSeekAds];
2499             xList[i]  = xList[nrOfSeekAds];
2500             yList[i]  = yList[nrOfSeekAds];
2501             zList[i]  = zList[nrOfSeekAds];
2502             seekAdList[nrOfSeekAds] = NULL;
2503             break;
2504         }
2505 }
2506
2507 Boolean
2508 MatchSoughtLine(char *line)
2509 {
2510     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2511     int nr, base, inc, u=0; char dummy;
2512
2513     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2514        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2515        (u=1) &&
2516        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2517         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2518         // match: compact and save the line
2519         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2520         return TRUE;
2521     }
2522     return FALSE;
2523 }
2524
2525 int
2526 DrawSeekGraph()
2527 {
2528     int i;
2529     if(!seekGraphUp) return FALSE;
2530     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2531     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2532
2533     DrawSeekBackground(0, 0, w, h);
2534     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2535     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2536     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2537         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2538         yy = h-1-yy;
2539         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2540         if(i%500 == 0) {
2541             char buf[MSG_SIZ];
2542             snprintf(buf, MSG_SIZ, "%d", i);
2543             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2544         }
2545     }
2546     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2547     for(i=1; i<100; i+=(i<10?1:5)) {
2548         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2549         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2550         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2551             char buf[MSG_SIZ];
2552             snprintf(buf, MSG_SIZ, "%d", i);
2553             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2554         }
2555     }
2556     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2557     return TRUE;
2558 }
2559
2560 int SeekGraphClick(ClickType click, int x, int y, int moving)
2561 {
2562     static int lastDown = 0, displayed = 0, lastSecond;
2563     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2564         if(click == Release || moving) return FALSE;
2565         nrOfSeekAds = 0;
2566         soughtPending = TRUE;
2567         SendToICS(ics_prefix);
2568         SendToICS("sought\n"); // should this be "sought all"?
2569     } else { // issue challenge based on clicked ad
2570         int dist = 10000; int i, closest = 0, second = 0;
2571         for(i=0; i<nrOfSeekAds; i++) {
2572             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2573             if(d < dist) { dist = d; closest = i; }
2574             second += (d - zList[i] < 120); // count in-range ads
2575             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2576         }
2577         if(dist < 120) {
2578             char buf[MSG_SIZ];
2579             second = (second > 1);
2580             if(displayed != closest || second != lastSecond) {
2581                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2582                 lastSecond = second; displayed = closest;
2583             }
2584             if(click == Press) {
2585                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2586                 lastDown = closest;
2587                 return TRUE;
2588             } // on press 'hit', only show info
2589             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2590             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2591             SendToICS(ics_prefix);
2592             SendToICS(buf);
2593             return TRUE; // let incoming board of started game pop down the graph
2594         } else if(click == Release) { // release 'miss' is ignored
2595             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2596             if(moving == 2) { // right up-click
2597                 nrOfSeekAds = 0; // refresh graph
2598                 soughtPending = TRUE;
2599                 SendToICS(ics_prefix);
2600                 SendToICS("sought\n"); // should this be "sought all"?
2601             }
2602             return TRUE;
2603         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2604         // press miss or release hit 'pop down' seek graph
2605         seekGraphUp = FALSE;
2606         DrawPosition(TRUE, NULL);
2607     }
2608     return TRUE;
2609 }
2610
2611 void
2612 read_from_ics(isr, closure, data, count, error)
2613      InputSourceRef isr;
2614      VOIDSTAR closure;
2615      char *data;
2616      int count;
2617      int error;
2618 {
2619 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2620 #define STARTED_NONE 0
2621 #define STARTED_MOVES 1
2622 #define STARTED_BOARD 2
2623 #define STARTED_OBSERVE 3
2624 #define STARTED_HOLDINGS 4
2625 #define STARTED_CHATTER 5
2626 #define STARTED_COMMENT 6
2627 #define STARTED_MOVES_NOHIDE 7
2628
2629     static int started = STARTED_NONE;
2630     static char parse[20000];
2631     static int parse_pos = 0;
2632     static char buf[BUF_SIZE + 1];
2633     static int firstTime = TRUE, intfSet = FALSE;
2634     static ColorClass prevColor = ColorNormal;
2635     static int savingComment = FALSE;
2636     static int cmatch = 0; // continuation sequence match
2637     char *bp;
2638     char str[MSG_SIZ];
2639     int i, oldi;
2640     int buf_len;
2641     int next_out;
2642     int tkind;
2643     int backup;    /* [DM] For zippy color lines */
2644     char *p;
2645     char talker[MSG_SIZ]; // [HGM] chat
2646     int channel;
2647
2648     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2649
2650     if (appData.debugMode) {
2651       if (!error) {
2652         fprintf(debugFP, "<ICS: ");
2653         show_bytes(debugFP, data, count);
2654         fprintf(debugFP, "\n");
2655       }
2656     }
2657
2658     if (appData.debugMode) { int f = forwardMostMove;
2659         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2660                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2661                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2662     }
2663     if (count > 0) {
2664         /* If last read ended with a partial line that we couldn't parse,
2665            prepend it to the new read and try again. */
2666         if (leftover_len > 0) {
2667             for (i=0; i<leftover_len; i++)
2668               buf[i] = buf[leftover_start + i];
2669         }
2670
2671     /* copy new characters into the buffer */
2672     bp = buf + leftover_len;
2673     buf_len=leftover_len;
2674     for (i=0; i<count; i++)
2675     {
2676         // ignore these
2677         if (data[i] == '\r')
2678             continue;
2679
2680         // join lines split by ICS?
2681         if (!appData.noJoin)
2682         {
2683             /*
2684                 Joining just consists of finding matches against the
2685                 continuation sequence, and discarding that sequence
2686                 if found instead of copying it.  So, until a match
2687                 fails, there's nothing to do since it might be the
2688                 complete sequence, and thus, something we don't want
2689                 copied.
2690             */
2691             if (data[i] == cont_seq[cmatch])
2692             {
2693                 cmatch++;
2694                 if (cmatch == strlen(cont_seq))
2695                 {
2696                     cmatch = 0; // complete match.  just reset the counter
2697
2698                     /*
2699                         it's possible for the ICS to not include the space
2700                         at the end of the last word, making our [correct]
2701                         join operation fuse two separate words.  the server
2702                         does this when the space occurs at the width setting.
2703                     */
2704                     if (!buf_len || buf[buf_len-1] != ' ')
2705                     {
2706                         *bp++ = ' ';
2707                         buf_len++;
2708                     }
2709                 }
2710                 continue;
2711             }
2712             else if (cmatch)
2713             {
2714                 /*
2715                     match failed, so we have to copy what matched before
2716                     falling through and copying this character.  In reality,
2717                     this will only ever be just the newline character, but
2718                     it doesn't hurt to be precise.
2719                 */
2720                 strncpy(bp, cont_seq, cmatch);
2721                 bp += cmatch;
2722                 buf_len += cmatch;
2723                 cmatch = 0;
2724             }
2725         }
2726
2727         // copy this char
2728         *bp++ = data[i];
2729         buf_len++;
2730     }
2731
2732         buf[buf_len] = NULLCHAR;
2733 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2734         next_out = 0;
2735         leftover_start = 0;
2736
2737         i = 0;
2738         while (i < buf_len) {
2739             /* Deal with part of the TELNET option negotiation
2740                protocol.  We refuse to do anything beyond the
2741                defaults, except that we allow the WILL ECHO option,
2742                which ICS uses to turn off password echoing when we are
2743                directly connected to it.  We reject this option
2744                if localLineEditing mode is on (always on in xboard)
2745                and we are talking to port 23, which might be a real
2746                telnet server that will try to keep WILL ECHO on permanently.
2747              */
2748             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2749                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2750                 unsigned char option;
2751                 oldi = i;
2752                 switch ((unsigned char) buf[++i]) {
2753                   case TN_WILL:
2754                     if (appData.debugMode)
2755                       fprintf(debugFP, "\n<WILL ");
2756                     switch (option = (unsigned char) buf[++i]) {
2757                       case TN_ECHO:
2758                         if (appData.debugMode)
2759                           fprintf(debugFP, "ECHO ");
2760                         /* Reply only if this is a change, according
2761                            to the protocol rules. */
2762                         if (remoteEchoOption) break;
2763                         if (appData.localLineEditing &&
2764                             atoi(appData.icsPort) == TN_PORT) {
2765                             TelnetRequest(TN_DONT, TN_ECHO);
2766                         } else {
2767                             EchoOff();
2768                             TelnetRequest(TN_DO, TN_ECHO);
2769                             remoteEchoOption = TRUE;
2770                         }
2771                         break;
2772                       default:
2773                         if (appData.debugMode)
2774                           fprintf(debugFP, "%d ", option);
2775                         /* Whatever this is, we don't want it. */
2776                         TelnetRequest(TN_DONT, option);
2777                         break;
2778                     }
2779                     break;
2780                   case TN_WONT:
2781                     if (appData.debugMode)
2782                       fprintf(debugFP, "\n<WONT ");
2783                     switch (option = (unsigned char) buf[++i]) {
2784                       case TN_ECHO:
2785                         if (appData.debugMode)
2786                           fprintf(debugFP, "ECHO ");
2787                         /* Reply only if this is a change, according
2788                            to the protocol rules. */
2789                         if (!remoteEchoOption) break;
2790                         EchoOn();
2791                         TelnetRequest(TN_DONT, TN_ECHO);
2792                         remoteEchoOption = FALSE;
2793                         break;
2794                       default:
2795                         if (appData.debugMode)
2796                           fprintf(debugFP, "%d ", (unsigned char) option);
2797                         /* Whatever this is, it must already be turned
2798                            off, because we never agree to turn on
2799                            anything non-default, so according to the
2800                            protocol rules, we don't reply. */
2801                         break;
2802                     }
2803                     break;
2804                   case TN_DO:
2805                     if (appData.debugMode)
2806                       fprintf(debugFP, "\n<DO ");
2807                     switch (option = (unsigned char) buf[++i]) {
2808                       default:
2809                         /* Whatever this is, we refuse to do it. */
2810                         if (appData.debugMode)
2811                           fprintf(debugFP, "%d ", option);
2812                         TelnetRequest(TN_WONT, option);
2813                         break;
2814                     }
2815                     break;
2816                   case TN_DONT:
2817                     if (appData.debugMode)
2818                       fprintf(debugFP, "\n<DONT ");
2819                     switch (option = (unsigned char) buf[++i]) {
2820                       default:
2821                         if (appData.debugMode)
2822                           fprintf(debugFP, "%d ", option);
2823                         /* Whatever this is, we are already not doing
2824                            it, because we never agree to do anything
2825                            non-default, so according to the protocol
2826                            rules, we don't reply. */
2827                         break;
2828                     }
2829                     break;
2830                   case TN_IAC:
2831                     if (appData.debugMode)
2832                       fprintf(debugFP, "\n<IAC ");
2833                     /* Doubled IAC; pass it through */
2834                     i--;
2835                     break;
2836                   default:
2837                     if (appData.debugMode)
2838                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2839                     /* Drop all other telnet commands on the floor */
2840                     break;
2841                 }
2842                 if (oldi > next_out)
2843                   SendToPlayer(&buf[next_out], oldi - next_out);
2844                 if (++i > next_out)
2845                   next_out = i;
2846                 continue;
2847             }
2848
2849             /* OK, this at least will *usually* work */
2850             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2851                 loggedOn = TRUE;
2852             }
2853
2854             if (loggedOn && !intfSet) {
2855                 if (ics_type == ICS_ICC) {
2856                   snprintf(str, MSG_SIZ,
2857                           "/set-quietly interface %s\n/set-quietly style 12\n",
2858                           programVersion);
2859                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2860                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2861                 } else if (ics_type == ICS_CHESSNET) {
2862                   snprintf(str, MSG_SIZ, "/style 12\n");
2863                 } else {
2864                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2865                   strcat(str, programVersion);
2866                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2867                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2868                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2869 #ifdef WIN32
2870                   strcat(str, "$iset nohighlight 1\n");
2871 #endif
2872                   strcat(str, "$iset lock 1\n$style 12\n");
2873                 }
2874                 SendToICS(str);
2875                 NotifyFrontendLogin();
2876                 intfSet = TRUE;
2877             }
2878
2879             if (started == STARTED_COMMENT) {
2880                 /* Accumulate characters in comment */
2881                 parse[parse_pos++] = buf[i];
2882                 if (buf[i] == '\n') {
2883                     parse[parse_pos] = NULLCHAR;
2884                     if(chattingPartner>=0) {
2885                         char mess[MSG_SIZ];
2886                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2887                         OutputChatMessage(chattingPartner, mess);
2888                         chattingPartner = -1;
2889                         next_out = i+1; // [HGM] suppress printing in ICS window
2890                     } else
2891                     if(!suppressKibitz) // [HGM] kibitz
2892                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2893                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2894                         int nrDigit = 0, nrAlph = 0, j;
2895                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2896                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2897                         parse[parse_pos] = NULLCHAR;
2898                         // try to be smart: if it does not look like search info, it should go to
2899                         // ICS interaction window after all, not to engine-output window.
2900                         for(j=0; j<parse_pos; j++) { // count letters and digits
2901                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2902                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2903                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2904                         }
2905                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2906                             int depth=0; float score;
2907                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2908                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2909                                 pvInfoList[forwardMostMove-1].depth = depth;
2910                                 pvInfoList[forwardMostMove-1].score = 100*score;
2911                             }
2912                             OutputKibitz(suppressKibitz, parse);
2913                         } else {
2914                             char tmp[MSG_SIZ];
2915                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2916                             SendToPlayer(tmp, strlen(tmp));
2917                         }
2918                         next_out = i+1; // [HGM] suppress printing in ICS window
2919                     }
2920                     started = STARTED_NONE;
2921                 } else {
2922                     /* Don't match patterns against characters in comment */
2923                     i++;
2924                     continue;
2925                 }
2926             }
2927             if (started == STARTED_CHATTER) {
2928                 if (buf[i] != '\n') {
2929                     /* Don't match patterns against characters in chatter */
2930                     i++;
2931                     continue;
2932                 }
2933                 started = STARTED_NONE;
2934                 if(suppressKibitz) next_out = i+1;
2935             }
2936
2937             /* Kludge to deal with rcmd protocol */
2938             if (firstTime && looking_at(buf, &i, "\001*")) {
2939                 DisplayFatalError(&buf[1], 0, 1);
2940                 continue;
2941             } else {
2942                 firstTime = FALSE;
2943             }
2944
2945             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2946                 ics_type = ICS_ICC;
2947                 ics_prefix = "/";
2948                 if (appData.debugMode)
2949                   fprintf(debugFP, "ics_type %d\n", ics_type);
2950                 continue;
2951             }
2952             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2953                 ics_type = ICS_FICS;
2954                 ics_prefix = "$";
2955                 if (appData.debugMode)
2956                   fprintf(debugFP, "ics_type %d\n", ics_type);
2957                 continue;
2958             }
2959             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2960                 ics_type = ICS_CHESSNET;
2961                 ics_prefix = "/";
2962                 if (appData.debugMode)
2963                   fprintf(debugFP, "ics_type %d\n", ics_type);
2964                 continue;
2965             }
2966
2967             if (!loggedOn &&
2968                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2969                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2970                  looking_at(buf, &i, "will be \"*\""))) {
2971               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2972               continue;
2973             }
2974
2975             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2976               char buf[MSG_SIZ];
2977               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2978               DisplayIcsInteractionTitle(buf);
2979               have_set_title = TRUE;
2980             }
2981
2982             /* skip finger notes */
2983             if (started == STARTED_NONE &&
2984                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2985                  (buf[i] == '1' && buf[i+1] == '0')) &&
2986                 buf[i+2] == ':' && buf[i+3] == ' ') {
2987               started = STARTED_CHATTER;
2988               i += 3;
2989               continue;
2990             }
2991
2992             oldi = i;
2993             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2994             if(appData.seekGraph) {
2995                 if(soughtPending && MatchSoughtLine(buf+i)) {
2996                     i = strstr(buf+i, "rated") - buf;
2997                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2998                     next_out = leftover_start = i;
2999                     started = STARTED_CHATTER;
3000                     suppressKibitz = TRUE;
3001                     continue;
3002                 }
3003                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3004                         && looking_at(buf, &i, "* ads displayed")) {
3005                     soughtPending = FALSE;
3006                     seekGraphUp = TRUE;
3007                     DrawSeekGraph();
3008                     continue;
3009                 }
3010                 if(appData.autoRefresh) {
3011                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3012                         int s = (ics_type == ICS_ICC); // ICC format differs
3013                         if(seekGraphUp)
3014                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3015                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3016                         looking_at(buf, &i, "*% "); // eat prompt
3017                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3018                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3019                         next_out = i; // suppress
3020                         continue;
3021                     }
3022                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3023                         char *p = star_match[0];
3024                         while(*p) {
3025                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3026                             while(*p && *p++ != ' '); // next
3027                         }
3028                         looking_at(buf, &i, "*% "); // eat prompt
3029                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3030                         next_out = i;
3031                         continue;
3032                     }
3033                 }
3034             }
3035
3036             /* skip formula vars */
3037             if (started == STARTED_NONE &&
3038                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3039               started = STARTED_CHATTER;
3040               i += 3;
3041               continue;
3042             }
3043
3044             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3045             if (appData.autoKibitz && started == STARTED_NONE &&
3046                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3047                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3048                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3049                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3050                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3051                         suppressKibitz = TRUE;
3052                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3053                         next_out = i;
3054                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3055                                 && (gameMode == IcsPlayingWhite)) ||
3056                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3057                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3058                             started = STARTED_CHATTER; // own kibitz we simply discard
3059                         else {
3060                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3061                             parse_pos = 0; parse[0] = NULLCHAR;
3062                             savingComment = TRUE;
3063                             suppressKibitz = gameMode != IcsObserving ? 2 :
3064                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3065                         }
3066                         continue;
3067                 } else
3068                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3069                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3070                          && atoi(star_match[0])) {
3071                     // suppress the acknowledgements of our own autoKibitz
3072                     char *p;
3073                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3075                     SendToPlayer(star_match[0], strlen(star_match[0]));
3076                     if(looking_at(buf, &i, "*% ")) // eat prompt
3077                         suppressKibitz = FALSE;
3078                     next_out = i;
3079                     continue;
3080                 }
3081             } // [HGM] kibitz: end of patch
3082
3083             // [HGM] chat: intercept tells by users for which we have an open chat window
3084             channel = -1;
3085             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3086                                            looking_at(buf, &i, "* whispers:") ||
3087                                            looking_at(buf, &i, "* kibitzes:") ||
3088                                            looking_at(buf, &i, "* shouts:") ||
3089                                            looking_at(buf, &i, "* c-shouts:") ||
3090                                            looking_at(buf, &i, "--> * ") ||
3091                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3092                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3093                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3094                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3095                 int p;
3096                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3097                 chattingPartner = -1;
3098
3099                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3100                 for(p=0; p<MAX_CHAT; p++) {
3101                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3102                     talker[0] = '['; strcat(talker, "] ");
3103                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3104                     chattingPartner = p; break;
3105                     }
3106                 } else
3107                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3108                 for(p=0; p<MAX_CHAT; p++) {
3109                     if(!strcmp("kibitzes", chatPartner[p])) {
3110                         talker[0] = '['; strcat(talker, "] ");
3111                         chattingPartner = p; break;
3112                     }
3113                 } else
3114                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3115                 for(p=0; p<MAX_CHAT; p++) {
3116                     if(!strcmp("whispers", chatPartner[p])) {
3117                         talker[0] = '['; strcat(talker, "] ");
3118                         chattingPartner = p; break;
3119                     }
3120                 } else
3121                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3122                   if(buf[i-8] == '-' && buf[i-3] == 't')
3123                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3124                     if(!strcmp("c-shouts", chatPartner[p])) {
3125                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3126                         chattingPartner = p; break;
3127                     }
3128                   }
3129                   if(chattingPartner < 0)
3130                   for(p=0; p<MAX_CHAT; p++) {
3131                     if(!strcmp("shouts", chatPartner[p])) {
3132                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3133                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3134                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3135                         chattingPartner = p; break;
3136                     }
3137                   }
3138                 }
3139                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3140                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3141                     talker[0] = 0; Colorize(ColorTell, FALSE);
3142                     chattingPartner = p; break;
3143                 }
3144                 if(chattingPartner<0) i = oldi; else {
3145                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3146                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3147                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3148                     started = STARTED_COMMENT;
3149                     parse_pos = 0; parse[0] = NULLCHAR;
3150                     savingComment = 3 + chattingPartner; // counts as TRUE
3151                     suppressKibitz = TRUE;
3152                     continue;
3153                 }
3154             } // [HGM] chat: end of patch
3155
3156           backup = i;
3157             if (appData.zippyTalk || appData.zippyPlay) {
3158                 /* [DM] Backup address for color zippy lines */
3159 #if ZIPPY
3160                if (loggedOn == TRUE)
3161                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3162                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3163 #endif
3164             } // [DM] 'else { ' deleted
3165                 if (
3166                     /* Regular tells and says */
3167                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3168                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3169                     looking_at(buf, &i, "* says: ") ||
3170                     /* Don't color "message" or "messages" output */
3171                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3172                     looking_at(buf, &i, "*. * at *:*: ") ||
3173                     looking_at(buf, &i, "--* (*:*): ") ||
3174                     /* Message notifications (same color as tells) */
3175                     looking_at(buf, &i, "* has left a message ") ||
3176                     looking_at(buf, &i, "* just sent you a message:\n") ||
3177                     /* Whispers and kibitzes */
3178                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3179                     looking_at(buf, &i, "* kibitzes: ") ||
3180                     /* Channel tells */
3181                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3182
3183                   if (tkind == 1 && strchr(star_match[0], ':')) {
3184                       /* Avoid "tells you:" spoofs in channels */
3185                      tkind = 3;
3186                   }
3187                   if (star_match[0][0] == NULLCHAR ||
3188                       strchr(star_match[0], ' ') ||
3189                       (tkind == 3 && strchr(star_match[1], ' '))) {
3190                     /* Reject bogus matches */
3191                     i = oldi;
3192                   } else {
3193                     if (appData.colorize) {
3194                       if (oldi > next_out) {
3195                         SendToPlayer(&buf[next_out], oldi - next_out);
3196                         next_out = oldi;
3197                       }
3198                       switch (tkind) {
3199                       case 1:
3200                         Colorize(ColorTell, FALSE);
3201                         curColor = ColorTell;
3202                         break;
3203                       case 2:
3204                         Colorize(ColorKibitz, FALSE);
3205                         curColor = ColorKibitz;
3206                         break;
3207                       case 3:
3208                         p = strrchr(star_match[1], '(');
3209                         if (p == NULL) {
3210                           p = star_match[1];
3211                         } else {
3212                           p++;
3213                         }
3214                         if (atoi(p) == 1) {
3215                           Colorize(ColorChannel1, FALSE);
3216                           curColor = ColorChannel1;
3217                         } else {
3218                           Colorize(ColorChannel, FALSE);
3219                           curColor = ColorChannel;
3220                         }
3221                         break;
3222                       case 5:
3223                         curColor = ColorNormal;
3224                         break;
3225                       }
3226                     }
3227                     if (started == STARTED_NONE && appData.autoComment &&
3228                         (gameMode == IcsObserving ||
3229                          gameMode == IcsPlayingWhite ||
3230                          gameMode == IcsPlayingBlack)) {
3231                       parse_pos = i - oldi;
3232                       memcpy(parse, &buf[oldi], parse_pos);
3233                       parse[parse_pos] = NULLCHAR;
3234                       started = STARTED_COMMENT;
3235                       savingComment = TRUE;
3236                     } else {
3237                       started = STARTED_CHATTER;
3238                       savingComment = FALSE;
3239                     }
3240                     loggedOn = TRUE;
3241                     continue;
3242                   }
3243                 }
3244
3245                 if (looking_at(buf, &i, "* s-shouts: ") ||
3246                     looking_at(buf, &i, "* c-shouts: ")) {
3247                     if (appData.colorize) {
3248                         if (oldi > next_out) {
3249                             SendToPlayer(&buf[next_out], oldi - next_out);
3250                             next_out = oldi;
3251                         }
3252                         Colorize(ColorSShout, FALSE);
3253                         curColor = ColorSShout;
3254                     }
3255                     loggedOn = TRUE;
3256                     started = STARTED_CHATTER;
3257                     continue;
3258                 }
3259
3260                 if (looking_at(buf, &i, "--->")) {
3261                     loggedOn = TRUE;
3262                     continue;
3263                 }
3264
3265                 if (looking_at(buf, &i, "* shouts: ") ||
3266                     looking_at(buf, &i, "--> ")) {
3267                     if (appData.colorize) {
3268                         if (oldi > next_out) {
3269                             SendToPlayer(&buf[next_out], oldi - next_out);
3270                             next_out = oldi;
3271                         }
3272                         Colorize(ColorShout, FALSE);
3273                         curColor = ColorShout;
3274                     }
3275                     loggedOn = TRUE;
3276                     started = STARTED_CHATTER;
3277                     continue;
3278                 }
3279
3280                 if (looking_at( buf, &i, "Challenge:")) {
3281                     if (appData.colorize) {
3282                         if (oldi > next_out) {
3283                             SendToPlayer(&buf[next_out], oldi - next_out);
3284                             next_out = oldi;
3285                         }
3286                         Colorize(ColorChallenge, FALSE);
3287                         curColor = ColorChallenge;
3288                     }
3289                     loggedOn = TRUE;
3290                     continue;
3291                 }
3292
3293                 if (looking_at(buf, &i, "* offers you") ||
3294                     looking_at(buf, &i, "* offers to be") ||
3295                     looking_at(buf, &i, "* would like to") ||
3296                     looking_at(buf, &i, "* requests to") ||
3297                     looking_at(buf, &i, "Your opponent offers") ||
3298                     looking_at(buf, &i, "Your opponent requests")) {
3299
3300                     if (appData.colorize) {
3301                         if (oldi > next_out) {
3302                             SendToPlayer(&buf[next_out], oldi - next_out);
3303                             next_out = oldi;
3304                         }
3305                         Colorize(ColorRequest, FALSE);
3306                         curColor = ColorRequest;
3307                     }
3308                     continue;
3309                 }
3310
3311                 if (looking_at(buf, &i, "* (*) seeking")) {
3312                     if (appData.colorize) {
3313                         if (oldi > next_out) {
3314                             SendToPlayer(&buf[next_out], oldi - next_out);
3315                             next_out = oldi;
3316                         }
3317                         Colorize(ColorSeek, FALSE);
3318                         curColor = ColorSeek;
3319                     }
3320                     continue;
3321             }
3322
3323           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3324
3325             if (looking_at(buf, &i, "\\   ")) {
3326                 if (prevColor != ColorNormal) {
3327                     if (oldi > next_out) {
3328                         SendToPlayer(&buf[next_out], oldi - next_out);
3329                         next_out = oldi;
3330                     }
3331                     Colorize(prevColor, TRUE);
3332                     curColor = prevColor;
3333                 }
3334                 if (savingComment) {
3335                     parse_pos = i - oldi;
3336                     memcpy(parse, &buf[oldi], parse_pos);
3337                     parse[parse_pos] = NULLCHAR;
3338                     started = STARTED_COMMENT;
3339                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3340                         chattingPartner = savingComment - 3; // kludge to remember the box
3341                 } else {
3342                     started = STARTED_CHATTER;
3343                 }
3344                 continue;
3345             }
3346
3347             if (looking_at(buf, &i, "Black Strength :") ||
3348                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3349                 looking_at(buf, &i, "<10>") ||
3350                 looking_at(buf, &i, "#@#")) {
3351                 /* Wrong board style */
3352                 loggedOn = TRUE;
3353                 SendToICS(ics_prefix);
3354                 SendToICS("set style 12\n");
3355                 SendToICS(ics_prefix);
3356                 SendToICS("refresh\n");
3357                 continue;
3358             }
3359
3360             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3361                 ICSInitScript();
3362                 have_sent_ICS_logon = 1;
3363                 continue;
3364             }
3365
3366             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3367                 (looking_at(buf, &i, "\n<12> ") ||
3368                  looking_at(buf, &i, "<12> "))) {
3369                 loggedOn = TRUE;
3370                 if (oldi > next_out) {
3371                     SendToPlayer(&buf[next_out], oldi - next_out);
3372                 }
3373                 next_out = i;
3374                 started = STARTED_BOARD;
3375                 parse_pos = 0;
3376                 continue;
3377             }
3378
3379             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3380                 looking_at(buf, &i, "<b1> ")) {
3381                 if (oldi > next_out) {
3382                     SendToPlayer(&buf[next_out], oldi - next_out);
3383                 }
3384                 next_out = i;
3385                 started = STARTED_HOLDINGS;
3386                 parse_pos = 0;
3387                 continue;
3388             }
3389
3390             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3391                 loggedOn = TRUE;
3392                 /* Header for a move list -- first line */
3393
3394                 switch (ics_getting_history) {
3395                   case H_FALSE:
3396                     switch (gameMode) {
3397                       case IcsIdle:
3398                       case BeginningOfGame:
3399                         /* User typed "moves" or "oldmoves" while we
3400                            were idle.  Pretend we asked for these
3401                            moves and soak them up so user can step
3402                            through them and/or save them.
3403                            */
3404                         Reset(FALSE, TRUE);
3405                         gameMode = IcsObserving;
3406                         ModeHighlight();
3407                         ics_gamenum = -1;
3408                         ics_getting_history = H_GOT_UNREQ_HEADER;
3409                         break;
3410                       case EditGame: /*?*/
3411                       case EditPosition: /*?*/
3412                         /* Should above feature work in these modes too? */
3413                         /* For now it doesn't */
3414                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3415                         break;
3416                       default:
3417                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3418                         break;
3419                     }
3420                     break;
3421                   case H_REQUESTED:
3422                     /* Is this the right one? */
3423                     if (gameInfo.white && gameInfo.black &&
3424                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3425                         strcmp(gameInfo.black, star_match[2]) == 0) {
3426                         /* All is well */
3427                         ics_getting_history = H_GOT_REQ_HEADER;
3428                     }
3429                     break;
3430                   case H_GOT_REQ_HEADER:
3431                   case H_GOT_UNREQ_HEADER:
3432                   case H_GOT_UNWANTED_HEADER:
3433                   case H_GETTING_MOVES:
3434                     /* Should not happen */
3435                     DisplayError(_("Error gathering move list: two headers"), 0);
3436                     ics_getting_history = H_FALSE;
3437                     break;
3438                 }
3439
3440                 /* Save player ratings into gameInfo if needed */
3441                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3442                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3443                     (gameInfo.whiteRating == -1 ||
3444                      gameInfo.blackRating == -1)) {
3445
3446                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3447                     gameInfo.blackRating = string_to_rating(star_match[3]);
3448                     if (appData.debugMode)
3449                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3450                               gameInfo.whiteRating, gameInfo.blackRating);
3451                 }
3452                 continue;
3453             }
3454
3455             if (looking_at(buf, &i,
3456               "* * match, initial time: * minute*, increment: * second")) {
3457                 /* Header for a move list -- second line */
3458                 /* Initial board will follow if this is a wild game */
3459                 if (gameInfo.event != NULL) free(gameInfo.event);
3460                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3461                 gameInfo.event = StrSave(str);
3462                 /* [HGM] we switched variant. Translate boards if needed. */
3463                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3464                 continue;
3465             }
3466
3467             if (looking_at(buf, &i, "Move  ")) {
3468                 /* Beginning of a move list */
3469                 switch (ics_getting_history) {
3470                   case H_FALSE:
3471                     /* Normally should not happen */
3472                     /* Maybe user hit reset while we were parsing */
3473                     break;
3474                   case H_REQUESTED:
3475                     /* Happens if we are ignoring a move list that is not
3476                      * the one we just requested.  Common if the user
3477                      * tries to observe two games without turning off
3478                      * getMoveList */
3479                     break;
3480                   case H_GETTING_MOVES:
3481                     /* Should not happen */
3482                     DisplayError(_("Error gathering move list: nested"), 0);
3483                     ics_getting_history = H_FALSE;
3484                     break;
3485                   case H_GOT_REQ_HEADER:
3486                     ics_getting_history = H_GETTING_MOVES;
3487                     started = STARTED_MOVES;
3488                     parse_pos = 0;
3489                     if (oldi > next_out) {
3490                         SendToPlayer(&buf[next_out], oldi - next_out);
3491                     }
3492                     break;
3493                   case H_GOT_UNREQ_HEADER:
3494                     ics_getting_history = H_GETTING_MOVES;
3495                     started = STARTED_MOVES_NOHIDE;
3496                     parse_pos = 0;
3497                     break;
3498                   case H_GOT_UNWANTED_HEADER:
3499                     ics_getting_history = H_FALSE;
3500                     break;
3501                 }
3502                 continue;
3503             }
3504
3505             if (looking_at(buf, &i, "% ") ||
3506                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3507                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3508                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3509                     soughtPending = FALSE;
3510                     seekGraphUp = TRUE;
3511                     DrawSeekGraph();
3512                 }
3513                 if(suppressKibitz) next_out = i;
3514                 savingComment = FALSE;
3515                 suppressKibitz = 0;
3516                 switch (started) {
3517                   case STARTED_MOVES:
3518                   case STARTED_MOVES_NOHIDE:
3519                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3520                     parse[parse_pos + i - oldi] = NULLCHAR;
3521                     ParseGameHistory(parse);
3522 #if ZIPPY
3523                     if (appData.zippyPlay && first.initDone) {
3524                         FeedMovesToProgram(&first, forwardMostMove);
3525                         if (gameMode == IcsPlayingWhite) {
3526                             if (WhiteOnMove(forwardMostMove)) {
3527                                 if (first.sendTime) {
3528                                   if (first.useColors) {
3529                                     SendToProgram("black\n", &first);
3530                                   }
3531                                   SendTimeRemaining(&first, TRUE);
3532                                 }
3533                                 if (first.useColors) {
3534                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3535                                 }
3536                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3537                                 first.maybeThinking = TRUE;
3538                             } else {
3539                                 if (first.usePlayother) {
3540                                   if (first.sendTime) {
3541                                     SendTimeRemaining(&first, TRUE);
3542                                   }
3543                                   SendToProgram("playother\n", &first);
3544                                   firstMove = FALSE;
3545                                 } else {
3546                                   firstMove = TRUE;
3547                                 }
3548                             }
3549                         } else if (gameMode == IcsPlayingBlack) {
3550                             if (!WhiteOnMove(forwardMostMove)) {
3551                                 if (first.sendTime) {
3552                                   if (first.useColors) {
3553                                     SendToProgram("white\n", &first);
3554                                   }
3555                                   SendTimeRemaining(&first, FALSE);
3556                                 }
3557                                 if (first.useColors) {
3558                                   SendToProgram("black\n", &first);
3559                                 }
3560                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3561                                 first.maybeThinking = TRUE;
3562                             } else {
3563                                 if (first.usePlayother) {
3564                                   if (first.sendTime) {
3565                                     SendTimeRemaining(&first, FALSE);
3566                                   }
3567                                   SendToProgram("playother\n", &first);
3568                                   firstMove = FALSE;
3569                                 } else {
3570                                   firstMove = TRUE;
3571                                 }
3572                             }
3573                         }
3574                     }
3575 #endif
3576                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3577                         /* Moves came from oldmoves or moves command
3578                            while we weren't doing anything else.
3579                            */
3580                         currentMove = forwardMostMove;
3581                         ClearHighlights();/*!!could figure this out*/
3582                         flipView = appData.flipView;
3583                         DrawPosition(TRUE, boards[currentMove]);
3584                         DisplayBothClocks();
3585                         snprintf(str, MSG_SIZ, "%s vs. %s",
3586                                 gameInfo.white, gameInfo.black);
3587                         DisplayTitle(str);
3588                         gameMode = IcsIdle;
3589                     } else {
3590                         /* Moves were history of an active game */
3591                         if (gameInfo.resultDetails != NULL) {
3592                             free(gameInfo.resultDetails);
3593                             gameInfo.resultDetails = NULL;
3594                         }
3595                     }
3596                     HistorySet(parseList, backwardMostMove,
3597                                forwardMostMove, currentMove-1);
3598                     DisplayMove(currentMove - 1);
3599                     if (started == STARTED_MOVES) next_out = i;
3600                     started = STARTED_NONE;
3601                     ics_getting_history = H_FALSE;
3602                     break;
3603
3604                   case STARTED_OBSERVE:
3605                     started = STARTED_NONE;
3606                     SendToICS(ics_prefix);
3607                     SendToICS("refresh\n");
3608                     break;
3609
3610                   default:
3611                     break;
3612                 }
3613                 if(bookHit) { // [HGM] book: simulate book reply
3614                     static char bookMove[MSG_SIZ]; // a bit generous?
3615
3616                     programStats.nodes = programStats.depth = programStats.time =
3617                     programStats.score = programStats.got_only_move = 0;
3618                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3619
3620                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3621                     strcat(bookMove, bookHit);
3622                     HandleMachineMove(bookMove, &first);
3623                 }
3624                 continue;
3625             }
3626
3627             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3628                  started == STARTED_HOLDINGS ||
3629                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3630                 /* Accumulate characters in move list or board */
3631                 parse[parse_pos++] = buf[i];
3632             }
3633
3634             /* Start of game messages.  Mostly we detect start of game
3635                when the first board image arrives.  On some versions
3636                of the ICS, though, we need to do a "refresh" after starting
3637                to observe in order to get the current board right away. */
3638             if (looking_at(buf, &i, "Adding game * to observation list")) {
3639                 started = STARTED_OBSERVE;
3640                 continue;
3641             }
3642
3643             /* Handle auto-observe */
3644             if (appData.autoObserve &&
3645                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3646                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3647                 char *player;
3648                 /* Choose the player that was highlighted, if any. */
3649                 if (star_match[0][0] == '\033' ||
3650                     star_match[1][0] != '\033') {
3651                     player = star_match[0];
3652                 } else {
3653                     player = star_match[2];
3654                 }
3655                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3656                         ics_prefix, StripHighlightAndTitle(player));
3657                 SendToICS(str);
3658
3659                 /* Save ratings from notify string */
3660                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3661                 player1Rating = string_to_rating(star_match[1]);
3662                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3663                 player2Rating = string_to_rating(star_match[3]);
3664
3665                 if (appData.debugMode)
3666                   fprintf(debugFP,
3667                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3668                           player1Name, player1Rating,
3669                           player2Name, player2Rating);
3670
3671                 continue;
3672             }
3673
3674             /* Deal with automatic examine mode after a game,
3675                and with IcsObserving -> IcsExamining transition */
3676             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3677                 looking_at(buf, &i, "has made you an examiner of game *")) {
3678
3679                 int gamenum = atoi(star_match[0]);
3680                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3681                     gamenum == ics_gamenum) {
3682                     /* We were already playing or observing this game;
3683                        no need to refetch history */
3684                     gameMode = IcsExamining;
3685                     if (pausing) {
3686                         pauseExamForwardMostMove = forwardMostMove;
3687                     } else if (currentMove < forwardMostMove) {
3688                         ForwardInner(forwardMostMove);
3689                     }
3690                 } else {
3691                     /* I don't think this case really can happen */
3692                     SendToICS(ics_prefix);
3693                     SendToICS("refresh\n");
3694                 }
3695                 continue;
3696             }
3697
3698             /* Error messages */
3699 //          if (ics_user_moved) {
3700             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3701                 if (looking_at(buf, &i, "Illegal move") ||
3702                     looking_at(buf, &i, "Not a legal move") ||
3703                     looking_at(buf, &i, "Your king is in check") ||
3704                     looking_at(buf, &i, "It isn't your turn") ||
3705                     looking_at(buf, &i, "It is not your move")) {
3706                     /* Illegal move */
3707                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3708                         currentMove = forwardMostMove-1;
3709                         DisplayMove(currentMove - 1); /* before DMError */
3710                         DrawPosition(FALSE, boards[currentMove]);
3711                         SwitchClocks(forwardMostMove-1); // [HGM] race
3712                         DisplayBothClocks();
3713                     }
3714                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3715                     ics_user_moved = 0;
3716                     continue;
3717                 }
3718             }
3719
3720             if (looking_at(buf, &i, "still have time") ||
3721                 looking_at(buf, &i, "not out of time") ||
3722                 looking_at(buf, &i, "either player is out of time") ||
3723                 looking_at(buf, &i, "has timeseal; checking")) {
3724                 /* We must have called his flag a little too soon */
3725                 whiteFlag = blackFlag = FALSE;
3726                 continue;
3727             }
3728
3729             if (looking_at(buf, &i, "added * seconds to") ||
3730                 looking_at(buf, &i, "seconds were added to")) {
3731                 /* Update the clocks */
3732                 SendToICS(ics_prefix);
3733                 SendToICS("refresh\n");
3734                 continue;
3735             }
3736
3737             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3738                 ics_clock_paused = TRUE;
3739                 StopClocks();
3740                 continue;
3741             }
3742
3743             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3744                 ics_clock_paused = FALSE;
3745                 StartClocks();
3746                 continue;
3747             }
3748
3749             /* Grab player ratings from the Creating: message.
3750                Note we have to check for the special case when
3751                the ICS inserts things like [white] or [black]. */
3752             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3753                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3754                 /* star_matches:
3755                    0    player 1 name (not necessarily white)
3756                    1    player 1 rating
3757                    2    empty, white, or black (IGNORED)
3758                    3    player 2 name (not necessarily black)
3759                    4    player 2 rating
3760
3761                    The names/ratings are sorted out when the game
3762                    actually starts (below).
3763                 */
3764                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3765                 player1Rating = string_to_rating(star_match[1]);
3766                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3767                 player2Rating = string_to_rating(star_match[4]);
3768
3769                 if (appData.debugMode)
3770                   fprintf(debugFP,
3771                           "Ratings from 'Creating:' %s %d, %s %d\n",
3772                           player1Name, player1Rating,
3773                           player2Name, player2Rating);
3774
3775                 continue;
3776             }
3777
3778             /* Improved generic start/end-of-game messages */
3779             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3780                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3781                 /* If tkind == 0: */
3782                 /* star_match[0] is the game number */
3783                 /*           [1] is the white player's name */
3784                 /*           [2] is the black player's name */
3785                 /* For end-of-game: */
3786                 /*           [3] is the reason for the game end */
3787                 /*           [4] is a PGN end game-token, preceded by " " */
3788                 /* For start-of-game: */
3789                 /*           [3] begins with "Creating" or "Continuing" */
3790                 /*           [4] is " *" or empty (don't care). */
3791                 int gamenum = atoi(star_match[0]);
3792                 char *whitename, *blackname, *why, *endtoken;
3793                 ChessMove endtype = EndOfFile;
3794
3795                 if (tkind == 0) {
3796                   whitename = star_match[1];
3797                   blackname = star_match[2];
3798                   why = star_match[3];
3799                   endtoken = star_match[4];
3800                 } else {
3801                   whitename = star_match[1];
3802                   blackname = star_match[3];
3803                   why = star_match[5];
3804                   endtoken = star_match[6];
3805                 }
3806
3807                 /* Game start messages */
3808                 if (strncmp(why, "Creating ", 9) == 0 ||
3809                     strncmp(why, "Continuing ", 11) == 0) {
3810                     gs_gamenum = gamenum;
3811                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3812                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3813 #if ZIPPY
3814                     if (appData.zippyPlay) {
3815                         ZippyGameStart(whitename, blackname);
3816                     }
3817 #endif /*ZIPPY*/
3818                     partnerBoardValid = FALSE; // [HGM] bughouse
3819                     continue;
3820                 }
3821
3822                 /* Game end messages */
3823                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3824                     ics_gamenum != gamenum) {
3825                     continue;
3826                 }
3827                 while (endtoken[0] == ' ') endtoken++;
3828                 switch (endtoken[0]) {
3829                   case '*':
3830                   default:
3831                     endtype = GameUnfinished;
3832                     break;
3833                   case '0':
3834                     endtype = BlackWins;
3835                     break;
3836                   case '1':
3837                     if (endtoken[1] == '/')
3838                       endtype = GameIsDrawn;
3839                     else
3840                       endtype = WhiteWins;
3841                     break;
3842                 }
3843                 GameEnds(endtype, why, GE_ICS);
3844 #if ZIPPY
3845                 if (appData.zippyPlay && first.initDone) {
3846                     ZippyGameEnd(endtype, why);
3847                     if (first.pr == NULL) {
3848                       /* Start the next process early so that we'll
3849                          be ready for the next challenge */
3850                       StartChessProgram(&first);
3851                     }
3852                     /* Send "new" early, in case this command takes
3853                        a long time to finish, so that we'll be ready
3854                        for the next challenge. */
3855                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3856                     Reset(TRUE, TRUE);
3857                 }
3858 #endif /*ZIPPY*/
3859                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3860                 continue;
3861             }
3862
3863             if (looking_at(buf, &i, "Removing game * from observation") ||
3864                 looking_at(buf, &i, "no longer observing game *") ||
3865                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3866                 if (gameMode == IcsObserving &&
3867                     atoi(star_match[0]) == ics_gamenum)
3868                   {
3869                       /* icsEngineAnalyze */
3870                       if (appData.icsEngineAnalyze) {
3871                             ExitAnalyzeMode();
3872                             ModeHighlight();
3873                       }
3874                       StopClocks();
3875                       gameMode = IcsIdle;
3876                       ics_gamenum = -1;
3877                       ics_user_moved = FALSE;
3878                   }
3879                 continue;
3880             }
3881
3882             if (looking_at(buf, &i, "no longer examining game *")) {
3883                 if (gameMode == IcsExamining &&
3884                     atoi(star_match[0]) == ics_gamenum)
3885                   {
3886                       gameMode = IcsIdle;
3887                       ics_gamenum = -1;
3888                       ics_user_moved = FALSE;
3889                   }
3890                 continue;
3891             }
3892
3893             /* Advance leftover_start past any newlines we find,
3894                so only partial lines can get reparsed */
3895             if (looking_at(buf, &i, "\n")) {
3896                 prevColor = curColor;
3897                 if (curColor != ColorNormal) {
3898                     if (oldi > next_out) {
3899                         SendToPlayer(&buf[next_out], oldi - next_out);
3900                         next_out = oldi;
3901                     }
3902                     Colorize(ColorNormal, FALSE);
3903                     curColor = ColorNormal;
3904                 }
3905                 if (started == STARTED_BOARD) {
3906                     started = STARTED_NONE;
3907                     parse[parse_pos] = NULLCHAR;
3908                     ParseBoard12(parse);
3909                     ics_user_moved = 0;
3910
3911                     /* Send premove here */
3912                     if (appData.premove) {
3913                       char str[MSG_SIZ];
3914                       if (currentMove == 0 &&
3915                           gameMode == IcsPlayingWhite &&
3916                           appData.premoveWhite) {
3917                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3918                         if (appData.debugMode)
3919                           fprintf(debugFP, "Sending premove:\n");
3920                         SendToICS(str);
3921                       } else if (currentMove == 1 &&
3922                                  gameMode == IcsPlayingBlack &&
3923                                  appData.premoveBlack) {
3924                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3925                         if (appData.debugMode)
3926                           fprintf(debugFP, "Sending premove:\n");
3927                         SendToICS(str);
3928                       } else if (gotPremove) {
3929                         gotPremove = 0;
3930                         ClearPremoveHighlights();
3931                         if (appData.debugMode)
3932                           fprintf(debugFP, "Sending premove:\n");
3933                           UserMoveEvent(premoveFromX, premoveFromY,
3934                                         premoveToX, premoveToY,
3935                                         premovePromoChar);
3936                       }
3937                     }
3938
3939                     /* Usually suppress following prompt */
3940                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3941                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3942                         if (looking_at(buf, &i, "*% ")) {
3943                             savingComment = FALSE;
3944                             suppressKibitz = 0;
3945                         }
3946                     }
3947                     next_out = i;
3948                 } else if (started == STARTED_HOLDINGS) {
3949                     int gamenum;
3950                     char new_piece[MSG_SIZ];
3951                     started = STARTED_NONE;
3952                     parse[parse_pos] = NULLCHAR;
3953                     if (appData.debugMode)
3954                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3955                                                         parse, currentMove);
3956                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3957                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3958                         if (gameInfo.variant == VariantNormal) {
3959                           /* [HGM] We seem to switch variant during a game!
3960                            * Presumably no holdings were displayed, so we have
3961                            * to move the position two files to the right to
3962                            * create room for them!
3963                            */
3964                           VariantClass newVariant;
3965                           switch(gameInfo.boardWidth) { // base guess on board width
3966                                 case 9:  newVariant = VariantShogi; break;
3967                                 case 10: newVariant = VariantGreat; break;
3968                                 default: newVariant = VariantCrazyhouse; break;
3969                           }
3970                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3971                           /* Get a move list just to see the header, which
3972                              will tell us whether this is really bug or zh */
3973                           if (ics_getting_history == H_FALSE) {
3974                             ics_getting_history = H_REQUESTED;
3975                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3976                             SendToICS(str);
3977                           }
3978                         }
3979                         new_piece[0] = NULLCHAR;
3980                         sscanf(parse, "game %d white [%s black [%s <- %s",
3981                                &gamenum, white_holding, black_holding,
3982                                new_piece);
3983                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3984                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3985                         /* [HGM] copy holdings to board holdings area */
3986                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3987                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3988                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3989 #if ZIPPY
3990                         if (appData.zippyPlay && first.initDone) {
3991                             ZippyHoldings(white_holding, black_holding,
3992                                           new_piece);
3993                         }
3994 #endif /*ZIPPY*/
3995                         if (tinyLayout || smallLayout) {
3996                             char wh[16], bh[16];
3997                             PackHolding(wh, white_holding);
3998                             PackHolding(bh, black_holding);
3999                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4000                                     gameInfo.white, gameInfo.black);
4001                         } else {
4002                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4003                                     gameInfo.white, white_holding,
4004                                     gameInfo.black, black_holding);
4005                         }
4006                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4007                         DrawPosition(FALSE, boards[currentMove]);
4008                         DisplayTitle(str);
4009                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4010                         sscanf(parse, "game %d white [%s black [%s <- %s",
4011                                &gamenum, white_holding, black_holding,
4012                                new_piece);
4013                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4014                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4015                         /* [HGM] copy holdings to partner-board holdings area */
4016                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4017                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4018                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4019                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4020                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4021                       }
4022                     }
4023                     /* Suppress following prompt */
4024                     if (looking_at(buf, &i, "*% ")) {
4025                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4026                         savingComment = FALSE;
4027                         suppressKibitz = 0;
4028                     }
4029                     next_out = i;
4030                 }
4031                 continue;
4032             }
4033
4034             i++;                /* skip unparsed character and loop back */
4035         }
4036
4037         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4038 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4039 //          SendToPlayer(&buf[next_out], i - next_out);
4040             started != STARTED_HOLDINGS && leftover_start > next_out) {
4041             SendToPlayer(&buf[next_out], leftover_start - next_out);
4042             next_out = i;
4043         }
4044
4045         leftover_len = buf_len - leftover_start;
4046         /* if buffer ends with something we couldn't parse,
4047            reparse it after appending the next read */
4048
4049     } else if (count == 0) {
4050         RemoveInputSource(isr);
4051         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4052     } else {
4053         DisplayFatalError(_("Error reading from ICS"), error, 1);
4054     }
4055 }
4056
4057
4058 /* Board style 12 looks like this:
4059
4060    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4061
4062  * The "<12> " is stripped before it gets to this routine.  The two
4063  * trailing 0's (flip state and clock ticking) are later addition, and
4064  * some chess servers may not have them, or may have only the first.
4065  * Additional trailing fields may be added in the future.
4066  */
4067
4068 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4069
4070 #define RELATION_OBSERVING_PLAYED    0
4071 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4072 #define RELATION_PLAYING_MYMOVE      1
4073 #define RELATION_PLAYING_NOTMYMOVE  -1
4074 #define RELATION_EXAMINING           2
4075 #define RELATION_ISOLATED_BOARD     -3
4076 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4077
4078 void
4079 ParseBoard12(string)
4080      char *string;
4081 {
4082     GameMode newGameMode;
4083     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4084     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4085     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4086     char to_play, board_chars[200];
4087     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4088     char black[32], white[32];
4089     Board board;
4090     int prevMove = currentMove;
4091     int ticking = 2;
4092     ChessMove moveType;
4093     int fromX, fromY, toX, toY;
4094     char promoChar;
4095     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4096     char *bookHit = NULL; // [HGM] book
4097     Boolean weird = FALSE, reqFlag = FALSE;
4098
4099     fromX = fromY = toX = toY = -1;
4100
4101     newGame = FALSE;
4102
4103     if (appData.debugMode)
4104       fprintf(debugFP, _("Parsing board: %s\n"), string);
4105
4106     move_str[0] = NULLCHAR;
4107     elapsed_time[0] = NULLCHAR;
4108     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4109         int  i = 0, j;
4110         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4111             if(string[i] == ' ') { ranks++; files = 0; }
4112             else files++;
4113             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4114             i++;
4115         }
4116         for(j = 0; j <i; j++) board_chars[j] = string[j];
4117         board_chars[i] = '\0';
4118         string += i + 1;
4119     }
4120     n = sscanf(string, PATTERN, &to_play, &double_push,
4121                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4122                &gamenum, white, black, &relation, &basetime, &increment,
4123                &white_stren, &black_stren, &white_time, &black_time,
4124                &moveNum, str, elapsed_time, move_str, &ics_flip,
4125                &ticking);
4126
4127     if (n < 21) {
4128         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4129         DisplayError(str, 0);
4130         return;
4131     }
4132
4133     /* Convert the move number to internal form */
4134     moveNum = (moveNum - 1) * 2;
4135     if (to_play == 'B') moveNum++;
4136     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4137       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4138                         0, 1);
4139       return;
4140     }
4141
4142     switch (relation) {
4143       case RELATION_OBSERVING_PLAYED:
4144       case RELATION_OBSERVING_STATIC:
4145         if (gamenum == -1) {
4146             /* Old ICC buglet */
4147             relation = RELATION_OBSERVING_STATIC;
4148         }
4149         newGameMode = IcsObserving;
4150         break;
4151       case RELATION_PLAYING_MYMOVE:
4152       case RELATION_PLAYING_NOTMYMOVE:
4153         newGameMode =
4154           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4155             IcsPlayingWhite : IcsPlayingBlack;
4156         break;
4157       case RELATION_EXAMINING:
4158         newGameMode = IcsExamining;
4159         break;
4160       case RELATION_ISOLATED_BOARD:
4161       default:
4162         /* Just display this board.  If user was doing something else,
4163            we will forget about it until the next board comes. */
4164         newGameMode = IcsIdle;
4165         break;
4166       case RELATION_STARTING_POSITION:
4167         newGameMode = gameMode;
4168         break;
4169     }
4170
4171     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4172          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4173       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4174       char *toSqr;
4175       for (k = 0; k < ranks; k++) {
4176         for (j = 0; j < files; j++)
4177           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4178         if(gameInfo.holdingsWidth > 1) {
4179              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4180              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4181         }
4182       }
4183       CopyBoard(partnerBoard, board);
4184       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4185         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4186         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4187       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4188       if(toSqr = strchr(str, '-')) {
4189         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4190         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4191       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4192       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4193       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4194       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4195       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4196       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4197                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4198       DisplayMessage(partnerStatus, "");
4199         partnerBoardValid = TRUE;
4200       return;
4201     }
4202
4203     /* Modify behavior for initial board display on move listing
4204        of wild games.
4205        */
4206     switch (ics_getting_history) {
4207       case H_FALSE:
4208       case H_REQUESTED:
4209         break;
4210       case H_GOT_REQ_HEADER:
4211       case H_GOT_UNREQ_HEADER:
4212         /* This is the initial position of the current game */
4213         gamenum = ics_gamenum;
4214         moveNum = 0;            /* old ICS bug workaround */
4215         if (to_play == 'B') {
4216           startedFromSetupPosition = TRUE;
4217           blackPlaysFirst = TRUE;
4218           moveNum = 1;
4219           if (forwardMostMove == 0) forwardMostMove = 1;
4220           if (backwardMostMove == 0) backwardMostMove = 1;
4221           if (currentMove == 0) currentMove = 1;
4222         }
4223         newGameMode = gameMode;
4224         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4225         break;
4226       case H_GOT_UNWANTED_HEADER:
4227         /* This is an initial board that we don't want */
4228         return;
4229       case H_GETTING_MOVES:
4230         /* Should not happen */
4231         DisplayError(_("Error gathering move list: extra board"), 0);
4232         ics_getting_history = H_FALSE;
4233         return;
4234     }
4235
4236    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4237                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4238      /* [HGM] We seem to have switched variant unexpectedly
4239       * Try to guess new variant from board size
4240       */
4241           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4242           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4243           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4244           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4245           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4246           if(!weird) newVariant = VariantNormal;
4247           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4248           /* Get a move list just to see the header, which
4249              will tell us whether this is really bug or zh */
4250           if (ics_getting_history == H_FALSE) {
4251             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4252             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4253             SendToICS(str);
4254           }
4255     }
4256
4257     /* Take action if this is the first board of a new game, or of a
4258        different game than is currently being displayed.  */
4259     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4260         relation == RELATION_ISOLATED_BOARD) {
4261
4262         /* Forget the old game and get the history (if any) of the new one */
4263         if (gameMode != BeginningOfGame) {
4264           Reset(TRUE, TRUE);
4265         }
4266         newGame = TRUE;
4267         if (appData.autoRaiseBoard) BoardToTop();
4268         prevMove = -3;
4269         if (gamenum == -1) {
4270             newGameMode = IcsIdle;
4271         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4272                    appData.getMoveList && !reqFlag) {
4273             /* Need to get game history */
4274             ics_getting_history = H_REQUESTED;
4275             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4276             SendToICS(str);
4277         }
4278
4279         /* Initially flip the board to have black on the bottom if playing
4280            black or if the ICS flip flag is set, but let the user change
4281            it with the Flip View button. */
4282         flipView = appData.autoFlipView ?
4283           (newGameMode == IcsPlayingBlack) || ics_flip :
4284           appData.flipView;
4285
4286         /* Done with values from previous mode; copy in new ones */
4287         gameMode = newGameMode;
4288         ModeHighlight();
4289         ics_gamenum = gamenum;
4290         if (gamenum == gs_gamenum) {
4291             int klen = strlen(gs_kind);
4292             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4293             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4294             gameInfo.event = StrSave(str);
4295         } else {
4296             gameInfo.event = StrSave("ICS game");
4297         }
4298         gameInfo.site = StrSave(appData.icsHost);
4299         gameInfo.date = PGNDate();
4300         gameInfo.round = StrSave("-");
4301         gameInfo.white = StrSave(white);
4302         gameInfo.black = StrSave(black);
4303         timeControl = basetime * 60 * 1000;
4304         timeControl_2 = 0;
4305         timeIncrement = increment * 1000;
4306         movesPerSession = 0;
4307         gameInfo.timeControl = TimeControlTagValue();
4308         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4309   if (appData.debugMode) {
4310     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4311     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4312     setbuf(debugFP, NULL);
4313   }
4314
4315         gameInfo.outOfBook = NULL;
4316
4317         /* Do we have the ratings? */
4318         if (strcmp(player1Name, white) == 0 &&
4319             strcmp(player2Name, black) == 0) {
4320             if (appData.debugMode)
4321               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4322                       player1Rating, player2Rating);
4323             gameInfo.whiteRating = player1Rating;
4324             gameInfo.blackRating = player2Rating;
4325         } else if (strcmp(player2Name, white) == 0 &&
4326                    strcmp(player1Name, black) == 0) {
4327             if (appData.debugMode)
4328               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4329                       player2Rating, player1Rating);
4330             gameInfo.whiteRating = player2Rating;
4331             gameInfo.blackRating = player1Rating;
4332         }
4333         player1Name[0] = player2Name[0] = NULLCHAR;
4334
4335         /* Silence shouts if requested */
4336         if (appData.quietPlay &&
4337             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4338             SendToICS(ics_prefix);
4339             SendToICS("set shout 0\n");
4340         }
4341     }
4342
4343     /* Deal with midgame name changes */
4344     if (!newGame) {
4345         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4346             if (gameInfo.white) free(gameInfo.white);
4347             gameInfo.white = StrSave(white);
4348         }
4349         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4350             if (gameInfo.black) free(gameInfo.black);
4351             gameInfo.black = StrSave(black);
4352         }
4353     }
4354
4355     /* Throw away game result if anything actually changes in examine mode */
4356     if (gameMode == IcsExamining && !newGame) {
4357         gameInfo.result = GameUnfinished;
4358         if (gameInfo.resultDetails != NULL) {
4359             free(gameInfo.resultDetails);
4360             gameInfo.resultDetails = NULL;
4361         }
4362     }
4363
4364     /* In pausing && IcsExamining mode, we ignore boards coming
4365        in if they are in a different variation than we are. */
4366     if (pauseExamInvalid) return;
4367     if (pausing && gameMode == IcsExamining) {
4368         if (moveNum <= pauseExamForwardMostMove) {
4369             pauseExamInvalid = TRUE;
4370             forwardMostMove = pauseExamForwardMostMove;
4371             return;
4372         }
4373     }
4374
4375   if (appData.debugMode) {
4376     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4377   }
4378     /* Parse the board */
4379     for (k = 0; k < ranks; k++) {
4380       for (j = 0; j < files; j++)
4381         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4382       if(gameInfo.holdingsWidth > 1) {
4383            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4384            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4385       }
4386     }
4387     CopyBoard(boards[moveNum], board);
4388     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4389     if (moveNum == 0) {
4390         startedFromSetupPosition =
4391           !CompareBoards(board, initialPosition);
4392         if(startedFromSetupPosition)
4393             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4394     }
4395
4396     /* [HGM] Set castling rights. Take the outermost Rooks,
4397        to make it also work for FRC opening positions. Note that board12
4398        is really defective for later FRC positions, as it has no way to
4399        indicate which Rook can castle if they are on the same side of King.
4400        For the initial position we grant rights to the outermost Rooks,
4401        and remember thos rights, and we then copy them on positions
4402        later in an FRC game. This means WB might not recognize castlings with
4403        Rooks that have moved back to their original position as illegal,
4404        but in ICS mode that is not its job anyway.
4405     */
4406     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4407     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4408
4409         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4410             if(board[0][i] == WhiteRook) j = i;
4411         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4412         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4413             if(board[0][i] == WhiteRook) j = i;
4414         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4415         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4416             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4417         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4418         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4419             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4420         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4421
4422         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4423         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4424             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4425         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4426             if(board[BOARD_HEIGHT-1][k] == bKing)
4427                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4428         if(gameInfo.variant == VariantTwoKings) {
4429             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4430             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4431             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4432         }
4433     } else { int r;
4434         r = boards[moveNum][CASTLING][0] = initialRights[0];
4435         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4436         r = boards[moveNum][CASTLING][1] = initialRights[1];
4437         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4438         r = boards[moveNum][CASTLING][3] = initialRights[3];
4439         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4440         r = boards[moveNum][CASTLING][4] = initialRights[4];
4441         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4442         /* wildcastle kludge: always assume King has rights */
4443         r = boards[moveNum][CASTLING][2] = initialRights[2];
4444         r = boards[moveNum][CASTLING][5] = initialRights[5];
4445     }
4446     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4447     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4448
4449
4450     if (ics_getting_history == H_GOT_REQ_HEADER ||
4451         ics_getting_history == H_GOT_UNREQ_HEADER) {
4452         /* This was an initial position from a move list, not
4453            the current position */
4454         return;
4455     }
4456
4457     /* Update currentMove and known move number limits */
4458     newMove = newGame || moveNum > forwardMostMove;
4459
4460     if (newGame) {
4461         forwardMostMove = backwardMostMove = currentMove = moveNum;
4462         if (gameMode == IcsExamining && moveNum == 0) {
4463           /* Workaround for ICS limitation: we are not told the wild
4464              type when starting to examine a game.  But if we ask for
4465              the move list, the move list header will tell us */
4466             ics_getting_history = H_REQUESTED;
4467             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4468             SendToICS(str);
4469         }
4470     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4471                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4472 #if ZIPPY
4473         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4474         /* [HGM] applied this also to an engine that is silently watching        */
4475         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4476             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4477             gameInfo.variant == currentlyInitializedVariant) {
4478           takeback = forwardMostMove - moveNum;
4479           for (i = 0; i < takeback; i++) {
4480             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4481             SendToProgram("undo\n", &first);
4482           }
4483         }
4484 #endif
4485
4486         forwardMostMove = moveNum;
4487         if (!pausing || currentMove > forwardMostMove)
4488           currentMove = forwardMostMove;
4489     } else {
4490         /* New part of history that is not contiguous with old part */
4491         if (pausing && gameMode == IcsExamining) {
4492             pauseExamInvalid = TRUE;
4493             forwardMostMove = pauseExamForwardMostMove;
4494             return;
4495         }
4496         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4497 #if ZIPPY
4498             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4499                 // [HGM] when we will receive the move list we now request, it will be
4500                 // fed to the engine from the first move on. So if the engine is not
4501                 // in the initial position now, bring it there.
4502                 InitChessProgram(&first, 0);
4503             }
4504 #endif
4505             ics_getting_history = H_REQUESTED;
4506             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4507             SendToICS(str);
4508         }
4509         forwardMostMove = backwardMostMove = currentMove = moveNum;
4510     }
4511
4512     /* Update the clocks */
4513     if (strchr(elapsed_time, '.')) {
4514       /* Time is in ms */
4515       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4516       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4517     } else {
4518       /* Time is in seconds */
4519       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4520       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4521     }
4522
4523
4524 #if ZIPPY
4525     if (appData.zippyPlay && newGame &&
4526         gameMode != IcsObserving && gameMode != IcsIdle &&
4527         gameMode != IcsExamining)
4528       ZippyFirstBoard(moveNum, basetime, increment);
4529 #endif
4530
4531     /* Put the move on the move list, first converting
4532        to canonical algebraic form. */
4533     if (moveNum > 0) {
4534   if (appData.debugMode) {
4535     if (appData.debugMode) { int f = forwardMostMove;
4536         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4537                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4538                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4539     }
4540     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4541     fprintf(debugFP, "moveNum = %d\n", moveNum);
4542     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4543     setbuf(debugFP, NULL);
4544   }
4545         if (moveNum <= backwardMostMove) {
4546             /* We don't know what the board looked like before
4547                this move.  Punt. */
4548           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4549             strcat(parseList[moveNum - 1], " ");
4550             strcat(parseList[moveNum - 1], elapsed_time);
4551             moveList[moveNum - 1][0] = NULLCHAR;
4552         } else if (strcmp(move_str, "none") == 0) {
4553             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4554             /* Again, we don't know what the board looked like;
4555                this is really the start of the game. */
4556             parseList[moveNum - 1][0] = NULLCHAR;
4557             moveList[moveNum - 1][0] = NULLCHAR;
4558             backwardMostMove = moveNum;
4559             startedFromSetupPosition = TRUE;
4560             fromX = fromY = toX = toY = -1;
4561         } else {
4562           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4563           //                 So we parse the long-algebraic move string in stead of the SAN move
4564           int valid; char buf[MSG_SIZ], *prom;
4565
4566           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4567                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4568           // str looks something like "Q/a1-a2"; kill the slash
4569           if(str[1] == '/')
4570             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4571           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4572           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4573                 strcat(buf, prom); // long move lacks promo specification!
4574           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4575                 if(appData.debugMode)
4576                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4577                 safeStrCpy(move_str, buf, MSG_SIZ);
4578           }
4579           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4580                                 &fromX, &fromY, &toX, &toY, &promoChar)
4581                || ParseOneMove(buf, moveNum - 1, &moveType,
4582                                 &fromX, &fromY, &toX, &toY, &promoChar);
4583           // end of long SAN patch
4584           if (valid) {
4585             (void) CoordsToAlgebraic(boards[moveNum - 1],
4586                                      PosFlags(moveNum - 1),
4587                                      fromY, fromX, toY, toX, promoChar,
4588                                      parseList[moveNum-1]);
4589             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4590               case MT_NONE:
4591               case MT_STALEMATE:
4592               default:
4593                 break;
4594               case MT_CHECK:
4595                 if(gameInfo.variant != VariantShogi)
4596                     strcat(parseList[moveNum - 1], "+");
4597                 break;
4598               case MT_CHECKMATE:
4599               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4600                 strcat(parseList[moveNum - 1], "#");
4601                 break;
4602             }
4603             strcat(parseList[moveNum - 1], " ");
4604             strcat(parseList[moveNum - 1], elapsed_time);
4605             /* currentMoveString is set as a side-effect of ParseOneMove */
4606             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4607             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4608             strcat(moveList[moveNum - 1], "\n");
4609
4610             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4611                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4612               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4613                 ChessSquare old, new = boards[moveNum][k][j];
4614                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4615                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4616                   if(old == new) continue;
4617                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4618                   else if(new == WhiteWazir || new == BlackWazir) {
4619                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4620                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4621                       else boards[moveNum][k][j] = old; // preserve type of Gold
4622                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4623                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4624               }
4625           } else {
4626             /* Move from ICS was illegal!?  Punt. */
4627             if (appData.debugMode) {
4628               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4629               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4630             }
4631             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4632             strcat(parseList[moveNum - 1], " ");
4633             strcat(parseList[moveNum - 1], elapsed_time);
4634             moveList[moveNum - 1][0] = NULLCHAR;
4635             fromX = fromY = toX = toY = -1;
4636           }
4637         }
4638   if (appData.debugMode) {
4639     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4640     setbuf(debugFP, NULL);
4641   }
4642
4643 #if ZIPPY
4644         /* Send move to chess program (BEFORE animating it). */
4645         if (appData.zippyPlay && !newGame && newMove &&
4646            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4647
4648             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4649                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4650                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4651                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4652                             move_str);
4653                     DisplayError(str, 0);
4654                 } else {
4655                     if (first.sendTime) {
4656                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4657                     }
4658                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4659                     if (firstMove && !bookHit) {
4660                         firstMove = FALSE;
4661                         if (first.useColors) {
4662                           SendToProgram(gameMode == IcsPlayingWhite ?
4663                                         "white\ngo\n" :
4664                                         "black\ngo\n", &first);
4665                         } else {
4666                           SendToProgram("go\n", &first);
4667                         }
4668                         first.maybeThinking = TRUE;
4669                     }
4670                 }
4671             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4672               if (moveList[moveNum - 1][0] == NULLCHAR) {
4673                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4674                 DisplayError(str, 0);
4675               } else {
4676                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4677                 SendMoveToProgram(moveNum - 1, &first);
4678               }
4679             }
4680         }
4681 #endif
4682     }
4683
4684     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4685         /* If move comes from a remote source, animate it.  If it
4686            isn't remote, it will have already been animated. */
4687         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4688             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4689         }
4690         if (!pausing && appData.highlightLastMove) {
4691             SetHighlights(fromX, fromY, toX, toY);
4692         }
4693     }
4694
4695     /* Start the clocks */
4696     whiteFlag = blackFlag = FALSE;
4697     appData.clockMode = !(basetime == 0 && increment == 0);
4698     if (ticking == 0) {
4699       ics_clock_paused = TRUE;
4700       StopClocks();
4701     } else if (ticking == 1) {
4702       ics_clock_paused = FALSE;
4703     }
4704     if (gameMode == IcsIdle ||
4705         relation == RELATION_OBSERVING_STATIC ||
4706         relation == RELATION_EXAMINING ||
4707         ics_clock_paused)
4708       DisplayBothClocks();
4709     else
4710       StartClocks();
4711
4712     /* Display opponents and material strengths */
4713     if (gameInfo.variant != VariantBughouse &&
4714         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4715         if (tinyLayout || smallLayout) {
4716             if(gameInfo.variant == VariantNormal)
4717               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4718                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4719                     basetime, increment);
4720             else
4721               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4722                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4723                     basetime, increment, (int) gameInfo.variant);
4724         } else {
4725             if(gameInfo.variant == VariantNormal)
4726               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4727                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4728                     basetime, increment);
4729             else
4730               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4731                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4732                     basetime, increment, VariantName(gameInfo.variant));
4733         }
4734         DisplayTitle(str);
4735   if (appData.debugMode) {
4736     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4737   }
4738     }
4739
4740
4741     /* Display the board */
4742     if (!pausing && !appData.noGUI) {
4743
4744       if (appData.premove)
4745           if (!gotPremove ||
4746              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4747              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4748               ClearPremoveHighlights();
4749
4750       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4751         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4752       DrawPosition(j, boards[currentMove]);
4753
4754       DisplayMove(moveNum - 1);
4755       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4756             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4757               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4758         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4759       }
4760     }
4761
4762     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4763 #if ZIPPY
4764     if(bookHit) { // [HGM] book: simulate book reply
4765         static char bookMove[MSG_SIZ]; // a bit generous?
4766
4767         programStats.nodes = programStats.depth = programStats.time =
4768         programStats.score = programStats.got_only_move = 0;
4769         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4770
4771         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4772         strcat(bookMove, bookHit);
4773         HandleMachineMove(bookMove, &first);
4774     }
4775 #endif
4776 }
4777
4778 void
4779 GetMoveListEvent()
4780 {
4781     char buf[MSG_SIZ];
4782     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4783         ics_getting_history = H_REQUESTED;
4784         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4785         SendToICS(buf);
4786     }
4787 }
4788
4789 void
4790 AnalysisPeriodicEvent(force)
4791      int force;
4792 {
4793     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4794          && !force) || !appData.periodicUpdates)
4795       return;
4796
4797     /* Send . command to Crafty to collect stats */
4798     SendToProgram(".\n", &first);
4799
4800     /* Don't send another until we get a response (this makes
4801        us stop sending to old Crafty's which don't understand
4802        the "." command (sending illegal cmds resets node count & time,
4803        which looks bad)) */
4804     programStats.ok_to_send = 0;
4805 }
4806
4807 void ics_update_width(new_width)
4808         int new_width;
4809 {
4810         ics_printf("set width %d\n", new_width);
4811 }
4812
4813 void
4814 SendMoveToProgram(moveNum, cps)
4815      int moveNum;
4816      ChessProgramState *cps;
4817 {
4818     char buf[MSG_SIZ];
4819
4820     if (cps->useUsermove) {
4821       SendToProgram("usermove ", cps);
4822     }
4823     if (cps->useSAN) {
4824       char *space;
4825       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4826         int len = space - parseList[moveNum];
4827         memcpy(buf, parseList[moveNum], len);
4828         buf[len++] = '\n';
4829         buf[len] = NULLCHAR;
4830       } else {
4831         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4832       }
4833       SendToProgram(buf, cps);
4834     } else {
4835       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4836         AlphaRank(moveList[moveNum], 4);
4837         SendToProgram(moveList[moveNum], cps);
4838         AlphaRank(moveList[moveNum], 4); // and back
4839       } else
4840       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4841        * the engine. It would be nice to have a better way to identify castle
4842        * moves here. */
4843       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4844                                                                          && cps->useOOCastle) {
4845         int fromX = moveList[moveNum][0] - AAA;
4846         int fromY = moveList[moveNum][1] - ONE;
4847         int toX = moveList[moveNum][2] - AAA;
4848         int toY = moveList[moveNum][3] - ONE;
4849         if((boards[moveNum][fromY][fromX] == WhiteKing
4850             && boards[moveNum][toY][toX] == WhiteRook)
4851            || (boards[moveNum][fromY][fromX] == BlackKing
4852                && boards[moveNum][toY][toX] == BlackRook)) {
4853           if(toX > fromX) SendToProgram("O-O\n", cps);
4854           else SendToProgram("O-O-O\n", cps);
4855         }
4856         else SendToProgram(moveList[moveNum], cps);
4857       }
4858       else SendToProgram(moveList[moveNum], cps);
4859       /* End of additions by Tord */
4860     }
4861
4862     /* [HGM] setting up the opening has brought engine in force mode! */
4863     /*       Send 'go' if we are in a mode where machine should play. */
4864     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4865         (gameMode == TwoMachinesPlay   ||
4866 #if ZIPPY
4867          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4868 #endif
4869          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4870         SendToProgram("go\n", cps);
4871   if (appData.debugMode) {
4872     fprintf(debugFP, "(extra)\n");
4873   }
4874     }
4875     setboardSpoiledMachineBlack = 0;
4876 }
4877
4878 void
4879 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4880      ChessMove moveType;
4881      int fromX, fromY, toX, toY;
4882      char promoChar;
4883 {
4884     char user_move[MSG_SIZ];
4885
4886     switch (moveType) {
4887       default:
4888         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4889                 (int)moveType, fromX, fromY, toX, toY);
4890         DisplayError(user_move + strlen("say "), 0);
4891         break;
4892       case WhiteKingSideCastle:
4893       case BlackKingSideCastle:
4894       case WhiteQueenSideCastleWild:
4895       case BlackQueenSideCastleWild:
4896       /* PUSH Fabien */
4897       case WhiteHSideCastleFR:
4898       case BlackHSideCastleFR:
4899       /* POP Fabien */
4900         snprintf(user_move, MSG_SIZ, "o-o\n");
4901         break;
4902       case WhiteQueenSideCastle:
4903       case BlackQueenSideCastle:
4904       case WhiteKingSideCastleWild:
4905       case BlackKingSideCastleWild:
4906       /* PUSH Fabien */
4907       case WhiteASideCastleFR:
4908       case BlackASideCastleFR:
4909       /* POP Fabien */
4910         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4911         break;
4912       case WhiteNonPromotion:
4913       case BlackNonPromotion:
4914         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4915         break;
4916       case WhitePromotion:
4917       case BlackPromotion:
4918         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4919           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4920                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4921                 PieceToChar(WhiteFerz));
4922         else if(gameInfo.variant == VariantGreat)
4923           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4924                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4925                 PieceToChar(WhiteMan));
4926         else
4927           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4928                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4929                 promoChar);
4930         break;
4931       case WhiteDrop:
4932       case BlackDrop:
4933       drop:
4934         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4935                  ToUpper(PieceToChar((ChessSquare) fromX)),
4936                  AAA + toX, ONE + toY);
4937         break;
4938       case IllegalMove:  /* could be a variant we don't quite understand */
4939         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4940       case NormalMove:
4941       case WhiteCapturesEnPassant:
4942       case BlackCapturesEnPassant:
4943         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4944                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4945         break;
4946     }
4947     SendToICS(user_move);
4948     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4949         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4950 }
4951
4952 void
4953 UploadGameEvent()
4954 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4955     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4956     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4957     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4958         DisplayError("You cannot do this while you are playing or observing", 0);
4959         return;
4960     }
4961     if(gameMode != IcsExamining) { // is this ever not the case?
4962         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4963
4964         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4965           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4966         } else { // on FICS we must first go to general examine mode
4967           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4968         }
4969         if(gameInfo.variant != VariantNormal) {
4970             // try figure out wild number, as xboard names are not always valid on ICS
4971             for(i=1; i<=36; i++) {
4972               snprintf(buf, MSG_SIZ, "wild/%d", i);
4973                 if(StringToVariant(buf) == gameInfo.variant) break;
4974             }
4975             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4976             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4977             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4978         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4979         SendToICS(ics_prefix);
4980         SendToICS(buf);
4981         if(startedFromSetupPosition || backwardMostMove != 0) {
4982           fen = PositionToFEN(backwardMostMove, NULL);
4983           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4984             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4985             SendToICS(buf);
4986           } else { // FICS: everything has to set by separate bsetup commands
4987             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4988             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4989             SendToICS(buf);
4990             if(!WhiteOnMove(backwardMostMove)) {
4991                 SendToICS("bsetup tomove black\n");
4992             }
4993             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4994             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4995             SendToICS(buf);
4996             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4997             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4998             SendToICS(buf);
4999             i = boards[backwardMostMove][EP_STATUS];
5000             if(i >= 0) { // set e.p.
5001               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5002                 SendToICS(buf);
5003             }
5004             bsetup++;
5005           }
5006         }
5007       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5008             SendToICS("bsetup done\n"); // switch to normal examining.
5009     }
5010     for(i = backwardMostMove; i<last; i++) {
5011         char buf[20];
5012         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5013         SendToICS(buf);
5014     }
5015     SendToICS(ics_prefix);
5016     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5017 }
5018
5019 void
5020 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5021      int rf, ff, rt, ft;
5022      char promoChar;
5023      char move[7];
5024 {
5025     if (rf == DROP_RANK) {
5026       sprintf(move, "%c@%c%c\n",
5027                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5028     } else {
5029         if (promoChar == 'x' || promoChar == NULLCHAR) {
5030           sprintf(move, "%c%c%c%c\n",
5031                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5032         } else {
5033             sprintf(move, "%c%c%c%c%c\n",
5034                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5035         }
5036     }
5037 }
5038
5039 void
5040 ProcessICSInitScript(f)
5041      FILE *f;
5042 {
5043     char buf[MSG_SIZ];
5044
5045     while (fgets(buf, MSG_SIZ, f)) {
5046         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5047     }
5048
5049     fclose(f);
5050 }
5051
5052
5053 static int lastX, lastY, selectFlag, dragging;
5054
5055 void
5056 Sweep(int step)
5057 {
5058     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5059     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5060     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5061     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5062     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5063     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5064     do {
5065         promoSweep -= step;
5066         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5067         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5068         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5069         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5070         if(!step) step = 1;
5071     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5072             appData.testLegality && (promoSweep == king ||
5073             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5074     ChangeDragPiece(promoSweep);
5075 }
5076
5077 int PromoScroll(int x, int y)
5078 {
5079   int step = 0;
5080
5081   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5082   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5083   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5084   if(!step) return FALSE;
5085   lastX = x; lastY = y;
5086   if((promoSweep < BlackPawn) == flipView) step = -step;
5087   if(step > 0) selectFlag = 1;
5088   if(!selectFlag) Sweep(step);
5089   return FALSE;
5090 }
5091
5092 void
5093 NextPiece(int step)
5094 {
5095     ChessSquare piece = boards[currentMove][toY][toX];
5096     do {
5097         pieceSweep -= step;
5098         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5099         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5100         if(!step) step = -1;
5101     } while(PieceToChar(pieceSweep) == '.');
5102     boards[currentMove][toY][toX] = pieceSweep;
5103     DrawPosition(FALSE, boards[currentMove]);
5104     boards[currentMove][toY][toX] = piece;
5105 }
5106 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5107 void
5108 AlphaRank(char *move, int n)
5109 {
5110 //    char *p = move, c; int x, y;
5111
5112     if (appData.debugMode) {
5113         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5114     }
5115
5116     if(move[1]=='*' &&
5117        move[2]>='0' && move[2]<='9' &&
5118        move[3]>='a' && move[3]<='x'    ) {
5119         move[1] = '@';
5120         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5121         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5122     } else
5123     if(move[0]>='0' && move[0]<='9' &&
5124        move[1]>='a' && move[1]<='x' &&
5125        move[2]>='0' && move[2]<='9' &&
5126        move[3]>='a' && move[3]<='x'    ) {
5127         /* input move, Shogi -> normal */
5128         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5129         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5130         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5131         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5132     } else
5133     if(move[1]=='@' &&
5134        move[3]>='0' && move[3]<='9' &&
5135        move[2]>='a' && move[2]<='x'    ) {
5136         move[1] = '*';
5137         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5138         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5139     } else
5140     if(
5141        move[0]>='a' && move[0]<='x' &&
5142        move[3]>='0' && move[3]<='9' &&
5143        move[2]>='a' && move[2]<='x'    ) {
5144          /* output move, normal -> Shogi */
5145         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5146         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5147         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5148         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5149         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5150     }
5151     if (appData.debugMode) {
5152         fprintf(debugFP, "   out = '%s'\n", move);
5153     }
5154 }
5155
5156 char yy_textstr[8000];
5157
5158 /* Parser for moves from gnuchess, ICS, or user typein box */
5159 Boolean
5160 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5161      char *move;
5162      int moveNum;
5163      ChessMove *moveType;
5164      int *fromX, *fromY, *toX, *toY;
5165      char *promoChar;
5166 {
5167     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5168
5169     switch (*moveType) {
5170       case WhitePromotion:
5171       case BlackPromotion:
5172       case WhiteNonPromotion:
5173       case BlackNonPromotion:
5174       case NormalMove:
5175       case WhiteCapturesEnPassant:
5176       case BlackCapturesEnPassant:
5177       case WhiteKingSideCastle:
5178       case WhiteQueenSideCastle:
5179       case BlackKingSideCastle:
5180       case BlackQueenSideCastle:
5181       case WhiteKingSideCastleWild:
5182       case WhiteQueenSideCastleWild:
5183       case BlackKingSideCastleWild:
5184       case BlackQueenSideCastleWild:
5185       /* Code added by Tord: */
5186       case WhiteHSideCastleFR:
5187       case WhiteASideCastleFR:
5188       case BlackHSideCastleFR:
5189       case BlackASideCastleFR:
5190       /* End of code added by Tord */
5191       case IllegalMove:         /* bug or odd chess variant */
5192         *fromX = currentMoveString[0] - AAA;
5193         *fromY = currentMoveString[1] - ONE;
5194         *toX = currentMoveString[2] - AAA;
5195         *toY = currentMoveString[3] - ONE;
5196         *promoChar = currentMoveString[4];
5197         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5198             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5199     if (appData.debugMode) {
5200         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5201     }
5202             *fromX = *fromY = *toX = *toY = 0;
5203             return FALSE;
5204         }
5205         if (appData.testLegality) {
5206           return (*moveType != IllegalMove);
5207         } else {
5208           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5209                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5210         }
5211
5212       case WhiteDrop:
5213       case BlackDrop:
5214         *fromX = *moveType == WhiteDrop ?
5215           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5216           (int) CharToPiece(ToLower(currentMoveString[0]));
5217         *fromY = DROP_RANK;
5218         *toX = currentMoveString[2] - AAA;
5219         *toY = currentMoveString[3] - ONE;
5220         *promoChar = NULLCHAR;
5221         return TRUE;
5222
5223       case AmbiguousMove:
5224       case ImpossibleMove:
5225       case EndOfFile:
5226       case ElapsedTime:
5227       case Comment:
5228       case PGNTag:
5229       case NAG:
5230       case WhiteWins:
5231       case BlackWins:
5232       case GameIsDrawn:
5233       default:
5234     if (appData.debugMode) {
5235         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5236     }
5237         /* bug? */
5238         *fromX = *fromY = *toX = *toY = 0;
5239         *promoChar = NULLCHAR;
5240         return FALSE;
5241     }
5242 }
5243
5244 Boolean pushed = FALSE;
5245
5246 void
5247 ParsePV(char *pv, Boolean storeComments)
5248 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5249   int fromX, fromY, toX, toY; char promoChar;
5250   ChessMove moveType;
5251   Boolean valid;
5252   int nr = 0;
5253
5254   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5255     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5256     pushed = TRUE;
5257   }
5258   endPV = forwardMostMove;
5259   do {
5260     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5261     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5262     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5263 if(appData.debugMode){
5264 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5265 }
5266     if(!valid && nr == 0 &&
5267        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5268         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5269         // Hande case where played move is different from leading PV move
5270         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5271         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5272         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5273         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5274           endPV += 2; // if position different, keep this
5275           moveList[endPV-1][0] = fromX + AAA;
5276           moveList[endPV-1][1] = fromY + ONE;
5277           moveList[endPV-1][2] = toX + AAA;
5278           moveList[endPV-1][3] = toY + ONE;
5279           parseList[endPV-1][0] = NULLCHAR;
5280           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5281         }
5282       }
5283     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5284     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5285     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5286     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5287         valid++; // allow comments in PV
5288         continue;
5289     }
5290     nr++;
5291     if(endPV+1 > framePtr) break; // no space, truncate
5292     if(!valid) break;
5293     endPV++;
5294     CopyBoard(boards[endPV], boards[endPV-1]);
5295     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5296     moveList[endPV-1][0] = fromX + AAA;
5297     moveList[endPV-1][1] = fromY + ONE;
5298     moveList[endPV-1][2] = toX + AAA;
5299     moveList[endPV-1][3] = toY + ONE;
5300     moveList[endPV-1][4] = promoChar;
5301     moveList[endPV-1][5] = NULLCHAR;
5302     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5303     if(storeComments)
5304         CoordsToAlgebraic(boards[endPV - 1],
5305                              PosFlags(endPV - 1),
5306                              fromY, fromX, toY, toX, promoChar,
5307                              parseList[endPV - 1]);
5308     else
5309         parseList[endPV-1][0] = NULLCHAR;
5310   } while(valid);
5311   currentMove = endPV;
5312   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5313   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5314                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5315   DrawPosition(TRUE, boards[currentMove]);
5316 }
5317
5318 Boolean
5319 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5320 {
5321         int startPV;
5322         char *p;
5323
5324         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5325         lastX = x; lastY = y;
5326         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5327         startPV = index;
5328         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5329         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5330         index = startPV;
5331         do{ while(buf[index] && buf[index] != '\n') index++;
5332         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5333         buf[index] = 0;
5334         ParsePV(buf+startPV, FALSE);
5335         *start = startPV; *end = index-1;
5336         return TRUE;
5337 }
5338
5339 Boolean
5340 LoadPV(int x, int y)
5341 { // called on right mouse click to load PV
5342   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5343   lastX = x; lastY = y;
5344   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5345   return TRUE;
5346 }
5347
5348 void
5349 UnLoadPV()
5350 {
5351   if(endPV < 0) return;
5352   endPV = -1;
5353   currentMove = forwardMostMove;
5354   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5355   ClearPremoveHighlights();
5356   DrawPosition(TRUE, boards[currentMove]);
5357 }
5358
5359 void
5360 MovePV(int x, int y, int h)
5361 { // step through PV based on mouse coordinates (called on mouse move)
5362   int margin = h>>3, step = 0;
5363
5364   // we must somehow check if right button is still down (might be released off board!)
5365   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5366   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5367   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5368   if(!step) return;
5369   lastX = x; lastY = y;
5370
5371   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5372   if(endPV < 0) return;
5373   if(y < margin) step = 1; else
5374   if(y > h - margin) step = -1;
5375   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5376   currentMove += step;
5377   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5378   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5379                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5380   DrawPosition(FALSE, boards[currentMove]);
5381 }
5382
5383
5384 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5385 // All positions will have equal probability, but the current method will not provide a unique
5386 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5387 #define DARK 1
5388 #define LITE 2
5389 #define ANY 3
5390
5391 int squaresLeft[4];
5392 int piecesLeft[(int)BlackPawn];
5393 int seed, nrOfShuffles;
5394
5395 void GetPositionNumber()
5396 {       // sets global variable seed
5397         int i;
5398
5399         seed = appData.defaultFrcPosition;
5400         if(seed < 0) { // randomize based on time for negative FRC position numbers
5401                 for(i=0; i<50; i++) seed += random();
5402                 seed = random() ^ random() >> 8 ^ random() << 8;
5403                 if(seed<0) seed = -seed;
5404         }
5405 }
5406
5407 int put(Board board, int pieceType, int rank, int n, int shade)
5408 // put the piece on the (n-1)-th empty squares of the given shade
5409 {
5410         int i;
5411
5412         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5413                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5414                         board[rank][i] = (ChessSquare) pieceType;
5415                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5416                         squaresLeft[ANY]--;
5417                         piecesLeft[pieceType]--;
5418                         return i;
5419                 }
5420         }
5421         return -1;
5422 }
5423
5424
5425 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5426 // calculate where the next piece goes, (any empty square), and put it there
5427 {
5428         int i;
5429
5430         i = seed % squaresLeft[shade];
5431         nrOfShuffles *= squaresLeft[shade];
5432         seed /= squaresLeft[shade];
5433         put(board, pieceType, rank, i, shade);
5434 }
5435
5436 void AddTwoPieces(Board board, int pieceType, int rank)
5437 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5438 {
5439         int i, n=squaresLeft[ANY], j=n-1, k;
5440
5441         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5442         i = seed % k;  // pick one
5443         nrOfShuffles *= k;
5444         seed /= k;
5445         while(i >= j) i -= j--;
5446         j = n - 1 - j; i += j;
5447         put(board, pieceType, rank, j, ANY);
5448         put(board, pieceType, rank, i, ANY);
5449 }
5450
5451 void SetUpShuffle(Board board, int number)
5452 {
5453         int i, p, first=1;
5454
5455         GetPositionNumber(); nrOfShuffles = 1;
5456
5457         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5458         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5459         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5460
5461         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5462
5463         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5464             p = (int) board[0][i];
5465             if(p < (int) BlackPawn) piecesLeft[p] ++;
5466             board[0][i] = EmptySquare;
5467         }
5468
5469         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5470             // shuffles restricted to allow normal castling put KRR first
5471             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5472                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5473             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5474                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5475             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5476                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5477             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5478                 put(board, WhiteRook, 0, 0, ANY);
5479             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5480         }
5481
5482         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5483             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5484             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5485                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5486                 while(piecesLeft[p] >= 2) {
5487                     AddOnePiece(board, p, 0, LITE);
5488                     AddOnePiece(board, p, 0, DARK);
5489                 }
5490                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5491             }
5492
5493         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5494             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5495             // but we leave King and Rooks for last, to possibly obey FRC restriction
5496             if(p == (int)WhiteRook) continue;
5497             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5498             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5499         }
5500
5501         // now everything is placed, except perhaps King (Unicorn) and Rooks
5502
5503         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5504             // Last King gets castling rights
5505             while(piecesLeft[(int)WhiteUnicorn]) {
5506                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5507                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5508             }
5509
5510             while(piecesLeft[(int)WhiteKing]) {
5511                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5512                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5513             }
5514
5515
5516         } else {
5517             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5518             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5519         }
5520
5521         // Only Rooks can be left; simply place them all
5522         while(piecesLeft[(int)WhiteRook]) {
5523                 i = put(board, WhiteRook, 0, 0, ANY);
5524                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5525                         if(first) {
5526                                 first=0;
5527                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5528                         }
5529                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5530                 }
5531         }
5532         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5533             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5534         }
5535
5536         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5537 }
5538
5539 int SetCharTable( char *table, const char * map )
5540 /* [HGM] moved here from winboard.c because of its general usefulness */
5541 /*       Basically a safe strcpy that uses the last character as King */
5542 {
5543     int result = FALSE; int NrPieces;
5544
5545     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5546                     && NrPieces >= 12 && !(NrPieces&1)) {
5547         int i; /* [HGM] Accept even length from 12 to 34 */
5548
5549         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5550         for( i=0; i<NrPieces/2-1; i++ ) {
5551             table[i] = map[i];
5552             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5553         }
5554         table[(int) WhiteKing]  = map[NrPieces/2-1];
5555         table[(int) BlackKing]  = map[NrPieces-1];
5556
5557         result = TRUE;
5558     }
5559
5560     return result;
5561 }
5562
5563 void Prelude(Board board)
5564 {       // [HGM] superchess: random selection of exo-pieces
5565         int i, j, k; ChessSquare p;
5566         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5567
5568         GetPositionNumber(); // use FRC position number
5569
5570         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5571             SetCharTable(pieceToChar, appData.pieceToCharTable);
5572             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5573                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5574         }
5575
5576         j = seed%4;                 seed /= 4;
5577         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5578         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5579         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5580         j = seed%3 + (seed%3 >= j); seed /= 3;
5581         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5582         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5583         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5584         j = seed%3;                 seed /= 3;
5585         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5586         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5587         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5588         j = seed%2 + (seed%2 >= j); seed /= 2;
5589         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5590         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5591         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5592         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5593         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5594         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5595         put(board, exoPieces[0],    0, 0, ANY);
5596         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5597 }
5598
5599 void
5600 InitPosition(redraw)
5601      int redraw;
5602 {
5603     ChessSquare (* pieces)[BOARD_FILES];
5604     int i, j, pawnRow, overrule,
5605     oldx = gameInfo.boardWidth,
5606     oldy = gameInfo.boardHeight,
5607     oldh = gameInfo.holdingsWidth;
5608     static int oldv;
5609
5610     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5611
5612     /* [AS] Initialize pv info list [HGM] and game status */
5613     {
5614         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5615             pvInfoList[i].depth = 0;
5616             boards[i][EP_STATUS] = EP_NONE;
5617             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5618         }
5619
5620         initialRulePlies = 0; /* 50-move counter start */
5621
5622         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5623         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5624     }
5625
5626
5627     /* [HGM] logic here is completely changed. In stead of full positions */
5628     /* the initialized data only consist of the two backranks. The switch */
5629     /* selects which one we will use, which is than copied to the Board   */
5630     /* initialPosition, which for the rest is initialized by Pawns and    */
5631     /* empty squares. This initial position is then copied to boards[0],  */
5632     /* possibly after shuffling, so that it remains available.            */
5633
5634     gameInfo.holdingsWidth = 0; /* default board sizes */
5635     gameInfo.boardWidth    = 8;
5636     gameInfo.boardHeight   = 8;
5637     gameInfo.holdingsSize  = 0;
5638     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5639     for(i=0; i<BOARD_FILES-2; i++)
5640       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5641     initialPosition[EP_STATUS] = EP_NONE;
5642     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5643     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5644          SetCharTable(pieceNickName, appData.pieceNickNames);
5645     else SetCharTable(pieceNickName, "............");
5646     pieces = FIDEArray;
5647
5648     switch (gameInfo.variant) {
5649     case VariantFischeRandom:
5650       shuffleOpenings = TRUE;
5651     default:
5652       break;
5653     case VariantShatranj:
5654       pieces = ShatranjArray;
5655       nrCastlingRights = 0;
5656       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5657       break;
5658     case VariantMakruk:
5659       pieces = makrukArray;
5660       nrCastlingRights = 0;
5661       startedFromSetupPosition = TRUE;
5662       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5663       break;
5664     case VariantTwoKings:
5665       pieces = twoKingsArray;
5666       break;
5667     case VariantCapaRandom:
5668       shuffleOpenings = TRUE;
5669     case VariantCapablanca:
5670       pieces = CapablancaArray;
5671       gameInfo.boardWidth = 10;
5672       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5673       break;
5674     case VariantGothic:
5675       pieces = GothicArray;
5676       gameInfo.boardWidth = 10;
5677       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5678       break;
5679     case VariantSChess:
5680       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5681       gameInfo.holdingsSize = 7;
5682       break;
5683     case VariantJanus:
5684       pieces = JanusArray;
5685       gameInfo.boardWidth = 10;
5686       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5687       nrCastlingRights = 6;
5688         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5689         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5690         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5691         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5692         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5693         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5694       break;
5695     case VariantFalcon:
5696       pieces = FalconArray;
5697       gameInfo.boardWidth = 10;
5698       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5699       break;
5700     case VariantXiangqi:
5701       pieces = XiangqiArray;
5702       gameInfo.boardWidth  = 9;
5703       gameInfo.boardHeight = 10;
5704       nrCastlingRights = 0;
5705       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5706       break;
5707     case VariantShogi:
5708       pieces = ShogiArray;
5709       gameInfo.boardWidth  = 9;
5710       gameInfo.boardHeight = 9;
5711       gameInfo.holdingsSize = 7;
5712       nrCastlingRights = 0;
5713       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5714       break;
5715     case VariantCourier:
5716       pieces = CourierArray;
5717       gameInfo.boardWidth  = 12;
5718       nrCastlingRights = 0;
5719       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5720       break;
5721     case VariantKnightmate:
5722       pieces = KnightmateArray;
5723       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5724       break;
5725     case VariantSpartan:
5726       pieces = SpartanArray;
5727       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5728       break;
5729     case VariantFairy:
5730       pieces = fairyArray;
5731       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5732       break;
5733     case VariantGreat:
5734       pieces = GreatArray;
5735       gameInfo.boardWidth = 10;
5736       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5737       gameInfo.holdingsSize = 8;
5738       break;
5739     case VariantSuper:
5740       pieces = FIDEArray;
5741       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5742       gameInfo.holdingsSize = 8;
5743       startedFromSetupPosition = TRUE;
5744       break;
5745     case VariantCrazyhouse:
5746     case VariantBughouse:
5747       pieces = FIDEArray;
5748       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5749       gameInfo.holdingsSize = 5;
5750       break;
5751     case VariantWildCastle:
5752       pieces = FIDEArray;
5753       /* !!?shuffle with kings guaranteed to be on d or e file */
5754       shuffleOpenings = 1;
5755       break;
5756     case VariantNoCastle:
5757       pieces = FIDEArray;
5758       nrCastlingRights = 0;
5759       /* !!?unconstrained back-rank shuffle */
5760       shuffleOpenings = 1;
5761       break;
5762     }
5763
5764     overrule = 0;
5765     if(appData.NrFiles >= 0) {
5766         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5767         gameInfo.boardWidth = appData.NrFiles;
5768     }
5769     if(appData.NrRanks >= 0) {
5770         gameInfo.boardHeight = appData.NrRanks;
5771     }
5772     if(appData.holdingsSize >= 0) {
5773         i = appData.holdingsSize;
5774         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5775         gameInfo.holdingsSize = i;
5776     }
5777     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5778     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5779         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5780
5781     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5782     if(pawnRow < 1) pawnRow = 1;
5783     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5784
5785     /* User pieceToChar list overrules defaults */
5786     if(appData.pieceToCharTable != NULL)
5787         SetCharTable(pieceToChar, appData.pieceToCharTable);
5788
5789     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5790
5791         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5792             s = (ChessSquare) 0; /* account holding counts in guard band */
5793         for( i=0; i<BOARD_HEIGHT; i++ )
5794             initialPosition[i][j] = s;
5795
5796         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5797         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5798         initialPosition[pawnRow][j] = WhitePawn;
5799         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5800         if(gameInfo.variant == VariantXiangqi) {
5801             if(j&1) {
5802                 initialPosition[pawnRow][j] =
5803                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5804                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5805                    initialPosition[2][j] = WhiteCannon;
5806                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5807                 }
5808             }
5809         }
5810         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5811     }
5812     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5813
5814             j=BOARD_LEFT+1;
5815             initialPosition[1][j] = WhiteBishop;
5816             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5817             j=BOARD_RGHT-2;
5818             initialPosition[1][j] = WhiteRook;
5819             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5820     }
5821
5822     if( nrCastlingRights == -1) {
5823         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5824         /*       This sets default castling rights from none to normal corners   */
5825         /* Variants with other castling rights must set them themselves above    */
5826         nrCastlingRights = 6;
5827
5828         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5829         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5830         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5831         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5832         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5833         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5834      }
5835
5836      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5837      if(gameInfo.variant == VariantGreat) { // promotion commoners
5838         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5839         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5840         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5841         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5842      }
5843      if( gameInfo.variant == VariantSChess ) {
5844       initialPosition[1][0] = BlackMarshall;
5845       initialPosition[2][0] = BlackAngel;
5846       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5847       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5848       initialPosition[1][1] = initialPosition[2][1] = 
5849       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5850      }
5851   if (appData.debugMode) {
5852     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5853   }
5854     if(shuffleOpenings) {
5855         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5856         startedFromSetupPosition = TRUE;
5857     }
5858     if(startedFromPositionFile) {
5859       /* [HGM] loadPos: use PositionFile for every new game */
5860       CopyBoard(initialPosition, filePosition);
5861       for(i=0; i<nrCastlingRights; i++)
5862           initialRights[i] = filePosition[CASTLING][i];
5863       startedFromSetupPosition = TRUE;
5864     }
5865
5866     CopyBoard(boards[0], initialPosition);
5867
5868     if(oldx != gameInfo.boardWidth ||
5869        oldy != gameInfo.boardHeight ||
5870        oldv != gameInfo.variant ||
5871        oldh != gameInfo.holdingsWidth
5872                                          )
5873             InitDrawingSizes(-2 ,0);
5874
5875     oldv = gameInfo.variant;
5876     if (redraw)
5877       DrawPosition(TRUE, boards[currentMove]);
5878 }
5879
5880 void
5881 SendBoard(cps, moveNum)
5882      ChessProgramState *cps;
5883      int moveNum;
5884 {
5885     char message[MSG_SIZ];
5886
5887     if (cps->useSetboard) {
5888       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5889       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5890       SendToProgram(message, cps);
5891       free(fen);
5892
5893     } else {
5894       ChessSquare *bp;
5895       int i, j;
5896       /* Kludge to set black to move, avoiding the troublesome and now
5897        * deprecated "black" command.
5898        */
5899       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5900         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5901
5902       SendToProgram("edit\n", cps);
5903       SendToProgram("#\n", cps);
5904       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5905         bp = &boards[moveNum][i][BOARD_LEFT];
5906         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5907           if ((int) *bp < (int) BlackPawn) {
5908             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5909                     AAA + j, ONE + i);
5910             if(message[0] == '+' || message[0] == '~') {
5911               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5912                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5913                         AAA + j, ONE + i);
5914             }
5915             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5916                 message[1] = BOARD_RGHT   - 1 - j + '1';
5917                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5918             }
5919             SendToProgram(message, cps);
5920           }
5921         }
5922       }
5923
5924       SendToProgram("c\n", cps);
5925       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5926         bp = &boards[moveNum][i][BOARD_LEFT];
5927         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5928           if (((int) *bp != (int) EmptySquare)
5929               && ((int) *bp >= (int) BlackPawn)) {
5930             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5931                     AAA + j, ONE + i);
5932             if(message[0] == '+' || message[0] == '~') {
5933               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5934                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5935                         AAA + j, ONE + i);
5936             }
5937             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5938                 message[1] = BOARD_RGHT   - 1 - j + '1';
5939                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5940             }
5941             SendToProgram(message, cps);
5942           }
5943         }
5944       }
5945
5946       SendToProgram(".\n", cps);
5947     }
5948     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5949 }
5950
5951 ChessSquare
5952 DefaultPromoChoice(int white)
5953 {
5954     ChessSquare result;
5955     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5956         result = WhiteFerz; // no choice
5957     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5958         result= WhiteKing; // in Suicide Q is the last thing we want
5959     else if(gameInfo.variant == VariantSpartan)
5960         result = white ? WhiteQueen : WhiteAngel;
5961     else result = WhiteQueen;
5962     if(!white) result = WHITE_TO_BLACK result;
5963     return result;
5964 }
5965
5966 static int autoQueen; // [HGM] oneclick
5967
5968 int
5969 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5970 {
5971     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5972     /* [HGM] add Shogi promotions */
5973     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5974     ChessSquare piece;
5975     ChessMove moveType;
5976     Boolean premove;
5977
5978     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5979     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5980
5981     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5982       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5983         return FALSE;
5984
5985     piece = boards[currentMove][fromY][fromX];
5986     if(gameInfo.variant == VariantShogi) {
5987         promotionZoneSize = BOARD_HEIGHT/3;
5988         highestPromotingPiece = (int)WhiteFerz;
5989     } else if(gameInfo.variant == VariantMakruk) {
5990         promotionZoneSize = 3;
5991     }
5992
5993     // Treat Lance as Pawn when it is not representing Amazon
5994     if(gameInfo.variant != VariantSuper) {
5995         if(piece == WhiteLance) piece = WhitePawn; else
5996         if(piece == BlackLance) piece = BlackPawn;
5997     }
5998
5999     // next weed out all moves that do not touch the promotion zone at all
6000     if((int)piece >= BlackPawn) {
6001         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6002              return FALSE;
6003         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6004     } else {
6005         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6006            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6007     }
6008
6009     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6010
6011     // weed out mandatory Shogi promotions
6012     if(gameInfo.variant == VariantShogi) {
6013         if(piece >= BlackPawn) {
6014             if(toY == 0 && piece == BlackPawn ||
6015                toY == 0 && piece == BlackQueen ||
6016                toY <= 1 && piece == BlackKnight) {
6017                 *promoChoice = '+';
6018                 return FALSE;
6019             }
6020         } else {
6021             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6022                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6023                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6024                 *promoChoice = '+';
6025                 return FALSE;
6026             }
6027         }
6028     }
6029
6030     // weed out obviously illegal Pawn moves
6031     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6032         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6033         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6034         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6035         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6036         // note we are not allowed to test for valid (non-)capture, due to premove
6037     }
6038
6039     // we either have a choice what to promote to, or (in Shogi) whether to promote
6040     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6041         *promoChoice = PieceToChar(BlackFerz);  // no choice
6042         return FALSE;
6043     }
6044     // no sense asking what we must promote to if it is going to explode...
6045     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6046         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6047         return FALSE;
6048     }
6049     // give caller the default choice even if we will not make it
6050     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6051     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6052     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6053                            && gameInfo.variant != VariantShogi
6054                            && gameInfo.variant != VariantSuper) return FALSE;
6055     if(autoQueen) return FALSE; // predetermined
6056
6057     // suppress promotion popup on illegal moves that are not premoves
6058     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6059               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6060     if(appData.testLegality && !premove) {
6061         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6062                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6063         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6064             return FALSE;
6065     }
6066
6067     return TRUE;
6068 }
6069
6070 int
6071 InPalace(row, column)
6072      int row, column;
6073 {   /* [HGM] for Xiangqi */
6074     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6075          column < (BOARD_WIDTH + 4)/2 &&
6076          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6077     return FALSE;
6078 }
6079
6080 int
6081 PieceForSquare (x, y)
6082      int x;
6083      int y;
6084 {
6085   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6086      return -1;
6087   else
6088      return boards[currentMove][y][x];
6089 }
6090
6091 int
6092 OKToStartUserMove(x, y)
6093      int x, y;
6094 {
6095     ChessSquare from_piece;
6096     int white_piece;
6097
6098     if (matchMode) return FALSE;
6099     if (gameMode == EditPosition) return TRUE;
6100
6101     if (x >= 0 && y >= 0)
6102       from_piece = boards[currentMove][y][x];
6103     else
6104       from_piece = EmptySquare;
6105
6106     if (from_piece == EmptySquare) return FALSE;
6107
6108     white_piece = (int)from_piece >= (int)WhitePawn &&
6109       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6110
6111     switch (gameMode) {
6112       case PlayFromGameFile:
6113       case AnalyzeFile:
6114       case TwoMachinesPlay:
6115       case EndOfGame:
6116         return FALSE;
6117
6118       case IcsObserving:
6119       case IcsIdle:
6120         return FALSE;
6121
6122       case MachinePlaysWhite:
6123       case IcsPlayingBlack:
6124         if (appData.zippyPlay) return FALSE;
6125         if (white_piece) {
6126             DisplayMoveError(_("You are playing Black"));
6127             return FALSE;
6128         }
6129         break;
6130
6131       case MachinePlaysBlack:
6132       case IcsPlayingWhite:
6133         if (appData.zippyPlay) return FALSE;
6134         if (!white_piece) {
6135             DisplayMoveError(_("You are playing White"));
6136             return FALSE;
6137         }
6138         break;
6139
6140       case EditGame:
6141         if (!white_piece && WhiteOnMove(currentMove)) {
6142             DisplayMoveError(_("It is White's turn"));
6143             return FALSE;
6144         }
6145         if (white_piece && !WhiteOnMove(currentMove)) {
6146             DisplayMoveError(_("It is Black's turn"));
6147             return FALSE;
6148         }
6149         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6150             /* Editing correspondence game history */
6151             /* Could disallow this or prompt for confirmation */
6152             cmailOldMove = -1;
6153         }
6154         break;
6155
6156       case BeginningOfGame:
6157         if (appData.icsActive) return FALSE;
6158         if (!appData.noChessProgram) {
6159             if (!white_piece) {
6160                 DisplayMoveError(_("You are playing White"));
6161                 return FALSE;
6162             }
6163         }
6164         break;
6165
6166       case Training:
6167         if (!white_piece && WhiteOnMove(currentMove)) {
6168             DisplayMoveError(_("It is White's turn"));
6169             return FALSE;
6170         }
6171         if (white_piece && !WhiteOnMove(currentMove)) {
6172             DisplayMoveError(_("It is Black's turn"));
6173             return FALSE;
6174         }
6175         break;
6176
6177       default:
6178       case IcsExamining:
6179         break;
6180     }
6181     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6182         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6183         && gameMode != AnalyzeFile && gameMode != Training) {
6184         DisplayMoveError(_("Displayed position is not current"));
6185         return FALSE;
6186     }
6187     return TRUE;
6188 }
6189
6190 Boolean
6191 OnlyMove(int *x, int *y, Boolean captures) {
6192     DisambiguateClosure cl;
6193     if (appData.zippyPlay) return FALSE;
6194     switch(gameMode) {
6195       case MachinePlaysBlack:
6196       case IcsPlayingWhite:
6197       case BeginningOfGame:
6198         if(!WhiteOnMove(currentMove)) return FALSE;
6199         break;
6200       case MachinePlaysWhite:
6201       case IcsPlayingBlack:
6202         if(WhiteOnMove(currentMove)) return FALSE;
6203         break;
6204       case EditGame:
6205         break;
6206       default:
6207         return FALSE;
6208     }
6209     cl.pieceIn = EmptySquare;
6210     cl.rfIn = *y;
6211     cl.ffIn = *x;
6212     cl.rtIn = -1;
6213     cl.ftIn = -1;
6214     cl.promoCharIn = NULLCHAR;
6215     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6216     if( cl.kind == NormalMove ||
6217         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6218         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6219         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6220       fromX = cl.ff;
6221       fromY = cl.rf;
6222       *x = cl.ft;
6223       *y = cl.rt;
6224       return TRUE;
6225     }
6226     if(cl.kind != ImpossibleMove) return FALSE;
6227     cl.pieceIn = EmptySquare;
6228     cl.rfIn = -1;
6229     cl.ffIn = -1;
6230     cl.rtIn = *y;
6231     cl.ftIn = *x;
6232     cl.promoCharIn = NULLCHAR;
6233     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6234     if( cl.kind == NormalMove ||
6235         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6236         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6237         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6238       fromX = cl.ff;
6239       fromY = cl.rf;
6240       *x = cl.ft;
6241       *y = cl.rt;
6242       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6243       return TRUE;
6244     }
6245     return FALSE;
6246 }
6247
6248 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6249 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6250 int lastLoadGameUseList = FALSE;
6251 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6252 ChessMove lastLoadGameStart = EndOfFile;
6253
6254 void
6255 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6256      int fromX, fromY, toX, toY;
6257      int promoChar;
6258 {
6259     ChessMove moveType;
6260     ChessSquare pdown, pup;
6261
6262     /* Check if the user is playing in turn.  This is complicated because we
6263        let the user "pick up" a piece before it is his turn.  So the piece he
6264        tried to pick up may have been captured by the time he puts it down!
6265        Therefore we use the color the user is supposed to be playing in this
6266        test, not the color of the piece that is currently on the starting
6267        square---except in EditGame mode, where the user is playing both
6268        sides; fortunately there the capture race can't happen.  (It can
6269        now happen in IcsExamining mode, but that's just too bad.  The user
6270        will get a somewhat confusing message in that case.)
6271        */
6272
6273     switch (gameMode) {
6274       case PlayFromGameFile:
6275       case AnalyzeFile:
6276       case TwoMachinesPlay:
6277       case EndOfGame:
6278       case IcsObserving:
6279       case IcsIdle:
6280         /* We switched into a game mode where moves are not accepted,
6281            perhaps while the mouse button was down. */
6282         return;
6283
6284       case MachinePlaysWhite:
6285         /* User is moving for Black */
6286         if (WhiteOnMove(currentMove)) {
6287             DisplayMoveError(_("It is White's turn"));
6288             return;
6289         }
6290         break;
6291
6292       case MachinePlaysBlack:
6293         /* User is moving for White */
6294         if (!WhiteOnMove(currentMove)) {
6295             DisplayMoveError(_("It is Black's turn"));
6296             return;
6297         }
6298         break;
6299
6300       case EditGame:
6301       case IcsExamining:
6302       case BeginningOfGame:
6303       case AnalyzeMode:
6304       case Training:
6305         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6306         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6307             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6308             /* User is moving for Black */
6309             if (WhiteOnMove(currentMove)) {
6310                 DisplayMoveError(_("It is White's turn"));
6311                 return;
6312             }
6313         } else {
6314             /* User is moving for White */
6315             if (!WhiteOnMove(currentMove)) {
6316                 DisplayMoveError(_("It is Black's turn"));
6317                 return;
6318             }
6319         }
6320         break;
6321
6322       case IcsPlayingBlack:
6323         /* User is moving for Black */
6324         if (WhiteOnMove(currentMove)) {
6325             if (!appData.premove) {
6326                 DisplayMoveError(_("It is White's turn"));
6327             } else if (toX >= 0 && toY >= 0) {
6328                 premoveToX = toX;
6329                 premoveToY = toY;
6330                 premoveFromX = fromX;
6331                 premoveFromY = fromY;
6332                 premovePromoChar = promoChar;
6333                 gotPremove = 1;
6334                 if (appData.debugMode)
6335                     fprintf(debugFP, "Got premove: fromX %d,"
6336                             "fromY %d, toX %d, toY %d\n",
6337                             fromX, fromY, toX, toY);
6338             }
6339             return;
6340         }
6341         break;
6342
6343       case IcsPlayingWhite:
6344         /* User is moving for White */
6345         if (!WhiteOnMove(currentMove)) {
6346             if (!appData.premove) {
6347                 DisplayMoveError(_("It is Black's turn"));
6348             } else if (toX >= 0 && toY >= 0) {
6349                 premoveToX = toX;
6350                 premoveToY = toY;
6351                 premoveFromX = fromX;
6352                 premoveFromY = fromY;
6353                 premovePromoChar = promoChar;
6354                 gotPremove = 1;
6355                 if (appData.debugMode)
6356                     fprintf(debugFP, "Got premove: fromX %d,"
6357                             "fromY %d, toX %d, toY %d\n",
6358                             fromX, fromY, toX, toY);
6359             }
6360             return;
6361         }
6362         break;
6363
6364       default:
6365         break;
6366
6367       case EditPosition:
6368         /* EditPosition, empty square, or different color piece;
6369            click-click move is possible */
6370         if (toX == -2 || toY == -2) {
6371             boards[0][fromY][fromX] = EmptySquare;
6372             DrawPosition(FALSE, boards[currentMove]);
6373             return;
6374         } else if (toX >= 0 && toY >= 0) {
6375             boards[0][toY][toX] = boards[0][fromY][fromX];
6376             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6377                 if(boards[0][fromY][0] != EmptySquare) {
6378                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6379                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6380                 }
6381             } else
6382             if(fromX == BOARD_RGHT+1) {
6383                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6384                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6385                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6386                 }
6387             } else
6388             boards[0][fromY][fromX] = EmptySquare;
6389             DrawPosition(FALSE, boards[currentMove]);
6390             return;
6391         }
6392         return;
6393     }
6394
6395     if(toX < 0 || toY < 0) return;
6396     pdown = boards[currentMove][fromY][fromX];
6397     pup = boards[currentMove][toY][toX];
6398
6399     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6400     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6401          if( pup != EmptySquare ) return;
6402          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6403            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6404                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6405            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6406            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6407            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6408            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6409          fromY = DROP_RANK;
6410     }
6411
6412     /* [HGM] always test for legality, to get promotion info */
6413     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6414                                          fromY, fromX, toY, toX, promoChar);
6415     /* [HGM] but possibly ignore an IllegalMove result */
6416     if (appData.testLegality) {
6417         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6418             DisplayMoveError(_("Illegal move"));
6419             return;
6420         }
6421     }
6422
6423     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6424 }
6425
6426 /* Common tail of UserMoveEvent and DropMenuEvent */
6427 int
6428 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6429      ChessMove moveType;
6430      int fromX, fromY, toX, toY;
6431      /*char*/int promoChar;
6432 {
6433     char *bookHit = 0;
6434
6435     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6436         // [HGM] superchess: suppress promotions to non-available piece
6437         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6438         if(WhiteOnMove(currentMove)) {
6439             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6440         } else {
6441             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6442         }
6443     }
6444
6445     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6446        move type in caller when we know the move is a legal promotion */
6447     if(moveType == NormalMove && promoChar)
6448         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6449
6450     /* [HGM] <popupFix> The following if has been moved here from
6451        UserMoveEvent(). Because it seemed to belong here (why not allow
6452        piece drops in training games?), and because it can only be
6453        performed after it is known to what we promote. */
6454     if (gameMode == Training) {
6455       /* compare the move played on the board to the next move in the
6456        * game. If they match, display the move and the opponent's response.
6457        * If they don't match, display an error message.
6458        */
6459       int saveAnimate;
6460       Board testBoard;
6461       CopyBoard(testBoard, boards[currentMove]);
6462       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6463
6464       if (CompareBoards(testBoard, boards[currentMove+1])) {
6465         ForwardInner(currentMove+1);
6466
6467         /* Autoplay the opponent's response.
6468          * if appData.animate was TRUE when Training mode was entered,
6469          * the response will be animated.
6470          */
6471         saveAnimate = appData.animate;
6472         appData.animate = animateTraining;
6473         ForwardInner(currentMove+1);
6474         appData.animate = saveAnimate;
6475
6476         /* check for the end of the game */
6477         if (currentMove >= forwardMostMove) {
6478           gameMode = PlayFromGameFile;
6479           ModeHighlight();
6480           SetTrainingModeOff();
6481           DisplayInformation(_("End of game"));
6482         }
6483       } else {
6484         DisplayError(_("Incorrect move"), 0);
6485       }
6486       return 1;
6487     }
6488
6489   /* Ok, now we know that the move is good, so we can kill
6490      the previous line in Analysis Mode */
6491   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6492                                 && currentMove < forwardMostMove) {
6493     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6494     else forwardMostMove = currentMove;
6495   }
6496
6497   /* If we need the chess program but it's dead, restart it */
6498   ResurrectChessProgram();
6499
6500   /* A user move restarts a paused game*/
6501   if (pausing)
6502     PauseEvent();
6503
6504   thinkOutput[0] = NULLCHAR;
6505
6506   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6507
6508   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6509     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6510     return 1;
6511   }
6512
6513   if (gameMode == BeginningOfGame) {
6514     if (appData.noChessProgram) {
6515       gameMode = EditGame;
6516       SetGameInfo();
6517     } else {
6518       char buf[MSG_SIZ];
6519       gameMode = MachinePlaysBlack;
6520       StartClocks();
6521       SetGameInfo();
6522       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6523       DisplayTitle(buf);
6524       if (first.sendName) {
6525         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6526         SendToProgram(buf, &first);
6527       }
6528       StartClocks();
6529     }
6530     ModeHighlight();
6531   }
6532
6533   /* Relay move to ICS or chess engine */
6534   if (appData.icsActive) {
6535     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6536         gameMode == IcsExamining) {
6537       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6538         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6539         SendToICS("draw ");
6540         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6541       }
6542       // also send plain move, in case ICS does not understand atomic claims
6543       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6544       ics_user_moved = 1;
6545     }
6546   } else {
6547     if (first.sendTime && (gameMode == BeginningOfGame ||
6548                            gameMode == MachinePlaysWhite ||
6549                            gameMode == MachinePlaysBlack)) {
6550       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6551     }
6552     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6553          // [HGM] book: if program might be playing, let it use book
6554         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6555         first.maybeThinking = TRUE;
6556     } else SendMoveToProgram(forwardMostMove-1, &first);
6557     if (currentMove == cmailOldMove + 1) {
6558       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6559     }
6560   }
6561
6562   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6563
6564   switch (gameMode) {
6565   case EditGame:
6566     if(appData.testLegality)
6567     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6568     case MT_NONE:
6569     case MT_CHECK:
6570       break;
6571     case MT_CHECKMATE:
6572     case MT_STAINMATE:
6573       if (WhiteOnMove(currentMove)) {
6574         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6575       } else {
6576         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6577       }
6578       break;
6579     case MT_STALEMATE:
6580       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6581       break;
6582     }
6583     break;
6584
6585   case MachinePlaysBlack:
6586   case MachinePlaysWhite:
6587     /* disable certain menu options while machine is thinking */
6588     SetMachineThinkingEnables();
6589     break;
6590
6591   default:
6592     break;
6593   }
6594
6595   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6596   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6597
6598   if(bookHit) { // [HGM] book: simulate book reply
6599         static char bookMove[MSG_SIZ]; // a bit generous?
6600
6601         programStats.nodes = programStats.depth = programStats.time =
6602         programStats.score = programStats.got_only_move = 0;
6603         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6604
6605         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6606         strcat(bookMove, bookHit);
6607         HandleMachineMove(bookMove, &first);
6608   }
6609   return 1;
6610 }
6611
6612 void
6613 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6614      Board board;
6615      int flags;
6616      ChessMove kind;
6617      int rf, ff, rt, ft;
6618      VOIDSTAR closure;
6619 {
6620     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6621     Markers *m = (Markers *) closure;
6622     if(rf == fromY && ff == fromX)
6623         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6624                          || kind == WhiteCapturesEnPassant
6625                          || kind == BlackCapturesEnPassant);
6626     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6627 }
6628
6629 void
6630 MarkTargetSquares(int clear)
6631 {
6632   int x, y;
6633   if(!appData.markers || !appData.highlightDragging ||
6634      !appData.testLegality || gameMode == EditPosition) return;
6635   if(clear) {
6636     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6637   } else {
6638     int capt = 0;
6639     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6640     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6641       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6642       if(capt)
6643       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6644     }
6645   }
6646   DrawPosition(TRUE, NULL);
6647 }
6648
6649 int
6650 Explode(Board board, int fromX, int fromY, int toX, int toY)
6651 {
6652     if(gameInfo.variant == VariantAtomic &&
6653        (board[toY][toX] != EmptySquare ||                     // capture?
6654         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6655                          board[fromY][fromX] == BlackPawn   )
6656       )) {
6657         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6658         return TRUE;
6659     }
6660     return FALSE;
6661 }
6662
6663 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6664
6665 int CanPromote(ChessSquare piece, int y)
6666 {
6667         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6668         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6669         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6670            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6671            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6672                                                   gameInfo.variant == VariantMakruk) return FALSE;
6673         return (piece == BlackPawn && y == 1 ||
6674                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6675                 piece == BlackLance && y == 1 ||
6676                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6677 }
6678
6679 void LeftClick(ClickType clickType, int xPix, int yPix)
6680 {
6681     int x, y;
6682     Boolean saveAnimate;
6683     static int second = 0, promotionChoice = 0, clearFlag = 0;
6684     char promoChoice = NULLCHAR;
6685     ChessSquare piece;
6686
6687     if(appData.seekGraph && appData.icsActive && loggedOn &&
6688         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6689         SeekGraphClick(clickType, xPix, yPix, 0);
6690         return;
6691     }
6692
6693     if (clickType == Press) ErrorPopDown();
6694     MarkTargetSquares(1);
6695
6696     x = EventToSquare(xPix, BOARD_WIDTH);
6697     y = EventToSquare(yPix, BOARD_HEIGHT);
6698     if (!flipView && y >= 0) {
6699         y = BOARD_HEIGHT - 1 - y;
6700     }
6701     if (flipView && x >= 0) {
6702         x = BOARD_WIDTH - 1 - x;
6703     }
6704
6705     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6706         defaultPromoChoice = promoSweep;
6707         promoSweep = EmptySquare;   // terminate sweep
6708         promoDefaultAltered = TRUE;
6709         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6710     }
6711
6712     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6713         if(clickType == Release) return; // ignore upclick of click-click destination
6714         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6715         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6716         if(gameInfo.holdingsWidth &&
6717                 (WhiteOnMove(currentMove)
6718                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6719                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6720             // click in right holdings, for determining promotion piece
6721             ChessSquare p = boards[currentMove][y][x];
6722             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6723             if(p != EmptySquare) {
6724                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6725                 fromX = fromY = -1;
6726                 return;
6727             }
6728         }
6729         DrawPosition(FALSE, boards[currentMove]);
6730         return;
6731     }
6732
6733     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6734     if(clickType == Press
6735             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6736               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6737               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6738         return;
6739
6740     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6741         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6742
6743     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6744         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6745                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6746         defaultPromoChoice = DefaultPromoChoice(side);
6747     }
6748
6749     autoQueen = appData.alwaysPromoteToQueen;
6750
6751     if (fromX == -1) {
6752       int originalY = y;
6753       gatingPiece = EmptySquare;
6754       if (clickType != Press) {
6755         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6756             DragPieceEnd(xPix, yPix); dragging = 0;
6757             DrawPosition(FALSE, NULL);
6758         }
6759         return;
6760       }
6761       fromX = x; fromY = y;
6762       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6763          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6764          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6765             /* First square */
6766             if (OKToStartUserMove(fromX, fromY)) {
6767                 second = 0;
6768                 MarkTargetSquares(0);
6769                 DragPieceBegin(xPix, yPix); dragging = 1;
6770                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6771                     promoSweep = defaultPromoChoice;
6772                     selectFlag = 0; lastX = xPix; lastY = yPix;
6773                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6774                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6775                 }
6776                 if (appData.highlightDragging) {
6777                     SetHighlights(fromX, fromY, -1, -1);
6778                 }
6779             } else fromX = fromY = -1;
6780             return;
6781         }
6782     }
6783
6784     /* fromX != -1 */
6785     if (clickType == Press && gameMode != EditPosition) {
6786         ChessSquare fromP;
6787         ChessSquare toP;
6788         int frc;
6789
6790         // ignore off-board to clicks
6791         if(y < 0 || x < 0) return;
6792
6793         /* Check if clicking again on the same color piece */
6794         fromP = boards[currentMove][fromY][fromX];
6795         toP = boards[currentMove][y][x];
6796         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6797         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6798              WhitePawn <= toP && toP <= WhiteKing &&
6799              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6800              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6801             (BlackPawn <= fromP && fromP <= BlackKing &&
6802              BlackPawn <= toP && toP <= BlackKing &&
6803              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6804              !(fromP == BlackKing && toP == BlackRook && frc))) {
6805             /* Clicked again on same color piece -- changed his mind */
6806             second = (x == fromX && y == fromY);
6807             promoDefaultAltered = FALSE;
6808            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6809             if (appData.highlightDragging) {
6810                 SetHighlights(x, y, -1, -1);
6811             } else {
6812                 ClearHighlights();
6813             }
6814             if (OKToStartUserMove(x, y)) {
6815                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6816                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6817                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6818                  gatingPiece = boards[currentMove][fromY][fromX];
6819                 else gatingPiece = EmptySquare;
6820                 fromX = x;
6821                 fromY = y; dragging = 1;
6822                 MarkTargetSquares(0);
6823                 DragPieceBegin(xPix, yPix);
6824                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6825                     promoSweep = defaultPromoChoice;
6826                     selectFlag = 0; lastX = xPix; lastY = yPix;
6827                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6828                 }
6829             }
6830            }
6831            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6832            second = FALSE; 
6833         }
6834         // ignore clicks on holdings
6835         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6836     }
6837
6838     if (clickType == Release && x == fromX && y == fromY) {
6839         DragPieceEnd(xPix, yPix); dragging = 0;
6840         if(clearFlag) {
6841             // a deferred attempt to click-click move an empty square on top of a piece
6842             boards[currentMove][y][x] = EmptySquare;
6843             ClearHighlights();
6844             DrawPosition(FALSE, boards[currentMove]);
6845             fromX = fromY = -1; clearFlag = 0;
6846             return;
6847         }
6848         if (appData.animateDragging) {
6849             /* Undo animation damage if any */
6850             DrawPosition(FALSE, NULL);
6851         }
6852         if (second) {
6853             /* Second up/down in same square; just abort move */
6854             second = 0;
6855             fromX = fromY = -1;
6856             gatingPiece = EmptySquare;
6857             ClearHighlights();
6858             gotPremove = 0;
6859             ClearPremoveHighlights();
6860         } else {
6861             /* First upclick in same square; start click-click mode */
6862             SetHighlights(x, y, -1, -1);
6863         }
6864         return;
6865     }
6866
6867     clearFlag = 0;
6868
6869     /* we now have a different from- and (possibly off-board) to-square */
6870     /* Completed move */
6871     toX = x;
6872     toY = y;
6873     saveAnimate = appData.animate;
6874     if (clickType == Press) {
6875         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6876             // must be Edit Position mode with empty-square selected
6877             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6878             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6879             return;
6880         }
6881         /* Finish clickclick move */
6882         if (appData.animate || appData.highlightLastMove) {
6883             SetHighlights(fromX, fromY, toX, toY);
6884         } else {
6885             ClearHighlights();
6886         }
6887     } else {
6888         /* Finish drag move */
6889         if (appData.highlightLastMove) {
6890             SetHighlights(fromX, fromY, toX, toY);
6891         } else {
6892             ClearHighlights();
6893         }
6894         DragPieceEnd(xPix, yPix); dragging = 0;
6895         /* Don't animate move and drag both */
6896         appData.animate = FALSE;
6897     }
6898
6899     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6900     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6901         ChessSquare piece = boards[currentMove][fromY][fromX];
6902         if(gameMode == EditPosition && piece != EmptySquare &&
6903            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6904             int n;
6905
6906             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6907                 n = PieceToNumber(piece - (int)BlackPawn);
6908                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6909                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6910                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6911             } else
6912             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6913                 n = PieceToNumber(piece);
6914                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6915                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6916                 boards[currentMove][n][BOARD_WIDTH-2]++;
6917             }
6918             boards[currentMove][fromY][fromX] = EmptySquare;
6919         }
6920         ClearHighlights();
6921         fromX = fromY = -1;
6922         DrawPosition(TRUE, boards[currentMove]);
6923         return;
6924     }
6925
6926     // off-board moves should not be highlighted
6927     if(x < 0 || y < 0) ClearHighlights();
6928
6929     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6930
6931     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6932         SetHighlights(fromX, fromY, toX, toY);
6933         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6934             // [HGM] super: promotion to captured piece selected from holdings
6935             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6936             promotionChoice = TRUE;
6937             // kludge follows to temporarily execute move on display, without promoting yet
6938             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6939             boards[currentMove][toY][toX] = p;
6940             DrawPosition(FALSE, boards[currentMove]);
6941             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6942             boards[currentMove][toY][toX] = q;
6943             DisplayMessage("Click in holdings to choose piece", "");
6944             return;
6945         }
6946         PromotionPopUp();
6947     } else {
6948         int oldMove = currentMove;
6949         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6950         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6951         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6952         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6953            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6954             DrawPosition(TRUE, boards[currentMove]);
6955         fromX = fromY = -1;
6956     }
6957     appData.animate = saveAnimate;
6958     if (appData.animate || appData.animateDragging) {
6959         /* Undo animation damage if needed */
6960         DrawPosition(FALSE, NULL);
6961     }
6962 }
6963
6964 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6965 {   // front-end-free part taken out of PieceMenuPopup
6966     int whichMenu; int xSqr, ySqr;
6967
6968     if(seekGraphUp) { // [HGM] seekgraph
6969         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6970         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6971         return -2;
6972     }
6973
6974     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6975          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6976         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6977         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6978         if(action == Press)   {
6979             originalFlip = flipView;
6980             flipView = !flipView; // temporarily flip board to see game from partners perspective
6981             DrawPosition(TRUE, partnerBoard);
6982             DisplayMessage(partnerStatus, "");
6983             partnerUp = TRUE;
6984         } else if(action == Release) {
6985             flipView = originalFlip;
6986             DrawPosition(TRUE, boards[currentMove]);
6987             partnerUp = FALSE;
6988         }
6989         return -2;
6990     }
6991
6992     xSqr = EventToSquare(x, BOARD_WIDTH);
6993     ySqr = EventToSquare(y, BOARD_HEIGHT);
6994     if (action == Release) {
6995         if(pieceSweep != EmptySquare) {
6996             EditPositionMenuEvent(pieceSweep, toX, toY);
6997             pieceSweep = EmptySquare;
6998         } else UnLoadPV(); // [HGM] pv
6999     }
7000     if (action != Press) return -2; // return code to be ignored
7001     switch (gameMode) {
7002       case IcsExamining:
7003         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7004       case EditPosition:
7005         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7006         if (xSqr < 0 || ySqr < 0) return -1;
7007         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7008         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7009         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7010         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7011         NextPiece(0);
7012         return -2;\r
7013       case IcsObserving:
7014         if(!appData.icsEngineAnalyze) return -1;
7015       case IcsPlayingWhite:
7016       case IcsPlayingBlack:
7017         if(!appData.zippyPlay) goto noZip;
7018       case AnalyzeMode:
7019       case AnalyzeFile:
7020       case MachinePlaysWhite:
7021       case MachinePlaysBlack:
7022       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7023         if (!appData.dropMenu) {
7024           LoadPV(x, y);
7025           return 2; // flag front-end to grab mouse events
7026         }
7027         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7028            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7029       case EditGame:
7030       noZip:
7031         if (xSqr < 0 || ySqr < 0) return -1;
7032         if (!appData.dropMenu || appData.testLegality &&
7033             gameInfo.variant != VariantBughouse &&
7034             gameInfo.variant != VariantCrazyhouse) return -1;
7035         whichMenu = 1; // drop menu
7036         break;
7037       default:
7038         return -1;
7039     }
7040
7041     if (((*fromX = xSqr) < 0) ||
7042         ((*fromY = ySqr) < 0)) {
7043         *fromX = *fromY = -1;
7044         return -1;
7045     }
7046     if (flipView)
7047       *fromX = BOARD_WIDTH - 1 - *fromX;
7048     else
7049       *fromY = BOARD_HEIGHT - 1 - *fromY;
7050
7051     return whichMenu;
7052 }
7053
7054 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7055 {
7056 //    char * hint = lastHint;
7057     FrontEndProgramStats stats;
7058
7059     stats.which = cps == &first ? 0 : 1;
7060     stats.depth = cpstats->depth;
7061     stats.nodes = cpstats->nodes;
7062     stats.score = cpstats->score;
7063     stats.time = cpstats->time;
7064     stats.pv = cpstats->movelist;
7065     stats.hint = lastHint;
7066     stats.an_move_index = 0;
7067     stats.an_move_count = 0;
7068
7069     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7070         stats.hint = cpstats->move_name;
7071         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7072         stats.an_move_count = cpstats->nr_moves;
7073     }
7074
7075     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
7076
7077     SetProgramStats( &stats );
7078 }
7079
7080 #define MAXPLAYERS 500
7081
7082 char *
7083 TourneyStandings(int display)
7084 {
7085     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7086     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7087     char result, *p, *names[MAXPLAYERS];
7088
7089     names[0] = p = strdup(appData.participants);
7090     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7091
7092     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7093
7094     while(result = appData.results[nr]) {
7095         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7096         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7097         wScore = bScore = 0;
7098         switch(result) {
7099           case '+': wScore = 2; break;
7100           case '-': bScore = 2; break;
7101           case '=': wScore = bScore = 1; break;
7102           case ' ':
7103           case '*': return strdup("busy"); // tourney not finished
7104         }
7105         score[w] += wScore;
7106         score[b] += bScore;
7107         games[w]++;
7108         games[b]++;
7109         nr++;
7110     }
7111     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7112     for(w=0; w<nPlayers; w++) {
7113         bScore = -1;
7114         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7115         ranking[w] = b; points[w] = bScore; score[b] = -2;
7116     }
7117     p = malloc(nPlayers*34+1);
7118     for(w=0; w<nPlayers && w<display; w++)
7119         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7120     free(names[0]);
7121     return p;
7122 }
7123
7124 void
7125 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7126 {       // count all piece types
7127         int p, f, r;
7128         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7129         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7130         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7131                 p = board[r][f];
7132                 pCnt[p]++;
7133                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7134                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7135                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7136                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7137                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7138                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7139         }
7140 }
7141
7142 int
7143 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7144 {
7145         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7146         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7147
7148         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7149         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7150         if(myPawns == 2 && nMine == 3) // KPP
7151             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7152         if(myPawns == 1 && nMine == 2) // KP
7153             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7154         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7155             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7156         if(myPawns) return FALSE;
7157         if(pCnt[WhiteRook+side])
7158             return pCnt[BlackRook-side] ||
7159                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7160                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7161                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7162         if(pCnt[WhiteCannon+side]) {
7163             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7164             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7165         }
7166         if(pCnt[WhiteKnight+side])
7167             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7168         return FALSE;
7169 }
7170
7171 int
7172 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7173 {
7174         VariantClass v = gameInfo.variant;
7175
7176         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7177         if(v == VariantShatranj) return TRUE; // always winnable through baring
7178         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7179         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7180
7181         if(v == VariantXiangqi) {
7182                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7183
7184                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7185                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7186                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7187                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7188                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7189                 if(stale) // we have at least one last-rank P plus perhaps C
7190                     return majors // KPKX
7191                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7192                 else // KCA*E*
7193                     return pCnt[WhiteFerz+side] // KCAK
7194                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7195                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7196                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7197
7198         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7199                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7200
7201                 if(nMine == 1) return FALSE; // bare King
7202                 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
7203                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7204                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7205                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7206                 if(pCnt[WhiteKnight+side])
7207                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7208                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7209                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7210                 if(nBishops)
7211                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7212                 if(pCnt[WhiteAlfil+side])
7213                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7214                 if(pCnt[WhiteWazir+side])
7215                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7216         }
7217
7218         return TRUE;
7219 }
7220
7221 int
7222 Adjudicate(ChessProgramState *cps)
7223 {       // [HGM] some adjudications useful with buggy engines
7224         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7225         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7226         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7227         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7228         int k, count = 0; static int bare = 1;
7229         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7230         Boolean canAdjudicate = !appData.icsActive;
7231
7232         // most tests only when we understand the game, i.e. legality-checking on
7233             if( appData.testLegality )
7234             {   /* [HGM] Some more adjudications for obstinate engines */
7235                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7236                 static int moveCount = 6;
7237                 ChessMove result;
7238                 char *reason = NULL;
7239
7240                 /* Count what is on board. */
7241                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7242
7243                 /* Some material-based adjudications that have to be made before stalemate test */
7244                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7245                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7246                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7247                      if(canAdjudicate && appData.checkMates) {
7248                          if(engineOpponent)
7249                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7250                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7251                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7252                          return 1;
7253                      }
7254                 }
7255
7256                 /* Bare King in Shatranj (loses) or Losers (wins) */
7257                 if( nrW == 1 || nrB == 1) {
7258                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7259                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7260                      if(canAdjudicate && appData.checkMates) {
7261                          if(engineOpponent)
7262                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7263                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7264                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7265                          return 1;
7266                      }
7267                   } else
7268                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7269                   {    /* bare King */
7270                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7271                         if(canAdjudicate && appData.checkMates) {
7272                             /* but only adjudicate if adjudication enabled */
7273                             if(engineOpponent)
7274                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7275                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7276                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7277                             return 1;
7278                         }
7279                   }
7280                 } else bare = 1;
7281
7282
7283             // don't wait for engine to announce game end if we can judge ourselves
7284             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7285               case MT_CHECK:
7286                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7287                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7288                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7289                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7290                             checkCnt++;
7291                         if(checkCnt >= 2) {
7292                             reason = "Xboard adjudication: 3rd check";
7293                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7294                             break;
7295                         }
7296                     }
7297                 }
7298               case MT_NONE:
7299               default:
7300                 break;
7301               case MT_STALEMATE:
7302               case MT_STAINMATE:
7303                 reason = "Xboard adjudication: Stalemate";
7304                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7305                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7306                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7307                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7308                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7309                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7310                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7311                                                                         EP_CHECKMATE : EP_WINS);
7312                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7313                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7314                 }
7315                 break;
7316               case MT_CHECKMATE:
7317                 reason = "Xboard adjudication: Checkmate";
7318                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7319                 break;
7320             }
7321
7322                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7323                     case EP_STALEMATE:
7324                         result = GameIsDrawn; break;
7325                     case EP_CHECKMATE:
7326                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7327                     case EP_WINS:
7328                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7329                     default:
7330                         result = EndOfFile;
7331                 }
7332                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7333                     if(engineOpponent)
7334                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7335                     GameEnds( result, reason, GE_XBOARD );
7336                     return 1;
7337                 }
7338
7339                 /* Next absolutely insufficient mating material. */
7340                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7341                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7342                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7343
7344                      /* always flag draws, for judging claims */
7345                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7346
7347                      if(canAdjudicate && appData.materialDraws) {
7348                          /* but only adjudicate them if adjudication enabled */
7349                          if(engineOpponent) {
7350                            SendToProgram("force\n", engineOpponent); // suppress reply
7351                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7352                          }
7353                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7354                          return 1;
7355                      }
7356                 }
7357
7358                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7359                 if(gameInfo.variant == VariantXiangqi ?
7360                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7361                  : nrW + nrB == 4 &&
7362                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7363                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7364                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7365                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7366                    ) ) {
7367                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7368                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7369                           if(engineOpponent) {
7370                             SendToProgram("force\n", engineOpponent); // suppress reply
7371                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7372                           }
7373                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7374                           return 1;
7375                      }
7376                 } else moveCount = 6;
7377             }
7378         if (appData.debugMode) { int i;
7379             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7380                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7381                     appData.drawRepeats);
7382             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7383               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7384
7385         }
7386
7387         // Repetition draws and 50-move rule can be applied independently of legality testing
7388
7389                 /* Check for rep-draws */
7390                 count = 0;
7391                 for(k = forwardMostMove-2;
7392                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7393                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7394                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7395                     k-=2)
7396                 {   int rights=0;
7397                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7398                         /* compare castling rights */
7399                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7400                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7401                                 rights++; /* King lost rights, while rook still had them */
7402                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7403                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7404                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7405                                    rights++; /* but at least one rook lost them */
7406                         }
7407                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7408                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7409                                 rights++;
7410                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7411                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7412                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7413                                    rights++;
7414                         }
7415                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7416                             && appData.drawRepeats > 1) {
7417                              /* adjudicate after user-specified nr of repeats */
7418                              int result = GameIsDrawn;
7419                              char *details = "XBoard adjudication: repetition draw";
7420                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7421                                 // [HGM] xiangqi: check for forbidden perpetuals
7422                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7423                                 for(m=forwardMostMove; m>k; m-=2) {
7424                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7425                                         ourPerpetual = 0; // the current mover did not always check
7426                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7427                                         hisPerpetual = 0; // the opponent did not always check
7428                                 }
7429                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7430                                                                         ourPerpetual, hisPerpetual);
7431                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7432                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7433                                     details = "Xboard adjudication: perpetual checking";
7434                                 } else
7435                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7436                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7437                                 } else
7438                                 // Now check for perpetual chases
7439                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7440                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7441                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7442                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7443                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7444                                         details = "Xboard adjudication: perpetual chasing";
7445                                     } else
7446                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7447                                         break; // Abort repetition-checking loop.
7448                                 }
7449                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7450                              }
7451                              if(engineOpponent) {
7452                                SendToProgram("force\n", engineOpponent); // suppress reply
7453                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7454                              }
7455                              GameEnds( result, details, GE_XBOARD );
7456                              return 1;
7457                         }
7458                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7459                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7460                     }
7461                 }
7462
7463                 /* Now we test for 50-move draws. Determine ply count */
7464                 count = forwardMostMove;
7465                 /* look for last irreversble move */
7466                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7467                     count--;
7468                 /* if we hit starting position, add initial plies */
7469                 if( count == backwardMostMove )
7470                     count -= initialRulePlies;
7471                 count = forwardMostMove - count;
7472                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7473                         // adjust reversible move counter for checks in Xiangqi
7474                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7475                         if(i < backwardMostMove) i = backwardMostMove;
7476                         while(i <= forwardMostMove) {
7477                                 lastCheck = inCheck; // check evasion does not count
7478                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7479                                 if(inCheck || lastCheck) count--; // check does not count
7480                                 i++;
7481                         }
7482                 }
7483                 if( count >= 100)
7484                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7485                          /* this is used to judge if draw claims are legal */
7486                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7487                          if(engineOpponent) {
7488                            SendToProgram("force\n", engineOpponent); // suppress reply
7489                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7490                          }
7491                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7492                          return 1;
7493                 }
7494
7495                 /* if draw offer is pending, treat it as a draw claim
7496                  * when draw condition present, to allow engines a way to
7497                  * claim draws before making their move to avoid a race
7498                  * condition occurring after their move
7499                  */
7500                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7501                          char *p = NULL;
7502                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7503                              p = "Draw claim: 50-move rule";
7504                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7505                              p = "Draw claim: 3-fold repetition";
7506                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7507                              p = "Draw claim: insufficient mating material";
7508                          if( p != NULL && canAdjudicate) {
7509                              if(engineOpponent) {
7510                                SendToProgram("force\n", engineOpponent); // suppress reply
7511                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7512                              }
7513                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7514                              return 1;
7515                          }
7516                 }
7517
7518                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7519                     if(engineOpponent) {
7520                       SendToProgram("force\n", engineOpponent); // suppress reply
7521                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7522                     }
7523                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7524                     return 1;
7525                 }
7526         return 0;
7527 }
7528
7529 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7530 {   // [HGM] book: this routine intercepts moves to simulate book replies
7531     char *bookHit = NULL;
7532
7533     //first determine if the incoming move brings opponent into his book
7534     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7535         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7536     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7537     if(bookHit != NULL && !cps->bookSuspend) {
7538         // make sure opponent is not going to reply after receiving move to book position
7539         SendToProgram("force\n", cps);
7540         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7541     }
7542     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7543     // now arrange restart after book miss
7544     if(bookHit) {
7545         // after a book hit we never send 'go', and the code after the call to this routine
7546         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7547         char buf[MSG_SIZ];
7548         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7549         SendToProgram(buf, cps);
7550         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7551     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7552         SendToProgram("go\n", cps);
7553         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7554     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7555         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7556             SendToProgram("go\n", cps);
7557         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7558     }
7559     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7560 }
7561
7562 char *savedMessage;
7563 ChessProgramState *savedState;
7564 void DeferredBookMove(void)
7565 {
7566         if(savedState->lastPing != savedState->lastPong)
7567                     ScheduleDelayedEvent(DeferredBookMove, 10);
7568         else
7569         HandleMachineMove(savedMessage, savedState);
7570 }
7571
7572 void
7573 HandleMachineMove(message, cps)
7574      char *message;
7575      ChessProgramState *cps;
7576 {
7577     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7578     char realname[MSG_SIZ];
7579     int fromX, fromY, toX, toY;
7580     ChessMove moveType;
7581     char promoChar;
7582     char *p;
7583     int machineWhite;
7584     char *bookHit;
7585
7586     cps->userError = 0;
7587
7588 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7589     /*
7590      * Kludge to ignore BEL characters
7591      */
7592     while (*message == '\007') message++;
7593
7594     /*
7595      * [HGM] engine debug message: ignore lines starting with '#' character
7596      */
7597     if(cps->debug && *message == '#') return;
7598
7599     /*
7600      * Look for book output
7601      */
7602     if (cps == &first && bookRequested) {
7603         if (message[0] == '\t' || message[0] == ' ') {
7604             /* Part of the book output is here; append it */
7605             strcat(bookOutput, message);
7606             strcat(bookOutput, "  \n");
7607             return;
7608         } else if (bookOutput[0] != NULLCHAR) {
7609             /* All of book output has arrived; display it */
7610             char *p = bookOutput;
7611             while (*p != NULLCHAR) {
7612                 if (*p == '\t') *p = ' ';
7613                 p++;
7614             }
7615             DisplayInformation(bookOutput);
7616             bookRequested = FALSE;
7617             /* Fall through to parse the current output */
7618         }
7619     }
7620
7621     /*
7622      * Look for machine move.
7623      */
7624     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7625         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7626     {
7627         /* This method is only useful on engines that support ping */
7628         if (cps->lastPing != cps->lastPong) {
7629           if (gameMode == BeginningOfGame) {
7630             /* Extra move from before last new; ignore */
7631             if (appData.debugMode) {
7632                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7633             }
7634           } else {
7635             if (appData.debugMode) {
7636                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7637                         cps->which, gameMode);
7638             }
7639
7640             SendToProgram("undo\n", cps);
7641           }
7642           return;
7643         }
7644
7645         switch (gameMode) {
7646           case BeginningOfGame:
7647             /* Extra move from before last reset; ignore */
7648             if (appData.debugMode) {
7649                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7650             }
7651             return;
7652
7653           case EndOfGame:
7654           case IcsIdle:
7655           default:
7656             /* Extra move after we tried to stop.  The mode test is
7657                not a reliable way of detecting this problem, but it's
7658                the best we can do on engines that don't support ping.
7659             */
7660             if (appData.debugMode) {
7661                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7662                         cps->which, gameMode);
7663             }
7664             SendToProgram("undo\n", cps);
7665             return;
7666
7667           case MachinePlaysWhite:
7668           case IcsPlayingWhite:
7669             machineWhite = TRUE;
7670             break;
7671
7672           case MachinePlaysBlack:
7673           case IcsPlayingBlack:
7674             machineWhite = FALSE;
7675             break;
7676
7677           case TwoMachinesPlay:
7678             machineWhite = (cps->twoMachinesColor[0] == 'w');
7679             break;
7680         }
7681         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7682             if (appData.debugMode) {
7683                 fprintf(debugFP,
7684                         "Ignoring move out of turn by %s, gameMode %d"
7685                         ", forwardMost %d\n",
7686                         cps->which, gameMode, forwardMostMove);
7687             }
7688             return;
7689         }
7690
7691     if (appData.debugMode) { int f = forwardMostMove;
7692         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7693                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7694                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7695     }
7696         if(cps->alphaRank) AlphaRank(machineMove, 4);
7697         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7698                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7699             /* Machine move could not be parsed; ignore it. */
7700           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7701                     machineMove, _(cps->which));
7702             DisplayError(buf1, 0);
7703             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7704                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7705             if (gameMode == TwoMachinesPlay) {
7706               GameEnds(machineWhite ? BlackWins : WhiteWins,
7707                        buf1, GE_XBOARD);
7708             }
7709             return;
7710         }
7711
7712         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7713         /* So we have to redo legality test with true e.p. status here,  */
7714         /* to make sure an illegal e.p. capture does not slip through,   */
7715         /* to cause a forfeit on a justified illegal-move complaint      */
7716         /* of the opponent.                                              */
7717         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7718            ChessMove moveType;
7719            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7720                              fromY, fromX, toY, toX, promoChar);
7721             if (appData.debugMode) {
7722                 int i;
7723                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7724                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7725                 fprintf(debugFP, "castling rights\n");
7726             }
7727             if(moveType == IllegalMove) {
7728               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7729                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7730                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7731                            buf1, GE_XBOARD);
7732                 return;
7733            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7734            /* [HGM] Kludge to handle engines that send FRC-style castling
7735               when they shouldn't (like TSCP-Gothic) */
7736            switch(moveType) {
7737              case WhiteASideCastleFR:
7738              case BlackASideCastleFR:
7739                toX+=2;
7740                currentMoveString[2]++;
7741                break;
7742              case WhiteHSideCastleFR:
7743              case BlackHSideCastleFR:
7744                toX--;
7745                currentMoveString[2]--;
7746                break;
7747              default: ; // nothing to do, but suppresses warning of pedantic compilers
7748            }
7749         }
7750         hintRequested = FALSE;
7751         lastHint[0] = NULLCHAR;
7752         bookRequested = FALSE;
7753         /* Program may be pondering now */
7754         cps->maybeThinking = TRUE;
7755         if (cps->sendTime == 2) cps->sendTime = 1;
7756         if (cps->offeredDraw) cps->offeredDraw--;
7757
7758         /* [AS] Save move info*/
7759         pvInfoList[ forwardMostMove ].score = programStats.score;
7760         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7761         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7762
7763         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7764
7765         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7766         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7767             int count = 0;
7768
7769             while( count < adjudicateLossPlies ) {
7770                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7771
7772                 if( count & 1 ) {
7773                     score = -score; /* Flip score for winning side */
7774                 }
7775
7776                 if( score > adjudicateLossThreshold ) {
7777                     break;
7778                 }
7779
7780                 count++;
7781             }
7782
7783             if( count >= adjudicateLossPlies ) {
7784                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7785
7786                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7787                     "Xboard adjudication",
7788                     GE_XBOARD );
7789
7790                 return;
7791             }
7792         }
7793
7794         if(Adjudicate(cps)) {
7795             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7796             return; // [HGM] adjudicate: for all automatic game ends
7797         }
7798
7799 #if ZIPPY
7800         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7801             first.initDone) {
7802           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7803                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7804                 SendToICS("draw ");
7805                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7806           }
7807           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7808           ics_user_moved = 1;
7809           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7810                 char buf[3*MSG_SIZ];
7811
7812                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7813                         programStats.score / 100.,
7814                         programStats.depth,
7815                         programStats.time / 100.,
7816                         (unsigned int)programStats.nodes,
7817                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7818                         programStats.movelist);
7819                 SendToICS(buf);
7820 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7821           }
7822         }
7823 #endif
7824
7825         /* [AS] Clear stats for next move */
7826         ClearProgramStats();
7827         thinkOutput[0] = NULLCHAR;
7828         hiddenThinkOutputState = 0;
7829
7830         bookHit = NULL;
7831         if (gameMode == TwoMachinesPlay) {
7832             /* [HGM] relaying draw offers moved to after reception of move */
7833             /* and interpreting offer as claim if it brings draw condition */
7834             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7835                 SendToProgram("draw\n", cps->other);
7836             }
7837             if (cps->other->sendTime) {
7838                 SendTimeRemaining(cps->other,
7839                                   cps->other->twoMachinesColor[0] == 'w');
7840             }
7841             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7842             if (firstMove && !bookHit) {
7843                 firstMove = FALSE;
7844                 if (cps->other->useColors) {
7845                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7846                 }
7847                 SendToProgram("go\n", cps->other);
7848             }
7849             cps->other->maybeThinking = TRUE;
7850         }
7851
7852         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7853
7854         if (!pausing && appData.ringBellAfterMoves) {
7855             RingBell();
7856         }
7857
7858         /*
7859          * Reenable menu items that were disabled while
7860          * machine was thinking
7861          */
7862         if (gameMode != TwoMachinesPlay)
7863             SetUserThinkingEnables();
7864
7865         // [HGM] book: after book hit opponent has received move and is now in force mode
7866         // force the book reply into it, and then fake that it outputted this move by jumping
7867         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7868         if(bookHit) {
7869                 static char bookMove[MSG_SIZ]; // a bit generous?
7870
7871                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7872                 strcat(bookMove, bookHit);
7873                 message = bookMove;
7874                 cps = cps->other;
7875                 programStats.nodes = programStats.depth = programStats.time =
7876                 programStats.score = programStats.got_only_move = 0;
7877                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7878
7879                 if(cps->lastPing != cps->lastPong) {
7880                     savedMessage = message; // args for deferred call
7881                     savedState = cps;
7882                     ScheduleDelayedEvent(DeferredBookMove, 10);
7883                     return;
7884                 }
7885                 goto FakeBookMove;
7886         }
7887
7888         return;
7889     }
7890
7891     /* Set special modes for chess engines.  Later something general
7892      *  could be added here; for now there is just one kludge feature,
7893      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7894      *  when "xboard" is given as an interactive command.
7895      */
7896     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7897         cps->useSigint = FALSE;
7898         cps->useSigterm = FALSE;
7899     }
7900     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7901       ParseFeatures(message+8, cps);
7902       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7903     }
7904
7905     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7906       int dummy, s=6; char buf[MSG_SIZ];
7907       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7908       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7909       ParseFEN(boards[0], &dummy, message+s);
7910       DrawPosition(TRUE, boards[0]);
7911       startedFromSetupPosition = TRUE;
7912       return;
7913     }
7914     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7915      * want this, I was asked to put it in, and obliged.
7916      */
7917     if (!strncmp(message, "setboard ", 9)) {
7918         Board initial_position;
7919
7920         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7921
7922         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7923             DisplayError(_("Bad FEN received from engine"), 0);
7924             return ;
7925         } else {
7926            Reset(TRUE, FALSE);
7927            CopyBoard(boards[0], initial_position);
7928            initialRulePlies = FENrulePlies;
7929            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7930            else gameMode = MachinePlaysBlack;
7931            DrawPosition(FALSE, boards[currentMove]);
7932         }
7933         return;
7934     }
7935
7936     /*
7937      * Look for communication commands
7938      */
7939     if (!strncmp(message, "telluser ", 9)) {
7940         if(message[9] == '\\' && message[10] == '\\')
7941             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7942         DisplayNote(message + 9);
7943         return;
7944     }
7945     if (!strncmp(message, "tellusererror ", 14)) {
7946         cps->userError = 1;
7947         if(message[14] == '\\' && message[15] == '\\')
7948             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7949         DisplayError(message + 14, 0);
7950         return;
7951     }
7952     if (!strncmp(message, "tellopponent ", 13)) {
7953       if (appData.icsActive) {
7954         if (loggedOn) {
7955           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7956           SendToICS(buf1);
7957         }
7958       } else {
7959         DisplayNote(message + 13);
7960       }
7961       return;
7962     }
7963     if (!strncmp(message, "tellothers ", 11)) {
7964       if (appData.icsActive) {
7965         if (loggedOn) {
7966           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7967           SendToICS(buf1);
7968         }
7969       }
7970       return;
7971     }
7972     if (!strncmp(message, "tellall ", 8)) {
7973       if (appData.icsActive) {
7974         if (loggedOn) {
7975           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7976           SendToICS(buf1);
7977         }
7978       } else {
7979         DisplayNote(message + 8);
7980       }
7981       return;
7982     }
7983     if (strncmp(message, "warning", 7) == 0) {
7984         /* Undocumented feature, use tellusererror in new code */
7985         DisplayError(message, 0);
7986         return;
7987     }
7988     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7989         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7990         strcat(realname, " query");
7991         AskQuestion(realname, buf2, buf1, cps->pr);
7992         return;
7993     }
7994     /* Commands from the engine directly to ICS.  We don't allow these to be
7995      *  sent until we are logged on. Crafty kibitzes have been known to
7996      *  interfere with the login process.
7997      */
7998     if (loggedOn) {
7999         if (!strncmp(message, "tellics ", 8)) {
8000             SendToICS(message + 8);
8001             SendToICS("\n");
8002             return;
8003         }
8004         if (!strncmp(message, "tellicsnoalias ", 15)) {
8005             SendToICS(ics_prefix);
8006             SendToICS(message + 15);
8007             SendToICS("\n");
8008             return;
8009         }
8010         /* The following are for backward compatibility only */
8011         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8012             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8013             SendToICS(ics_prefix);
8014             SendToICS(message);
8015             SendToICS("\n");
8016             return;
8017         }
8018     }
8019     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8020         return;
8021     }
8022     /*
8023      * If the move is illegal, cancel it and redraw the board.
8024      * Also deal with other error cases.  Matching is rather loose
8025      * here to accommodate engines written before the spec.
8026      */
8027     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8028         strncmp(message, "Error", 5) == 0) {
8029         if (StrStr(message, "name") ||
8030             StrStr(message, "rating") || StrStr(message, "?") ||
8031             StrStr(message, "result") || StrStr(message, "board") ||
8032             StrStr(message, "bk") || StrStr(message, "computer") ||
8033             StrStr(message, "variant") || StrStr(message, "hint") ||
8034             StrStr(message, "random") || StrStr(message, "depth") ||
8035             StrStr(message, "accepted")) {
8036             return;
8037         }
8038         if (StrStr(message, "protover")) {
8039           /* Program is responding to input, so it's apparently done
8040              initializing, and this error message indicates it is
8041              protocol version 1.  So we don't need to wait any longer
8042              for it to initialize and send feature commands. */
8043           FeatureDone(cps, 1);
8044           cps->protocolVersion = 1;
8045           return;
8046         }
8047         cps->maybeThinking = FALSE;
8048
8049         if (StrStr(message, "draw")) {
8050             /* Program doesn't have "draw" command */
8051             cps->sendDrawOffers = 0;
8052             return;
8053         }
8054         if (cps->sendTime != 1 &&
8055             (StrStr(message, "time") || StrStr(message, "otim"))) {
8056           /* Program apparently doesn't have "time" or "otim" command */
8057           cps->sendTime = 0;
8058           return;
8059         }
8060         if (StrStr(message, "analyze")) {
8061             cps->analysisSupport = FALSE;
8062             cps->analyzing = FALSE;
8063             Reset(FALSE, TRUE);
8064             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8065             DisplayError(buf2, 0);
8066             return;
8067         }
8068         if (StrStr(message, "(no matching move)st")) {
8069           /* Special kludge for GNU Chess 4 only */
8070           cps->stKludge = TRUE;
8071           SendTimeControl(cps, movesPerSession, timeControl,
8072                           timeIncrement, appData.searchDepth,
8073                           searchTime);
8074           return;
8075         }
8076         if (StrStr(message, "(no matching move)sd")) {
8077           /* Special kludge for GNU Chess 4 only */
8078           cps->sdKludge = TRUE;
8079           SendTimeControl(cps, movesPerSession, timeControl,
8080                           timeIncrement, appData.searchDepth,
8081                           searchTime);
8082           return;
8083         }
8084         if (!StrStr(message, "llegal")) {
8085             return;
8086         }
8087         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8088             gameMode == IcsIdle) return;
8089         if (forwardMostMove <= backwardMostMove) return;
8090         if (pausing) PauseEvent();
8091       if(appData.forceIllegal) {
8092             // [HGM] illegal: machine refused move; force position after move into it
8093           SendToProgram("force\n", cps);
8094           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8095                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8096                 // when black is to move, while there might be nothing on a2 or black
8097                 // might already have the move. So send the board as if white has the move.
8098                 // But first we must change the stm of the engine, as it refused the last move
8099                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8100                 if(WhiteOnMove(forwardMostMove)) {
8101                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8102                     SendBoard(cps, forwardMostMove); // kludgeless board
8103                 } else {
8104                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8105                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8106                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8107                 }
8108           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8109             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8110                  gameMode == TwoMachinesPlay)
8111               SendToProgram("go\n", cps);
8112             return;
8113       } else
8114         if (gameMode == PlayFromGameFile) {
8115             /* Stop reading this game file */
8116             gameMode = EditGame;
8117             ModeHighlight();
8118         }
8119         /* [HGM] illegal-move claim should forfeit game when Xboard */
8120         /* only passes fully legal moves                            */
8121         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8122             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8123                                 "False illegal-move claim", GE_XBOARD );
8124             return; // do not take back move we tested as valid
8125         }
8126         currentMove = forwardMostMove-1;
8127         DisplayMove(currentMove-1); /* before DisplayMoveError */
8128         SwitchClocks(forwardMostMove-1); // [HGM] race
8129         DisplayBothClocks();
8130         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8131                 parseList[currentMove], _(cps->which));
8132         DisplayMoveError(buf1);
8133         DrawPosition(FALSE, boards[currentMove]);
8134         return;
8135     }
8136     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8137         /* Program has a broken "time" command that
8138            outputs a string not ending in newline.
8139            Don't use it. */
8140         cps->sendTime = 0;
8141     }
8142
8143     /*
8144      * If chess program startup fails, exit with an error message.
8145      * Attempts to recover here are futile.
8146      */
8147     if ((StrStr(message, "unknown host") != NULL)
8148         || (StrStr(message, "No remote directory") != NULL)
8149         || (StrStr(message, "not found") != NULL)
8150         || (StrStr(message, "No such file") != NULL)
8151         || (StrStr(message, "can't alloc") != NULL)
8152         || (StrStr(message, "Permission denied") != NULL)) {
8153
8154         cps->maybeThinking = FALSE;
8155         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8156                 _(cps->which), cps->program, cps->host, message);
8157         RemoveInputSource(cps->isr);
8158         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8159             if(cps == &first) appData.noChessProgram = TRUE;
8160             DisplayError(buf1, 0);
8161         }
8162         return;
8163     }
8164
8165     /*
8166      * Look for hint output
8167      */
8168     if (sscanf(message, "Hint: %s", buf1) == 1) {
8169         if (cps == &first && hintRequested) {
8170             hintRequested = FALSE;
8171             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8172                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8173                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8174                                     PosFlags(forwardMostMove),
8175                                     fromY, fromX, toY, toX, promoChar, buf1);
8176                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8177                 DisplayInformation(buf2);
8178             } else {
8179                 /* Hint move could not be parsed!? */
8180               snprintf(buf2, sizeof(buf2),
8181                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8182                         buf1, _(cps->which));
8183                 DisplayError(buf2, 0);
8184             }
8185         } else {
8186           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8187         }
8188         return;
8189     }
8190
8191     /*
8192      * Ignore other messages if game is not in progress
8193      */
8194     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8195         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8196
8197     /*
8198      * look for win, lose, draw, or draw offer
8199      */
8200     if (strncmp(message, "1-0", 3) == 0) {
8201         char *p, *q, *r = "";
8202         p = strchr(message, '{');
8203         if (p) {
8204             q = strchr(p, '}');
8205             if (q) {
8206                 *q = NULLCHAR;
8207                 r = p + 1;
8208             }
8209         }
8210         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8211         return;
8212     } else if (strncmp(message, "0-1", 3) == 0) {
8213         char *p, *q, *r = "";
8214         p = strchr(message, '{');
8215         if (p) {
8216             q = strchr(p, '}');
8217             if (q) {
8218                 *q = NULLCHAR;
8219                 r = p + 1;
8220             }
8221         }
8222         /* Kludge for Arasan 4.1 bug */
8223         if (strcmp(r, "Black resigns") == 0) {
8224             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8225             return;
8226         }
8227         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8228         return;
8229     } else if (strncmp(message, "1/2", 3) == 0) {
8230         char *p, *q, *r = "";
8231         p = strchr(message, '{');
8232         if (p) {
8233             q = strchr(p, '}');
8234             if (q) {
8235                 *q = NULLCHAR;
8236                 r = p + 1;
8237             }
8238         }
8239
8240         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8241         return;
8242
8243     } else if (strncmp(message, "White resign", 12) == 0) {
8244         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8245         return;
8246     } else if (strncmp(message, "Black resign", 12) == 0) {
8247         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8248         return;
8249     } else if (strncmp(message, "White matches", 13) == 0 ||
8250                strncmp(message, "Black matches", 13) == 0   ) {
8251         /* [HGM] ignore GNUShogi noises */
8252         return;
8253     } else if (strncmp(message, "White", 5) == 0 &&
8254                message[5] != '(' &&
8255                StrStr(message, "Black") == NULL) {
8256         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8257         return;
8258     } else if (strncmp(message, "Black", 5) == 0 &&
8259                message[5] != '(') {
8260         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8261         return;
8262     } else if (strcmp(message, "resign") == 0 ||
8263                strcmp(message, "computer resigns") == 0) {
8264         switch (gameMode) {
8265           case MachinePlaysBlack:
8266           case IcsPlayingBlack:
8267             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8268             break;
8269           case MachinePlaysWhite:
8270           case IcsPlayingWhite:
8271             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8272             break;
8273           case TwoMachinesPlay:
8274             if (cps->twoMachinesColor[0] == 'w')
8275               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8276             else
8277               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8278             break;
8279           default:
8280             /* can't happen */
8281             break;
8282         }
8283         return;
8284     } else if (strncmp(message, "opponent mates", 14) == 0) {
8285         switch (gameMode) {
8286           case MachinePlaysBlack:
8287           case IcsPlayingBlack:
8288             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8289             break;
8290           case MachinePlaysWhite:
8291           case IcsPlayingWhite:
8292             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8293             break;
8294           case TwoMachinesPlay:
8295             if (cps->twoMachinesColor[0] == 'w')
8296               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8297             else
8298               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8299             break;
8300           default:
8301             /* can't happen */
8302             break;
8303         }
8304         return;
8305     } else if (strncmp(message, "computer mates", 14) == 0) {
8306         switch (gameMode) {
8307           case MachinePlaysBlack:
8308           case IcsPlayingBlack:
8309             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8310             break;
8311           case MachinePlaysWhite:
8312           case IcsPlayingWhite:
8313             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8314             break;
8315           case TwoMachinesPlay:
8316             if (cps->twoMachinesColor[0] == 'w')
8317               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8318             else
8319               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8320             break;
8321           default:
8322             /* can't happen */
8323             break;
8324         }
8325         return;
8326     } else if (strncmp(message, "checkmate", 9) == 0) {
8327         if (WhiteOnMove(forwardMostMove)) {
8328             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8329         } else {
8330             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8331         }
8332         return;
8333     } else if (strstr(message, "Draw") != NULL ||
8334                strstr(message, "game is a draw") != NULL) {
8335         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8336         return;
8337     } else if (strstr(message, "offer") != NULL &&
8338                strstr(message, "draw") != NULL) {
8339 #if ZIPPY
8340         if (appData.zippyPlay && first.initDone) {
8341             /* Relay offer to ICS */
8342             SendToICS(ics_prefix);
8343             SendToICS("draw\n");
8344         }
8345 #endif
8346         cps->offeredDraw = 2; /* valid until this engine moves twice */
8347         if (gameMode == TwoMachinesPlay) {
8348             if (cps->other->offeredDraw) {
8349                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8350             /* [HGM] in two-machine mode we delay relaying draw offer      */
8351             /* until after we also have move, to see if it is really claim */
8352             }
8353         } else if (gameMode == MachinePlaysWhite ||
8354                    gameMode == MachinePlaysBlack) {
8355           if (userOfferedDraw) {
8356             DisplayInformation(_("Machine accepts your draw offer"));
8357             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8358           } else {
8359             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8360           }
8361         }
8362     }
8363
8364
8365     /*
8366      * Look for thinking output
8367      */
8368     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8369           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8370                                 ) {
8371         int plylev, mvleft, mvtot, curscore, time;
8372         char mvname[MOVE_LEN];
8373         u64 nodes; // [DM]
8374         char plyext;
8375         int ignore = FALSE;
8376         int prefixHint = FALSE;
8377         mvname[0] = NULLCHAR;
8378
8379         switch (gameMode) {
8380           case MachinePlaysBlack:
8381           case IcsPlayingBlack:
8382             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8383             break;
8384           case MachinePlaysWhite:
8385           case IcsPlayingWhite:
8386             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8387             break;
8388           case AnalyzeMode:
8389           case AnalyzeFile:
8390             break;
8391           case IcsObserving: /* [DM] icsEngineAnalyze */
8392             if (!appData.icsEngineAnalyze) ignore = TRUE;
8393             break;
8394           case TwoMachinesPlay:
8395             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8396                 ignore = TRUE;
8397             }
8398             break;
8399           default:
8400             ignore = TRUE;
8401             break;
8402         }
8403
8404         if (!ignore) {
8405             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8406             buf1[0] = NULLCHAR;
8407             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8408                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8409
8410                 if (plyext != ' ' && plyext != '\t') {
8411                     time *= 100;
8412                 }
8413
8414                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8415                 if( cps->scoreIsAbsolute &&
8416                     ( gameMode == MachinePlaysBlack ||
8417                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8418                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8419                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8420                      !WhiteOnMove(currentMove)
8421                     ) )
8422                 {
8423                     curscore = -curscore;
8424                 }
8425
8426
8427                 tempStats.depth = plylev;
8428                 tempStats.nodes = nodes;
8429                 tempStats.time = time;
8430                 tempStats.score = curscore;
8431                 tempStats.got_only_move = 0;
8432
8433                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8434                         int ticklen;
8435
8436                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8437                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8438                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8439                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8440                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8441                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8442                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8443                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8444                 }
8445
8446                 /* Buffer overflow protection */
8447                 if (buf1[0] != NULLCHAR) {
8448                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8449                         && appData.debugMode) {
8450                         fprintf(debugFP,
8451                                 "PV is too long; using the first %u bytes.\n",
8452                                 (unsigned) sizeof(tempStats.movelist) - 1);
8453                     }
8454
8455                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8456                 } else {
8457                     sprintf(tempStats.movelist, " no PV\n");
8458                 }
8459
8460                 if (tempStats.seen_stat) {
8461                     tempStats.ok_to_send = 1;
8462                 }
8463
8464                 if (strchr(tempStats.movelist, '(') != NULL) {
8465                     tempStats.line_is_book = 1;
8466                     tempStats.nr_moves = 0;
8467                     tempStats.moves_left = 0;
8468                 } else {
8469                     tempStats.line_is_book = 0;
8470                 }
8471
8472                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8473                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8474
8475                 SendProgramStatsToFrontend( cps, &tempStats );
8476
8477                 /*
8478                     [AS] Protect the thinkOutput buffer from overflow... this
8479                     is only useful if buf1 hasn't overflowed first!
8480                 */
8481                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8482                          plylev,
8483                          (gameMode == TwoMachinesPlay ?
8484                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8485                          ((double) curscore) / 100.0,
8486                          prefixHint ? lastHint : "",
8487                          prefixHint ? " " : "" );
8488
8489                 if( buf1[0] != NULLCHAR ) {
8490                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8491
8492                     if( strlen(buf1) > max_len ) {
8493                         if( appData.debugMode) {
8494                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8495                         }
8496                         buf1[max_len+1] = '\0';
8497                     }
8498
8499                     strcat( thinkOutput, buf1 );
8500                 }
8501
8502                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8503                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8504                     DisplayMove(currentMove - 1);
8505                 }
8506                 return;
8507
8508             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8509                 /* crafty (9.25+) says "(only move) <move>"
8510                  * if there is only 1 legal move
8511                  */
8512                 sscanf(p, "(only move) %s", buf1);
8513                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8514                 sprintf(programStats.movelist, "%s (only move)", buf1);
8515                 programStats.depth = 1;
8516                 programStats.nr_moves = 1;
8517                 programStats.moves_left = 1;
8518                 programStats.nodes = 1;
8519                 programStats.time = 1;
8520                 programStats.got_only_move = 1;
8521
8522                 /* Not really, but we also use this member to
8523                    mean "line isn't going to change" (Crafty
8524                    isn't searching, so stats won't change) */
8525                 programStats.line_is_book = 1;
8526
8527                 SendProgramStatsToFrontend( cps, &programStats );
8528
8529                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8530                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8531                     DisplayMove(currentMove - 1);
8532                 }
8533                 return;
8534             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8535                               &time, &nodes, &plylev, &mvleft,
8536                               &mvtot, mvname) >= 5) {
8537                 /* The stat01: line is from Crafty (9.29+) in response
8538                    to the "." command */
8539                 programStats.seen_stat = 1;
8540                 cps->maybeThinking = TRUE;
8541
8542                 if (programStats.got_only_move || !appData.periodicUpdates)
8543                   return;
8544
8545                 programStats.depth = plylev;
8546                 programStats.time = time;
8547                 programStats.nodes = nodes;
8548                 programStats.moves_left = mvleft;
8549                 programStats.nr_moves = mvtot;
8550                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8551                 programStats.ok_to_send = 1;
8552                 programStats.movelist[0] = '\0';
8553
8554                 SendProgramStatsToFrontend( cps, &programStats );
8555
8556                 return;
8557
8558             } else if (strncmp(message,"++",2) == 0) {
8559                 /* Crafty 9.29+ outputs this */
8560                 programStats.got_fail = 2;
8561                 return;
8562
8563             } else if (strncmp(message,"--",2) == 0) {
8564                 /* Crafty 9.29+ outputs this */
8565                 programStats.got_fail = 1;
8566                 return;
8567
8568             } else if (thinkOutput[0] != NULLCHAR &&
8569                        strncmp(message, "    ", 4) == 0) {
8570                 unsigned message_len;
8571
8572                 p = message;
8573                 while (*p && *p == ' ') p++;
8574
8575                 message_len = strlen( p );
8576
8577                 /* [AS] Avoid buffer overflow */
8578                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8579                     strcat(thinkOutput, " ");
8580                     strcat(thinkOutput, p);
8581                 }
8582
8583                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8584                     strcat(programStats.movelist, " ");
8585                     strcat(programStats.movelist, p);
8586                 }
8587
8588                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8589                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8590                     DisplayMove(currentMove - 1);
8591                 }
8592                 return;
8593             }
8594         }
8595         else {
8596             buf1[0] = NULLCHAR;
8597
8598             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8599                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8600             {
8601                 ChessProgramStats cpstats;
8602
8603                 if (plyext != ' ' && plyext != '\t') {
8604                     time *= 100;
8605                 }
8606
8607                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8608                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8609                     curscore = -curscore;
8610                 }
8611
8612                 cpstats.depth = plylev;
8613                 cpstats.nodes = nodes;
8614                 cpstats.time = time;
8615                 cpstats.score = curscore;
8616                 cpstats.got_only_move = 0;
8617                 cpstats.movelist[0] = '\0';
8618
8619                 if (buf1[0] != NULLCHAR) {
8620                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8621                 }
8622
8623                 cpstats.ok_to_send = 0;
8624                 cpstats.line_is_book = 0;
8625                 cpstats.nr_moves = 0;
8626                 cpstats.moves_left = 0;
8627
8628                 SendProgramStatsToFrontend( cps, &cpstats );
8629             }
8630         }
8631     }
8632 }
8633
8634
8635 /* Parse a game score from the character string "game", and
8636    record it as the history of the current game.  The game
8637    score is NOT assumed to start from the standard position.
8638    The display is not updated in any way.
8639    */
8640 void
8641 ParseGameHistory(game)
8642      char *game;
8643 {
8644     ChessMove moveType;
8645     int fromX, fromY, toX, toY, boardIndex;
8646     char promoChar;
8647     char *p, *q;
8648     char buf[MSG_SIZ];
8649
8650     if (appData.debugMode)
8651       fprintf(debugFP, "Parsing game history: %s\n", game);
8652
8653     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8654     gameInfo.site = StrSave(appData.icsHost);
8655     gameInfo.date = PGNDate();
8656     gameInfo.round = StrSave("-");
8657
8658     /* Parse out names of players */
8659     while (*game == ' ') game++;
8660     p = buf;
8661     while (*game != ' ') *p++ = *game++;
8662     *p = NULLCHAR;
8663     gameInfo.white = StrSave(buf);
8664     while (*game == ' ') game++;
8665     p = buf;
8666     while (*game != ' ' && *game != '\n') *p++ = *game++;
8667     *p = NULLCHAR;
8668     gameInfo.black = StrSave(buf);
8669
8670     /* Parse moves */
8671     boardIndex = blackPlaysFirst ? 1 : 0;
8672     yynewstr(game);
8673     for (;;) {
8674         yyboardindex = boardIndex;
8675         moveType = (ChessMove) Myylex();
8676         switch (moveType) {
8677           case IllegalMove:             /* maybe suicide chess, etc. */
8678   if (appData.debugMode) {
8679     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8680     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8681     setbuf(debugFP, NULL);
8682   }
8683           case WhitePromotion:
8684           case BlackPromotion:
8685           case WhiteNonPromotion:
8686           case BlackNonPromotion:
8687           case NormalMove:
8688           case WhiteCapturesEnPassant:
8689           case BlackCapturesEnPassant:
8690           case WhiteKingSideCastle:
8691           case WhiteQueenSideCastle:
8692           case BlackKingSideCastle:
8693           case BlackQueenSideCastle:
8694           case WhiteKingSideCastleWild:
8695           case WhiteQueenSideCastleWild:
8696           case BlackKingSideCastleWild:
8697           case BlackQueenSideCastleWild:
8698           /* PUSH Fabien */
8699           case WhiteHSideCastleFR:
8700           case WhiteASideCastleFR:
8701           case BlackHSideCastleFR:
8702           case BlackASideCastleFR:
8703           /* POP Fabien */
8704             fromX = currentMoveString[0] - AAA;
8705             fromY = currentMoveString[1] - ONE;
8706             toX = currentMoveString[2] - AAA;
8707             toY = currentMoveString[3] - ONE;
8708             promoChar = currentMoveString[4];
8709             break;
8710           case WhiteDrop:
8711           case BlackDrop:
8712             fromX = moveType == WhiteDrop ?
8713               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8714             (int) CharToPiece(ToLower(currentMoveString[0]));
8715             fromY = DROP_RANK;
8716             toX = currentMoveString[2] - AAA;
8717             toY = currentMoveString[3] - ONE;
8718             promoChar = NULLCHAR;
8719             break;
8720           case AmbiguousMove:
8721             /* bug? */
8722             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8723   if (appData.debugMode) {
8724     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8725     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8726     setbuf(debugFP, NULL);
8727   }
8728             DisplayError(buf, 0);
8729             return;
8730           case ImpossibleMove:
8731             /* bug? */
8732             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8733   if (appData.debugMode) {
8734     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8735     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8736     setbuf(debugFP, NULL);
8737   }
8738             DisplayError(buf, 0);
8739             return;
8740           case EndOfFile:
8741             if (boardIndex < backwardMostMove) {
8742                 /* Oops, gap.  How did that happen? */
8743                 DisplayError(_("Gap in move list"), 0);
8744                 return;
8745             }
8746             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8747             if (boardIndex > forwardMostMove) {
8748                 forwardMostMove = boardIndex;
8749             }
8750             return;
8751           case ElapsedTime:
8752             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8753                 strcat(parseList[boardIndex-1], " ");
8754                 strcat(parseList[boardIndex-1], yy_text);
8755             }
8756             continue;
8757           case Comment:
8758           case PGNTag:
8759           case NAG:
8760           default:
8761             /* ignore */
8762             continue;
8763           case WhiteWins:
8764           case BlackWins:
8765           case GameIsDrawn:
8766           case GameUnfinished:
8767             if (gameMode == IcsExamining) {
8768                 if (boardIndex < backwardMostMove) {
8769                     /* Oops, gap.  How did that happen? */
8770                     return;
8771                 }
8772                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8773                 return;
8774             }
8775             gameInfo.result = moveType;
8776             p = strchr(yy_text, '{');
8777             if (p == NULL) p = strchr(yy_text, '(');
8778             if (p == NULL) {
8779                 p = yy_text;
8780                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8781             } else {
8782                 q = strchr(p, *p == '{' ? '}' : ')');
8783                 if (q != NULL) *q = NULLCHAR;
8784                 p++;
8785             }
8786             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8787             gameInfo.resultDetails = StrSave(p);
8788             continue;
8789         }
8790         if (boardIndex >= forwardMostMove &&
8791             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8792             backwardMostMove = blackPlaysFirst ? 1 : 0;
8793             return;
8794         }
8795         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8796                                  fromY, fromX, toY, toX, promoChar,
8797                                  parseList[boardIndex]);
8798         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8799         /* currentMoveString is set as a side-effect of yylex */
8800         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8801         strcat(moveList[boardIndex], "\n");
8802         boardIndex++;
8803         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8804         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8805           case MT_NONE:
8806           case MT_STALEMATE:
8807           default:
8808             break;
8809           case MT_CHECK:
8810             if(gameInfo.variant != VariantShogi)
8811                 strcat(parseList[boardIndex - 1], "+");
8812             break;
8813           case MT_CHECKMATE:
8814           case MT_STAINMATE:
8815             strcat(parseList[boardIndex - 1], "#");
8816             break;
8817         }
8818     }
8819 }
8820
8821
8822 /* Apply a move to the given board  */
8823 void
8824 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8825      int fromX, fromY, toX, toY;
8826      int promoChar;
8827      Board board;
8828 {
8829   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8830   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8831
8832     /* [HGM] compute & store e.p. status and castling rights for new position */
8833     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8834
8835       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8836       oldEP = (signed char)board[EP_STATUS];
8837       board[EP_STATUS] = EP_NONE;
8838
8839       if( board[toY][toX] != EmptySquare )
8840            board[EP_STATUS] = EP_CAPTURE;
8841
8842   if (fromY == DROP_RANK) {
8843         /* must be first */
8844         piece = board[toY][toX] = (ChessSquare) fromX;
8845   } else {
8846       int i;
8847
8848       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8849            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8850                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8851       } else
8852       if( board[fromY][fromX] == WhitePawn ) {
8853            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8854                board[EP_STATUS] = EP_PAWN_MOVE;
8855            if( toY-fromY==2) {
8856                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8857                         gameInfo.variant != VariantBerolina || toX < fromX)
8858                       board[EP_STATUS] = toX | berolina;
8859                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8860                         gameInfo.variant != VariantBerolina || toX > fromX)
8861                       board[EP_STATUS] = toX;
8862            }
8863       } else
8864       if( board[fromY][fromX] == BlackPawn ) {
8865            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8866                board[EP_STATUS] = EP_PAWN_MOVE;
8867            if( toY-fromY== -2) {
8868                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8869                         gameInfo.variant != VariantBerolina || toX < fromX)
8870                       board[EP_STATUS] = toX | berolina;
8871                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8872                         gameInfo.variant != VariantBerolina || toX > fromX)
8873                       board[EP_STATUS] = toX;
8874            }
8875        }
8876
8877        for(i=0; i<nrCastlingRights; i++) {
8878            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8879               board[CASTLING][i] == toX   && castlingRank[i] == toY
8880              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8881        }
8882
8883      if (fromX == toX && fromY == toY) return;
8884
8885      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8886      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8887      if(gameInfo.variant == VariantKnightmate)
8888          king += (int) WhiteUnicorn - (int) WhiteKing;
8889
8890     /* Code added by Tord: */
8891     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8892     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8893         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8894       board[fromY][fromX] = EmptySquare;
8895       board[toY][toX] = EmptySquare;
8896       if((toX > fromX) != (piece == WhiteRook)) {
8897         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8898       } else {
8899         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8900       }
8901     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8902                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8903       board[fromY][fromX] = EmptySquare;
8904       board[toY][toX] = EmptySquare;
8905       if((toX > fromX) != (piece == BlackRook)) {
8906         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8907       } else {
8908         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8909       }
8910     /* End of code added by Tord */
8911
8912     } else if (board[fromY][fromX] == king
8913         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8914         && toY == fromY && toX > fromX+1) {
8915         board[fromY][fromX] = EmptySquare;
8916         board[toY][toX] = king;
8917         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8918         board[fromY][BOARD_RGHT-1] = EmptySquare;
8919     } else if (board[fromY][fromX] == king
8920         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8921                && toY == fromY && toX < fromX-1) {
8922         board[fromY][fromX] = EmptySquare;
8923         board[toY][toX] = king;
8924         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8925         board[fromY][BOARD_LEFT] = EmptySquare;
8926     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8927                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8928                && toY >= BOARD_HEIGHT-promoRank
8929                ) {
8930         /* white pawn promotion */
8931         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8932         if (board[toY][toX] == EmptySquare) {
8933             board[toY][toX] = WhiteQueen;
8934         }
8935         if(gameInfo.variant==VariantBughouse ||
8936            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8937             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8938         board[fromY][fromX] = EmptySquare;
8939     } else if ((fromY == BOARD_HEIGHT-4)
8940                && (toX != fromX)
8941                && gameInfo.variant != VariantXiangqi
8942                && gameInfo.variant != VariantBerolina
8943                && (board[fromY][fromX] == WhitePawn)
8944                && (board[toY][toX] == EmptySquare)) {
8945         board[fromY][fromX] = EmptySquare;
8946         board[toY][toX] = WhitePawn;
8947         captured = board[toY - 1][toX];
8948         board[toY - 1][toX] = EmptySquare;
8949     } else if ((fromY == BOARD_HEIGHT-4)
8950                && (toX == fromX)
8951                && gameInfo.variant == VariantBerolina
8952                && (board[fromY][fromX] == WhitePawn)
8953                && (board[toY][toX] == EmptySquare)) {
8954         board[fromY][fromX] = EmptySquare;
8955         board[toY][toX] = WhitePawn;
8956         if(oldEP & EP_BEROLIN_A) {
8957                 captured = board[fromY][fromX-1];
8958                 board[fromY][fromX-1] = EmptySquare;
8959         }else{  captured = board[fromY][fromX+1];
8960                 board[fromY][fromX+1] = EmptySquare;
8961         }
8962     } else if (board[fromY][fromX] == king
8963         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8964                && toY == fromY && toX > fromX+1) {
8965         board[fromY][fromX] = EmptySquare;
8966         board[toY][toX] = king;
8967         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8968         board[fromY][BOARD_RGHT-1] = EmptySquare;
8969     } else if (board[fromY][fromX] == king
8970         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8971                && toY == fromY && toX < fromX-1) {
8972         board[fromY][fromX] = EmptySquare;
8973         board[toY][toX] = king;
8974         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8975         board[fromY][BOARD_LEFT] = EmptySquare;
8976     } else if (fromY == 7 && fromX == 3
8977                && board[fromY][fromX] == BlackKing
8978                && toY == 7 && toX == 5) {
8979         board[fromY][fromX] = EmptySquare;
8980         board[toY][toX] = BlackKing;
8981         board[fromY][7] = EmptySquare;
8982         board[toY][4] = BlackRook;
8983     } else if (fromY == 7 && fromX == 3
8984                && board[fromY][fromX] == BlackKing
8985                && toY == 7 && toX == 1) {
8986         board[fromY][fromX] = EmptySquare;
8987         board[toY][toX] = BlackKing;
8988         board[fromY][0] = EmptySquare;
8989         board[toY][2] = BlackRook;
8990     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8991                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8992                && toY < promoRank
8993                ) {
8994         /* black pawn promotion */
8995         board[toY][toX] = CharToPiece(ToLower(promoChar));
8996         if (board[toY][toX] == EmptySquare) {
8997             board[toY][toX] = BlackQueen;
8998         }
8999         if(gameInfo.variant==VariantBughouse ||
9000            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9001             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9002         board[fromY][fromX] = EmptySquare;
9003     } else if ((fromY == 3)
9004                && (toX != fromX)
9005                && gameInfo.variant != VariantXiangqi
9006                && gameInfo.variant != VariantBerolina
9007                && (board[fromY][fromX] == BlackPawn)
9008                && (board[toY][toX] == EmptySquare)) {
9009         board[fromY][fromX] = EmptySquare;
9010         board[toY][toX] = BlackPawn;
9011         captured = board[toY + 1][toX];
9012         board[toY + 1][toX] = EmptySquare;
9013     } else if ((fromY == 3)
9014                && (toX == fromX)
9015                && gameInfo.variant == VariantBerolina
9016                && (board[fromY][fromX] == BlackPawn)
9017                && (board[toY][toX] == EmptySquare)) {
9018         board[fromY][fromX] = EmptySquare;
9019         board[toY][toX] = BlackPawn;
9020         if(oldEP & EP_BEROLIN_A) {
9021                 captured = board[fromY][fromX-1];
9022                 board[fromY][fromX-1] = EmptySquare;
9023         }else{  captured = board[fromY][fromX+1];
9024                 board[fromY][fromX+1] = EmptySquare;
9025         }
9026     } else {
9027         board[toY][toX] = board[fromY][fromX];
9028         board[fromY][fromX] = EmptySquare;
9029     }
9030   }
9031
9032     if (gameInfo.holdingsWidth != 0) {
9033
9034       /* !!A lot more code needs to be written to support holdings  */
9035       /* [HGM] OK, so I have written it. Holdings are stored in the */
9036       /* penultimate board files, so they are automaticlly stored   */
9037       /* in the game history.                                       */
9038       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9039                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9040         /* Delete from holdings, by decreasing count */
9041         /* and erasing image if necessary            */
9042         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9043         if(p < (int) BlackPawn) { /* white drop */
9044              p -= (int)WhitePawn;
9045                  p = PieceToNumber((ChessSquare)p);
9046              if(p >= gameInfo.holdingsSize) p = 0;
9047              if(--board[p][BOARD_WIDTH-2] <= 0)
9048                   board[p][BOARD_WIDTH-1] = EmptySquare;
9049              if((int)board[p][BOARD_WIDTH-2] < 0)
9050                         board[p][BOARD_WIDTH-2] = 0;
9051         } else {                  /* black drop */
9052              p -= (int)BlackPawn;
9053                  p = PieceToNumber((ChessSquare)p);
9054              if(p >= gameInfo.holdingsSize) p = 0;
9055              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9056                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9057              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9058                         board[BOARD_HEIGHT-1-p][1] = 0;
9059         }
9060       }
9061       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9062           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9063         /* [HGM] holdings: Add to holdings, if holdings exist */
9064         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9065                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9066                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9067         }
9068         p = (int) captured;
9069         if (p >= (int) BlackPawn) {
9070           p -= (int)BlackPawn;
9071           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9072                   /* in Shogi restore piece to its original  first */
9073                   captured = (ChessSquare) (DEMOTED captured);
9074                   p = DEMOTED p;
9075           }
9076           p = PieceToNumber((ChessSquare)p);
9077           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9078           board[p][BOARD_WIDTH-2]++;
9079           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9080         } else {
9081           p -= (int)WhitePawn;
9082           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9083                   captured = (ChessSquare) (DEMOTED captured);
9084                   p = DEMOTED p;
9085           }
9086           p = PieceToNumber((ChessSquare)p);
9087           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9088           board[BOARD_HEIGHT-1-p][1]++;
9089           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9090         }
9091       }
9092     } else if (gameInfo.variant == VariantAtomic) {
9093       if (captured != EmptySquare) {
9094         int y, x;
9095         for (y = toY-1; y <= toY+1; y++) {
9096           for (x = toX-1; x <= toX+1; x++) {
9097             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9098                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9099               board[y][x] = EmptySquare;
9100             }
9101           }
9102         }
9103         board[toY][toX] = EmptySquare;
9104       }
9105     }
9106     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9107         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9108     } else
9109     if(promoChar == '+') {
9110         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9111         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9112     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9113         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9114     }
9115     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9116                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9117         // [HGM] superchess: take promotion piece out of holdings
9118         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9119         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9120             if(!--board[k][BOARD_WIDTH-2])
9121                 board[k][BOARD_WIDTH-1] = EmptySquare;
9122         } else {
9123             if(!--board[BOARD_HEIGHT-1-k][1])
9124                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9125         }
9126     }
9127
9128 }
9129
9130 /* Updates forwardMostMove */
9131 void
9132 MakeMove(fromX, fromY, toX, toY, promoChar)
9133      int fromX, fromY, toX, toY;
9134      int promoChar;
9135 {
9136 //    forwardMostMove++; // [HGM] bare: moved downstream
9137
9138     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9139         int timeLeft; static int lastLoadFlag=0; int king, piece;
9140         piece = boards[forwardMostMove][fromY][fromX];
9141         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9142         if(gameInfo.variant == VariantKnightmate)
9143             king += (int) WhiteUnicorn - (int) WhiteKing;
9144         if(forwardMostMove == 0) {
9145             if(blackPlaysFirst)
9146                 fprintf(serverMoves, "%s;", second.tidy);
9147             fprintf(serverMoves, "%s;", first.tidy);
9148             if(!blackPlaysFirst)
9149                 fprintf(serverMoves, "%s;", second.tidy);
9150         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9151         lastLoadFlag = loadFlag;
9152         // print base move
9153         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9154         // print castling suffix
9155         if( toY == fromY && piece == king ) {
9156             if(toX-fromX > 1)
9157                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9158             if(fromX-toX >1)
9159                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9160         }
9161         // e.p. suffix
9162         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9163              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9164              boards[forwardMostMove][toY][toX] == EmptySquare
9165              && fromX != toX && fromY != toY)
9166                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9167         // promotion suffix
9168         if(promoChar != NULLCHAR)
9169                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9170         if(!loadFlag) {
9171             fprintf(serverMoves, "/%d/%d",
9172                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9173             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9174             else                      timeLeft = blackTimeRemaining/1000;
9175             fprintf(serverMoves, "/%d", timeLeft);
9176         }
9177         fflush(serverMoves);
9178     }
9179
9180     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9181       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9182                         0, 1);
9183       return;
9184     }
9185     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9186     if (commentList[forwardMostMove+1] != NULL) {
9187         free(commentList[forwardMostMove+1]);
9188         commentList[forwardMostMove+1] = NULL;
9189     }
9190     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9191     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9192     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9193     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9194     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9195     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9196     gameInfo.result = GameUnfinished;
9197     if (gameInfo.resultDetails != NULL) {
9198         free(gameInfo.resultDetails);
9199         gameInfo.resultDetails = NULL;
9200     }
9201     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9202                               moveList[forwardMostMove - 1]);
9203     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9204                              PosFlags(forwardMostMove - 1),
9205                              fromY, fromX, toY, toX, promoChar,
9206                              parseList[forwardMostMove - 1]);
9207     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9208       case MT_NONE:
9209       case MT_STALEMATE:
9210       default:
9211         break;
9212       case MT_CHECK:
9213         if(gameInfo.variant != VariantShogi)
9214             strcat(parseList[forwardMostMove - 1], "+");
9215         break;
9216       case MT_CHECKMATE:
9217       case MT_STAINMATE:
9218         strcat(parseList[forwardMostMove - 1], "#");
9219         break;
9220     }
9221     if (appData.debugMode) {
9222         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9223     }
9224
9225 }
9226
9227 /* Updates currentMove if not pausing */
9228 void
9229 ShowMove(fromX, fromY, toX, toY)
9230 {
9231     int instant = (gameMode == PlayFromGameFile) ?
9232         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9233     if(appData.noGUI) return;
9234     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9235         if (!instant) {
9236             if (forwardMostMove == currentMove + 1) {
9237                 AnimateMove(boards[forwardMostMove - 1],
9238                             fromX, fromY, toX, toY);
9239             }
9240             if (appData.highlightLastMove) {
9241                 SetHighlights(fromX, fromY, toX, toY);
9242             }
9243         }
9244         currentMove = forwardMostMove;
9245     }
9246
9247     if (instant) return;
9248
9249     DisplayMove(currentMove - 1);
9250     DrawPosition(FALSE, boards[currentMove]);
9251     DisplayBothClocks();
9252     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9253     DisplayBook(currentMove);
9254 }
9255
9256 void SendEgtPath(ChessProgramState *cps)
9257 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9258         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9259
9260         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9261
9262         while(*p) {
9263             char c, *q = name+1, *r, *s;
9264
9265             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9266             while(*p && *p != ',') *q++ = *p++;
9267             *q++ = ':'; *q = 0;
9268             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9269                 strcmp(name, ",nalimov:") == 0 ) {
9270                 // take nalimov path from the menu-changeable option first, if it is defined
9271               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9272                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9273             } else
9274             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9275                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9276                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9277                 s = r = StrStr(s, ":") + 1; // beginning of path info
9278                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9279                 c = *r; *r = 0;             // temporarily null-terminate path info
9280                     *--q = 0;               // strip of trailig ':' from name
9281                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9282                 *r = c;
9283                 SendToProgram(buf,cps);     // send egtbpath command for this format
9284             }
9285             if(*p == ',') p++; // read away comma to position for next format name
9286         }
9287 }
9288
9289 void
9290 InitChessProgram(cps, setup)
9291      ChessProgramState *cps;
9292      int setup; /* [HGM] needed to setup FRC opening position */
9293 {
9294     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9295     if (appData.noChessProgram) return;
9296     hintRequested = FALSE;
9297     bookRequested = FALSE;
9298
9299     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9300     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9301     if(cps->memSize) { /* [HGM] memory */
9302       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9303         SendToProgram(buf, cps);
9304     }
9305     SendEgtPath(cps); /* [HGM] EGT */
9306     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9307       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9308         SendToProgram(buf, cps);
9309     }
9310
9311     SendToProgram(cps->initString, cps);
9312     if (gameInfo.variant != VariantNormal &&
9313         gameInfo.variant != VariantLoadable
9314         /* [HGM] also send variant if board size non-standard */
9315         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9316                                             ) {
9317       char *v = VariantName(gameInfo.variant);
9318       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9319         /* [HGM] in protocol 1 we have to assume all variants valid */
9320         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9321         DisplayFatalError(buf, 0, 1);
9322         return;
9323       }
9324
9325       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9326       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9327       if( gameInfo.variant == VariantXiangqi )
9328            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9329       if( gameInfo.variant == VariantShogi )
9330            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9331       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9332            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9333       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9334           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9335            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9336       if( gameInfo.variant == VariantCourier )
9337            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9338       if( gameInfo.variant == VariantSuper )
9339            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9340       if( gameInfo.variant == VariantGreat )
9341            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9342       if( gameInfo.variant == VariantSChess )
9343            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9344
9345       if(overruled) {
9346         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9347                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9348            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9349            if(StrStr(cps->variants, b) == NULL) {
9350                // specific sized variant not known, check if general sizing allowed
9351                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9352                    if(StrStr(cps->variants, "boardsize") == NULL) {
9353                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9354                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9355                        DisplayFatalError(buf, 0, 1);
9356                        return;
9357                    }
9358                    /* [HGM] here we really should compare with the maximum supported board size */
9359                }
9360            }
9361       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9362       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9363       SendToProgram(buf, cps);
9364     }
9365     currentlyInitializedVariant = gameInfo.variant;
9366
9367     /* [HGM] send opening position in FRC to first engine */
9368     if(setup) {
9369           SendToProgram("force\n", cps);
9370           SendBoard(cps, 0);
9371           /* engine is now in force mode! Set flag to wake it up after first move. */
9372           setboardSpoiledMachineBlack = 1;
9373     }
9374
9375     if (cps->sendICS) {
9376       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9377       SendToProgram(buf, cps);
9378     }
9379     cps->maybeThinking = FALSE;
9380     cps->offeredDraw = 0;
9381     if (!appData.icsActive) {
9382         SendTimeControl(cps, movesPerSession, timeControl,
9383                         timeIncrement, appData.searchDepth,
9384                         searchTime);
9385     }
9386     if (appData.showThinking
9387         // [HGM] thinking: four options require thinking output to be sent
9388         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9389                                 ) {
9390         SendToProgram("post\n", cps);
9391     }
9392     SendToProgram("hard\n", cps);
9393     if (!appData.ponderNextMove) {
9394         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9395            it without being sure what state we are in first.  "hard"
9396            is not a toggle, so that one is OK.
9397          */
9398         SendToProgram("easy\n", cps);
9399     }
9400     if (cps->usePing) {
9401       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9402       SendToProgram(buf, cps);
9403     }
9404     cps->initDone = TRUE;
9405 }
9406
9407
9408 void
9409 StartChessProgram(cps)
9410      ChessProgramState *cps;
9411 {
9412     char buf[MSG_SIZ];
9413     int err;
9414
9415     if (appData.noChessProgram) return;
9416     cps->initDone = FALSE;
9417
9418     if (strcmp(cps->host, "localhost") == 0) {
9419         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9420     } else if (*appData.remoteShell == NULLCHAR) {
9421         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9422     } else {
9423         if (*appData.remoteUser == NULLCHAR) {
9424           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9425                     cps->program);
9426         } else {
9427           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9428                     cps->host, appData.remoteUser, cps->program);
9429         }
9430         err = StartChildProcess(buf, "", &cps->pr);
9431     }
9432
9433     if (err != 0) {
9434       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9435         DisplayFatalError(buf, err, 1);
9436         cps->pr = NoProc;
9437         cps->isr = NULL;
9438         return;
9439     }
9440
9441     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9442     if (cps->protocolVersion > 1) {
9443       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9444       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9445       cps->comboCnt = 0;  //                and values of combo boxes
9446       SendToProgram(buf, cps);
9447     } else {
9448       SendToProgram("xboard\n", cps);
9449     }
9450 }
9451
9452 void
9453 TwoMachinesEventIfReady P((void))
9454 {
9455   static int curMess = 0;
9456   if (first.lastPing != first.lastPong) {
9457     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9458     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9459     return;
9460   }
9461   if (second.lastPing != second.lastPong) {
9462     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9463     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9464     return;
9465   }
9466   DisplayMessage("", ""); curMess = 0;
9467   ThawUI();
9468   TwoMachinesEvent();
9469 }
9470
9471 int
9472 CreateTourney(char *name)
9473 {
9474         FILE *f;
9475         if(name[0] == NULLCHAR) return 0;
9476         f = fopen(appData.tourneyFile, "r");
9477         if(f) { // file exists
9478             ParseArgsFromFile(f); // parse it
9479         } else {
9480             f = fopen(appData.tourneyFile, "w");
9481             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9482                 // create a file with tournament description
9483                 fprintf(f, "-participants {%s}\n", appData.participants);
9484                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9485                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9486                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9487                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9488                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9489                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9490                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9491                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9492                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9493                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9494                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9495                 if(searchTime > 0)
9496                         fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9497                 else {
9498                         fprintf(f, "-mps %d\n", appData.movesPerSession);
9499                         fprintf(f, "-tc %s\n", appData.timeControl);
9500                         fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9501                 }
9502                 fprintf(f, "-results \"\"\n");
9503             }
9504         }
9505         fclose(f);
9506         appData.noChessProgram = FALSE;
9507         appData.clockMode = TRUE;
9508         SetGNUMode();
9509         return 1;
9510 }
9511
9512 #define MAXENGINES 1000
9513 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9514
9515 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9516 {
9517     char buf[MSG_SIZ], *p, *q;
9518     int i=1;
9519     while(*names) {
9520         p = names; q = buf;
9521         while(*p && *p != '\n') *q++ = *p++;
9522         *q = 0;
9523         if(engineList[i]) free(engineList[i]);
9524         engineList[i] = strdup(buf);
9525         if(*p == '\n') p++;
9526         TidyProgramName(engineList[i], "localhost", buf);
9527         if(engineMnemonic[i]) free(engineMnemonic[i]);
9528         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9529             strcat(buf, " (");
9530             sscanf(q + 8, "%s", buf + strlen(buf));
9531             strcat(buf, ")");
9532         }
9533         engineMnemonic[i] = strdup(buf);
9534         names = p; i++;
9535       if(i > MAXENGINES - 2) break;
9536     }
9537     engineList[i] = NULL;
9538 }
9539
9540 // following implemented as macro to avoid type limitations
9541 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9542
9543 void SwapEngines(int n)
9544 {   // swap settings for first engine and other engine (so far only some selected options)
9545     int h;
9546     char *p;
9547     if(n == 0) return;
9548     SWAP(directory, p)
9549     SWAP(chessProgram, p)
9550     SWAP(isUCI, h)
9551     SWAP(hasOwnBookUCI, h)
9552     SWAP(protocolVersion, h)
9553     SWAP(reuse, h)
9554     SWAP(scoreIsAbsolute, h)
9555     SWAP(timeOdds, h)
9556     SWAP(logo, p)
9557     SWAP(pgnName, p)
9558 }
9559
9560 void
9561 SetPlayer(int player)
9562 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9563     int i;
9564     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9565     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9566     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9567     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9568     if(mnemonic[i]) {
9569         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9570         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9571         ParseArgsFromString(buf);
9572     }
9573     free(engineName);
9574 }
9575
9576 int
9577 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9578 {   // determine players from game number
9579     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9580
9581     if(appData.tourneyType == 0) {
9582         roundsPerCycle = (nPlayers - 1) | 1;
9583         pairingsPerRound = nPlayers / 2;
9584     } else if(appData.tourneyType > 0) {
9585         roundsPerCycle = nPlayers - appData.tourneyType;
9586         pairingsPerRound = appData.tourneyType;
9587     }
9588     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9589     gamesPerCycle = gamesPerRound * roundsPerCycle;
9590     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9591     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9592     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9593     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9594     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9595     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9596
9597     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9598     if(appData.roundSync) *syncInterval = gamesPerRound;
9599
9600     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9601
9602     if(appData.tourneyType == 0) {
9603         if(curPairing == (nPlayers-1)/2 ) {
9604             *whitePlayer = curRound;
9605             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9606         } else {
9607             *whitePlayer = curRound - pairingsPerRound + curPairing;
9608             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9609             *blackPlayer = curRound + pairingsPerRound - curPairing;
9610             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9611         }
9612     } else if(appData.tourneyType > 0) {
9613         *whitePlayer = curPairing;
9614         *blackPlayer = curRound + appData.tourneyType;
9615     }
9616
9617     // take care of white/black alternation per round. 
9618     // For cycles and games this is already taken care of by default, derived from matchGame!
9619     return curRound & 1;
9620 }
9621
9622 int
9623 NextTourneyGame(int nr, int *swapColors)
9624 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9625     char *p, *q;
9626     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9627     FILE *tf;
9628     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9629     tf = fopen(appData.tourneyFile, "r");
9630     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9631     ParseArgsFromFile(tf); fclose(tf);
9632     InitTimeControls(); // TC might be altered from tourney file
9633
9634     p = appData.participants;
9635     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9636     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9637
9638     if(syncInterval) {
9639         p = q = appData.results;
9640         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9641         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9642             DisplayMessage(_("Waiting for other game(s)"),"");
9643             waitingForGame = TRUE;
9644             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9645             return 0;
9646         }
9647         waitingForGame = FALSE;
9648     }
9649
9650     if(first.pr != NoProc) return 1; // engines already loaded
9651
9652     // redefine engines, engine dir, etc.
9653     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9654     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9655     SwapEngines(1);
9656     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9657     SwapEngines(1);         // and make that valid for second engine by swapping
9658     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9659     InitEngine(&second, 1);
9660     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9661     return 1;
9662 }
9663
9664 void
9665 NextMatchGame()
9666 {   // performs game initialization that does not invoke engines, and then tries to start the game
9667     int firstWhite, swapColors = 0;
9668     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9669     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9670     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9671     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9672     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9673     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9674     Reset(FALSE, first.pr != NoProc);
9675     appData.noChessProgram = FALSE;
9676     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9677     TwoMachinesEvent();
9678 }
9679
9680 void UserAdjudicationEvent( int result )
9681 {
9682     ChessMove gameResult = GameIsDrawn;
9683
9684     if( result > 0 ) {
9685         gameResult = WhiteWins;
9686     }
9687     else if( result < 0 ) {
9688         gameResult = BlackWins;
9689     }
9690
9691     if( gameMode == TwoMachinesPlay ) {
9692         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9693     }
9694 }
9695
9696
9697 // [HGM] save: calculate checksum of game to make games easily identifiable
9698 int StringCheckSum(char *s)
9699 {
9700         int i = 0;
9701         if(s==NULL) return 0;
9702         while(*s) i = i*259 + *s++;
9703         return i;
9704 }
9705
9706 int GameCheckSum()
9707 {
9708         int i, sum=0;
9709         for(i=backwardMostMove; i<forwardMostMove; i++) {
9710                 sum += pvInfoList[i].depth;
9711                 sum += StringCheckSum(parseList[i]);
9712                 sum += StringCheckSum(commentList[i]);
9713                 sum *= 261;
9714         }
9715         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9716         return sum + StringCheckSum(commentList[i]);
9717 } // end of save patch
9718
9719 void
9720 GameEnds(result, resultDetails, whosays)
9721      ChessMove result;
9722      char *resultDetails;
9723      int whosays;
9724 {
9725     GameMode nextGameMode;
9726     int isIcsGame;
9727     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9728
9729     if(endingGame) return; /* [HGM] crash: forbid recursion */
9730     endingGame = 1;
9731     if(twoBoards) { // [HGM] dual: switch back to one board
9732         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9733         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9734     }
9735     if (appData.debugMode) {
9736       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9737               result, resultDetails ? resultDetails : "(null)", whosays);
9738     }
9739
9740     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9741
9742     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9743         /* If we are playing on ICS, the server decides when the
9744            game is over, but the engine can offer to draw, claim
9745            a draw, or resign.
9746          */
9747 #if ZIPPY
9748         if (appData.zippyPlay && first.initDone) {
9749             if (result == GameIsDrawn) {
9750                 /* In case draw still needs to be claimed */
9751                 SendToICS(ics_prefix);
9752                 SendToICS("draw\n");
9753             } else if (StrCaseStr(resultDetails, "resign")) {
9754                 SendToICS(ics_prefix);
9755                 SendToICS("resign\n");
9756             }
9757         }
9758 #endif
9759         endingGame = 0; /* [HGM] crash */
9760         return;
9761     }
9762
9763     /* If we're loading the game from a file, stop */
9764     if (whosays == GE_FILE) {
9765       (void) StopLoadGameTimer();
9766       gameFileFP = NULL;
9767     }
9768
9769     /* Cancel draw offers */
9770     first.offeredDraw = second.offeredDraw = 0;
9771
9772     /* If this is an ICS game, only ICS can really say it's done;
9773        if not, anyone can. */
9774     isIcsGame = (gameMode == IcsPlayingWhite ||
9775                  gameMode == IcsPlayingBlack ||
9776                  gameMode == IcsObserving    ||
9777                  gameMode == IcsExamining);
9778
9779     if (!isIcsGame || whosays == GE_ICS) {
9780         /* OK -- not an ICS game, or ICS said it was done */
9781         StopClocks();
9782         if (!isIcsGame && !appData.noChessProgram)
9783           SetUserThinkingEnables();
9784
9785         /* [HGM] if a machine claims the game end we verify this claim */
9786         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9787             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9788                 char claimer;
9789                 ChessMove trueResult = (ChessMove) -1;
9790
9791                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9792                                             first.twoMachinesColor[0] :
9793                                             second.twoMachinesColor[0] ;
9794
9795                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9796                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9797                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9798                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9799                 } else
9800                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9801                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9802                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9803                 } else
9804                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9805                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9806                 }
9807
9808                 // now verify win claims, but not in drop games, as we don't understand those yet
9809                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9810                                                  || gameInfo.variant == VariantGreat) &&
9811                     (result == WhiteWins && claimer == 'w' ||
9812                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9813                       if (appData.debugMode) {
9814                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9815                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9816                       }
9817                       if(result != trueResult) {
9818                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9819                               result = claimer == 'w' ? BlackWins : WhiteWins;
9820                               resultDetails = buf;
9821                       }
9822                 } else
9823                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9824                     && (forwardMostMove <= backwardMostMove ||
9825                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9826                         (claimer=='b')==(forwardMostMove&1))
9827                                                                                   ) {
9828                       /* [HGM] verify: draws that were not flagged are false claims */
9829                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9830                       result = claimer == 'w' ? BlackWins : WhiteWins;
9831                       resultDetails = buf;
9832                 }
9833                 /* (Claiming a loss is accepted no questions asked!) */
9834             }
9835             /* [HGM] bare: don't allow bare King to win */
9836             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9837                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9838                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9839                && result != GameIsDrawn)
9840             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9841                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9842                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9843                         if(p >= 0 && p <= (int)WhiteKing) k++;
9844                 }
9845                 if (appData.debugMode) {
9846                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9847                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9848                 }
9849                 if(k <= 1) {
9850                         result = GameIsDrawn;
9851                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9852                         resultDetails = buf;
9853                 }
9854             }
9855         }
9856
9857
9858         if(serverMoves != NULL && !loadFlag) { char c = '=';
9859             if(result==WhiteWins) c = '+';
9860             if(result==BlackWins) c = '-';
9861             if(resultDetails != NULL)
9862                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9863         }
9864         if (resultDetails != NULL) {
9865             gameInfo.result = result;
9866             gameInfo.resultDetails = StrSave(resultDetails);
9867
9868             /* display last move only if game was not loaded from file */
9869             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9870                 DisplayMove(currentMove - 1);
9871
9872             if (forwardMostMove != 0) {
9873                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9874                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9875                                                                 ) {
9876                     if (*appData.saveGameFile != NULLCHAR) {
9877                         SaveGameToFile(appData.saveGameFile, TRUE);
9878                     } else if (appData.autoSaveGames) {
9879                         AutoSaveGame();
9880                     }
9881                     if (*appData.savePositionFile != NULLCHAR) {
9882                         SavePositionToFile(appData.savePositionFile);
9883                     }
9884                 }
9885             }
9886
9887             /* Tell program how game ended in case it is learning */
9888             /* [HGM] Moved this to after saving the PGN, just in case */
9889             /* engine died and we got here through time loss. In that */
9890             /* case we will get a fatal error writing the pipe, which */
9891             /* would otherwise lose us the PGN.                       */
9892             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9893             /* output during GameEnds should never be fatal anymore   */
9894             if (gameMode == MachinePlaysWhite ||
9895                 gameMode == MachinePlaysBlack ||
9896                 gameMode == TwoMachinesPlay ||
9897                 gameMode == IcsPlayingWhite ||
9898                 gameMode == IcsPlayingBlack ||
9899                 gameMode == BeginningOfGame) {
9900                 char buf[MSG_SIZ];
9901                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9902                         resultDetails);
9903                 if (first.pr != NoProc) {
9904                     SendToProgram(buf, &first);
9905                 }
9906                 if (second.pr != NoProc &&
9907                     gameMode == TwoMachinesPlay) {
9908                     SendToProgram(buf, &second);
9909                 }
9910             }
9911         }
9912
9913         if (appData.icsActive) {
9914             if (appData.quietPlay &&
9915                 (gameMode == IcsPlayingWhite ||
9916                  gameMode == IcsPlayingBlack)) {
9917                 SendToICS(ics_prefix);
9918                 SendToICS("set shout 1\n");
9919             }
9920             nextGameMode = IcsIdle;
9921             ics_user_moved = FALSE;
9922             /* clean up premove.  It's ugly when the game has ended and the
9923              * premove highlights are still on the board.
9924              */
9925             if (gotPremove) {
9926               gotPremove = FALSE;
9927               ClearPremoveHighlights();
9928               DrawPosition(FALSE, boards[currentMove]);
9929             }
9930             if (whosays == GE_ICS) {
9931                 switch (result) {
9932                 case WhiteWins:
9933                     if (gameMode == IcsPlayingWhite)
9934                         PlayIcsWinSound();
9935                     else if(gameMode == IcsPlayingBlack)
9936                         PlayIcsLossSound();
9937                     break;
9938                 case BlackWins:
9939                     if (gameMode == IcsPlayingBlack)
9940                         PlayIcsWinSound();
9941                     else if(gameMode == IcsPlayingWhite)
9942                         PlayIcsLossSound();
9943                     break;
9944                 case GameIsDrawn:
9945                     PlayIcsDrawSound();
9946                     break;
9947                 default:
9948                     PlayIcsUnfinishedSound();
9949                 }
9950             }
9951         } else if (gameMode == EditGame ||
9952                    gameMode == PlayFromGameFile ||
9953                    gameMode == AnalyzeMode ||
9954                    gameMode == AnalyzeFile) {
9955             nextGameMode = gameMode;
9956         } else {
9957             nextGameMode = EndOfGame;
9958         }
9959         pausing = FALSE;
9960         ModeHighlight();
9961     } else {
9962         nextGameMode = gameMode;
9963     }
9964
9965     if (appData.noChessProgram) {
9966         gameMode = nextGameMode;
9967         ModeHighlight();
9968         endingGame = 0; /* [HGM] crash */
9969         return;
9970     }
9971
9972     if (first.reuse) {
9973         /* Put first chess program into idle state */
9974         if (first.pr != NoProc &&
9975             (gameMode == MachinePlaysWhite ||
9976              gameMode == MachinePlaysBlack ||
9977              gameMode == TwoMachinesPlay ||
9978              gameMode == IcsPlayingWhite ||
9979              gameMode == IcsPlayingBlack ||
9980              gameMode == BeginningOfGame)) {
9981             SendToProgram("force\n", &first);
9982             if (first.usePing) {
9983               char buf[MSG_SIZ];
9984               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9985               SendToProgram(buf, &first);
9986             }
9987         }
9988     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9989         /* Kill off first chess program */
9990         if (first.isr != NULL)
9991           RemoveInputSource(first.isr);
9992         first.isr = NULL;
9993
9994         if (first.pr != NoProc) {
9995             ExitAnalyzeMode();
9996             DoSleep( appData.delayBeforeQuit );
9997             SendToProgram("quit\n", &first);
9998             DoSleep( appData.delayAfterQuit );
9999             DestroyChildProcess(first.pr, first.useSigterm);
10000         }
10001         first.pr = NoProc;
10002     }
10003     if (second.reuse) {
10004         /* Put second chess program into idle state */
10005         if (second.pr != NoProc &&
10006             gameMode == TwoMachinesPlay) {
10007             SendToProgram("force\n", &second);
10008             if (second.usePing) {
10009               char buf[MSG_SIZ];
10010               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10011               SendToProgram(buf, &second);
10012             }
10013         }
10014     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10015         /* Kill off second chess program */
10016         if (second.isr != NULL)
10017           RemoveInputSource(second.isr);
10018         second.isr = NULL;
10019
10020         if (second.pr != NoProc) {
10021             DoSleep( appData.delayBeforeQuit );
10022             SendToProgram("quit\n", &second);
10023             DoSleep( appData.delayAfterQuit );
10024             DestroyChildProcess(second.pr, second.useSigterm);
10025         }
10026         second.pr = NoProc;
10027     }
10028
10029     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10030         char resChar = '=';
10031         switch (result) {
10032         case WhiteWins:
10033           resChar = '+';
10034           if (first.twoMachinesColor[0] == 'w') {
10035             first.matchWins++;
10036           } else {
10037             second.matchWins++;
10038           }
10039           break;
10040         case BlackWins:
10041           resChar = '-';
10042           if (first.twoMachinesColor[0] == 'b') {
10043             first.matchWins++;
10044           } else {
10045             second.matchWins++;
10046           }
10047           break;
10048         case GameUnfinished:
10049           resChar = ' ';
10050         default:
10051           break;
10052         }
10053
10054         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10055         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10056             ReserveGame(nextGame, resChar); // sets nextGame
10057             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10058         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10059
10060         if (nextGame <= appData.matchGames && !abortMatch) {
10061             gameMode = nextGameMode;
10062             matchGame = nextGame; // this will be overruled in tourney mode!
10063             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10064             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10065             endingGame = 0; /* [HGM] crash */
10066             return;
10067         } else {
10068             gameMode = nextGameMode;
10069             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10070                      first.tidy, second.tidy,
10071                      first.matchWins, second.matchWins,
10072                      appData.matchGames - (first.matchWins + second.matchWins));
10073             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10074             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10075                 first.twoMachinesColor = "black\n";
10076                 second.twoMachinesColor = "white\n";
10077             } else {
10078                 first.twoMachinesColor = "white\n";
10079                 second.twoMachinesColor = "black\n";
10080             }
10081         }
10082     }
10083     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10084         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10085       ExitAnalyzeMode();
10086     gameMode = nextGameMode;
10087     ModeHighlight();
10088     endingGame = 0;  /* [HGM] crash */
10089     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10090         if(matchMode == TRUE) { // match through command line: exit with or without popup
10091             if(ranking) {
10092                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10093                 else ExitEvent(0);
10094             } else DisplayFatalError(buf, 0, 0);
10095         } else { // match through menu; just stop, with or without popup
10096             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10097             if(ranking){
10098                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10099             } else DisplayNote(buf);
10100       }
10101       if(ranking) free(ranking);
10102     }
10103 }
10104
10105 /* Assumes program was just initialized (initString sent).
10106    Leaves program in force mode. */
10107 void
10108 FeedMovesToProgram(cps, upto)
10109      ChessProgramState *cps;
10110      int upto;
10111 {
10112     int i;
10113
10114     if (appData.debugMode)
10115       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10116               startedFromSetupPosition ? "position and " : "",
10117               backwardMostMove, upto, cps->which);
10118     if(currentlyInitializedVariant != gameInfo.variant) {
10119       char buf[MSG_SIZ];
10120         // [HGM] variantswitch: make engine aware of new variant
10121         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10122                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10123         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10124         SendToProgram(buf, cps);
10125         currentlyInitializedVariant = gameInfo.variant;
10126     }
10127     SendToProgram("force\n", cps);
10128     if (startedFromSetupPosition) {
10129         SendBoard(cps, backwardMostMove);
10130     if (appData.debugMode) {
10131         fprintf(debugFP, "feedMoves\n");
10132     }
10133     }
10134     for (i = backwardMostMove; i < upto; i++) {
10135         SendMoveToProgram(i, cps);
10136     }
10137 }
10138
10139
10140 int
10141 ResurrectChessProgram()
10142 {
10143      /* The chess program may have exited.
10144         If so, restart it and feed it all the moves made so far. */
10145     static int doInit = 0;
10146
10147     if (appData.noChessProgram) return 1;
10148
10149     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10150         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10151         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10152         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10153     } else {
10154         if (first.pr != NoProc) return 1;
10155         StartChessProgram(&first);
10156     }
10157     InitChessProgram(&first, FALSE);
10158     FeedMovesToProgram(&first, currentMove);
10159
10160     if (!first.sendTime) {
10161         /* can't tell gnuchess what its clock should read,
10162            so we bow to its notion. */
10163         ResetClocks();
10164         timeRemaining[0][currentMove] = whiteTimeRemaining;
10165         timeRemaining[1][currentMove] = blackTimeRemaining;
10166     }
10167
10168     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10169                 appData.icsEngineAnalyze) && first.analysisSupport) {
10170       SendToProgram("analyze\n", &first);
10171       first.analyzing = TRUE;
10172     }
10173     return 1;
10174 }
10175
10176 /*
10177  * Button procedures
10178  */
10179 void
10180 Reset(redraw, init)
10181      int redraw, init;
10182 {
10183     int i;
10184
10185     if (appData.debugMode) {
10186         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10187                 redraw, init, gameMode);
10188     }
10189     CleanupTail(); // [HGM] vari: delete any stored variations
10190     pausing = pauseExamInvalid = FALSE;
10191     startedFromSetupPosition = blackPlaysFirst = FALSE;
10192     firstMove = TRUE;
10193     whiteFlag = blackFlag = FALSE;
10194     userOfferedDraw = FALSE;
10195     hintRequested = bookRequested = FALSE;
10196     first.maybeThinking = FALSE;
10197     second.maybeThinking = FALSE;
10198     first.bookSuspend = FALSE; // [HGM] book
10199     second.bookSuspend = FALSE;
10200     thinkOutput[0] = NULLCHAR;
10201     lastHint[0] = NULLCHAR;
10202     ClearGameInfo(&gameInfo);
10203     gameInfo.variant = StringToVariant(appData.variant);
10204     ics_user_moved = ics_clock_paused = FALSE;
10205     ics_getting_history = H_FALSE;
10206     ics_gamenum = -1;
10207     white_holding[0] = black_holding[0] = NULLCHAR;
10208     ClearProgramStats();
10209     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10210
10211     ResetFrontEnd();
10212     ClearHighlights();
10213     flipView = appData.flipView;
10214     ClearPremoveHighlights();
10215     gotPremove = FALSE;
10216     alarmSounded = FALSE;
10217
10218     GameEnds(EndOfFile, NULL, GE_PLAYER);
10219     if(appData.serverMovesName != NULL) {
10220         /* [HGM] prepare to make moves file for broadcasting */
10221         clock_t t = clock();
10222         if(serverMoves != NULL) fclose(serverMoves);
10223         serverMoves = fopen(appData.serverMovesName, "r");
10224         if(serverMoves != NULL) {
10225             fclose(serverMoves);
10226             /* delay 15 sec before overwriting, so all clients can see end */
10227             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10228         }
10229         serverMoves = fopen(appData.serverMovesName, "w");
10230     }
10231
10232     ExitAnalyzeMode();
10233     gameMode = BeginningOfGame;
10234     ModeHighlight();
10235     if(appData.icsActive) gameInfo.variant = VariantNormal;
10236     currentMove = forwardMostMove = backwardMostMove = 0;
10237     InitPosition(redraw);
10238     for (i = 0; i < MAX_MOVES; i++) {
10239         if (commentList[i] != NULL) {
10240             free(commentList[i]);
10241             commentList[i] = NULL;
10242         }
10243     }
10244     ResetClocks();
10245     timeRemaining[0][0] = whiteTimeRemaining;
10246     timeRemaining[1][0] = blackTimeRemaining;
10247
10248     if (first.pr == NULL) {
10249         StartChessProgram(&first);
10250     }
10251     if (init) {
10252             InitChessProgram(&first, startedFromSetupPosition);
10253     }
10254     DisplayTitle("");
10255     DisplayMessage("", "");
10256     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10257     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10258 }
10259
10260 void
10261 AutoPlayGameLoop()
10262 {
10263     for (;;) {
10264         if (!AutoPlayOneMove())
10265           return;
10266         if (matchMode || appData.timeDelay == 0)
10267           continue;
10268         if (appData.timeDelay < 0)
10269           return;
10270         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10271         break;
10272     }
10273 }
10274
10275
10276 int
10277 AutoPlayOneMove()
10278 {
10279     int fromX, fromY, toX, toY;
10280
10281     if (appData.debugMode) {
10282       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10283     }
10284
10285     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10286       return FALSE;
10287
10288     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10289       pvInfoList[currentMove].depth = programStats.depth;
10290       pvInfoList[currentMove].score = programStats.score;
10291       pvInfoList[currentMove].time  = 0;
10292       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10293     }
10294
10295     if (currentMove >= forwardMostMove) {
10296       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10297       gameMode = EditGame;
10298       ModeHighlight();
10299
10300       /* [AS] Clear current move marker at the end of a game */
10301       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10302
10303       return FALSE;
10304     }
10305
10306     toX = moveList[currentMove][2] - AAA;
10307     toY = moveList[currentMove][3] - ONE;
10308
10309     if (moveList[currentMove][1] == '@') {
10310         if (appData.highlightLastMove) {
10311             SetHighlights(-1, -1, toX, toY);
10312         }
10313     } else {
10314         fromX = moveList[currentMove][0] - AAA;
10315         fromY = moveList[currentMove][1] - ONE;
10316
10317         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10318
10319         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10320
10321         if (appData.highlightLastMove) {
10322             SetHighlights(fromX, fromY, toX, toY);
10323         }
10324     }
10325     DisplayMove(currentMove);
10326     SendMoveToProgram(currentMove++, &first);
10327     DisplayBothClocks();
10328     DrawPosition(FALSE, boards[currentMove]);
10329     // [HGM] PV info: always display, routine tests if empty
10330     DisplayComment(currentMove - 1, commentList[currentMove]);
10331     return TRUE;
10332 }
10333
10334
10335 int
10336 LoadGameOneMove(readAhead)
10337      ChessMove readAhead;
10338 {
10339     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10340     char promoChar = NULLCHAR;
10341     ChessMove moveType;
10342     char move[MSG_SIZ];
10343     char *p, *q;
10344
10345     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10346         gameMode != AnalyzeMode && gameMode != Training) {
10347         gameFileFP = NULL;
10348         return FALSE;
10349     }
10350
10351     yyboardindex = forwardMostMove;
10352     if (readAhead != EndOfFile) {
10353       moveType = readAhead;
10354     } else {
10355       if (gameFileFP == NULL)
10356           return FALSE;
10357       moveType = (ChessMove) Myylex();
10358     }
10359
10360     done = FALSE;
10361     switch (moveType) {
10362       case Comment:
10363         if (appData.debugMode)
10364           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10365         p = yy_text;
10366
10367         /* append the comment but don't display it */
10368         AppendComment(currentMove, p, FALSE);
10369         return TRUE;
10370
10371       case WhiteCapturesEnPassant:
10372       case BlackCapturesEnPassant:
10373       case WhitePromotion:
10374       case BlackPromotion:
10375       case WhiteNonPromotion:
10376       case BlackNonPromotion:
10377       case NormalMove:
10378       case WhiteKingSideCastle:
10379       case WhiteQueenSideCastle:
10380       case BlackKingSideCastle:
10381       case BlackQueenSideCastle:
10382       case WhiteKingSideCastleWild:
10383       case WhiteQueenSideCastleWild:
10384       case BlackKingSideCastleWild:
10385       case BlackQueenSideCastleWild:
10386       /* PUSH Fabien */
10387       case WhiteHSideCastleFR:
10388       case WhiteASideCastleFR:
10389       case BlackHSideCastleFR:
10390       case BlackASideCastleFR:
10391       /* POP Fabien */
10392         if (appData.debugMode)
10393           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10394         fromX = currentMoveString[0] - AAA;
10395         fromY = currentMoveString[1] - ONE;
10396         toX = currentMoveString[2] - AAA;
10397         toY = currentMoveString[3] - ONE;
10398         promoChar = currentMoveString[4];
10399         break;
10400
10401       case WhiteDrop:
10402       case BlackDrop:
10403         if (appData.debugMode)
10404           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10405         fromX = moveType == WhiteDrop ?
10406           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10407         (int) CharToPiece(ToLower(currentMoveString[0]));
10408         fromY = DROP_RANK;
10409         toX = currentMoveString[2] - AAA;
10410         toY = currentMoveString[3] - ONE;
10411         break;
10412
10413       case WhiteWins:
10414       case BlackWins:
10415       case GameIsDrawn:
10416       case GameUnfinished:
10417         if (appData.debugMode)
10418           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10419         p = strchr(yy_text, '{');
10420         if (p == NULL) p = strchr(yy_text, '(');
10421         if (p == NULL) {
10422             p = yy_text;
10423             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10424         } else {
10425             q = strchr(p, *p == '{' ? '}' : ')');
10426             if (q != NULL) *q = NULLCHAR;
10427             p++;
10428         }
10429         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10430         GameEnds(moveType, p, GE_FILE);
10431         done = TRUE;
10432         if (cmailMsgLoaded) {
10433             ClearHighlights();
10434             flipView = WhiteOnMove(currentMove);
10435             if (moveType == GameUnfinished) flipView = !flipView;
10436             if (appData.debugMode)
10437               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10438         }
10439         break;
10440
10441       case EndOfFile:
10442         if (appData.debugMode)
10443           fprintf(debugFP, "Parser hit end of file\n");
10444         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10445           case MT_NONE:
10446           case MT_CHECK:
10447             break;
10448           case MT_CHECKMATE:
10449           case MT_STAINMATE:
10450             if (WhiteOnMove(currentMove)) {
10451                 GameEnds(BlackWins, "Black mates", GE_FILE);
10452             } else {
10453                 GameEnds(WhiteWins, "White mates", GE_FILE);
10454             }
10455             break;
10456           case MT_STALEMATE:
10457             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10458             break;
10459         }
10460         done = TRUE;
10461         break;
10462
10463       case MoveNumberOne:
10464         if (lastLoadGameStart == GNUChessGame) {
10465             /* GNUChessGames have numbers, but they aren't move numbers */
10466             if (appData.debugMode)
10467               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10468                       yy_text, (int) moveType);
10469             return LoadGameOneMove(EndOfFile); /* tail recursion */
10470         }
10471         /* else fall thru */
10472
10473       case XBoardGame:
10474       case GNUChessGame:
10475       case PGNTag:
10476         /* Reached start of next game in file */
10477         if (appData.debugMode)
10478           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10479         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10480           case MT_NONE:
10481           case MT_CHECK:
10482             break;
10483           case MT_CHECKMATE:
10484           case MT_STAINMATE:
10485             if (WhiteOnMove(currentMove)) {
10486                 GameEnds(BlackWins, "Black mates", GE_FILE);
10487             } else {
10488                 GameEnds(WhiteWins, "White mates", GE_FILE);
10489             }
10490             break;
10491           case MT_STALEMATE:
10492             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10493             break;
10494         }
10495         done = TRUE;
10496         break;
10497
10498       case PositionDiagram:     /* should not happen; ignore */
10499       case ElapsedTime:         /* ignore */
10500       case NAG:                 /* ignore */
10501         if (appData.debugMode)
10502           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10503                   yy_text, (int) moveType);
10504         return LoadGameOneMove(EndOfFile); /* tail recursion */
10505
10506       case IllegalMove:
10507         if (appData.testLegality) {
10508             if (appData.debugMode)
10509               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10510             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10511                     (forwardMostMove / 2) + 1,
10512                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10513             DisplayError(move, 0);
10514             done = TRUE;
10515         } else {
10516             if (appData.debugMode)
10517               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10518                       yy_text, currentMoveString);
10519             fromX = currentMoveString[0] - AAA;
10520             fromY = currentMoveString[1] - ONE;
10521             toX = currentMoveString[2] - AAA;
10522             toY = currentMoveString[3] - ONE;
10523             promoChar = currentMoveString[4];
10524         }
10525         break;
10526
10527       case AmbiguousMove:
10528         if (appData.debugMode)
10529           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10530         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10531                 (forwardMostMove / 2) + 1,
10532                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10533         DisplayError(move, 0);
10534         done = TRUE;
10535         break;
10536
10537       default:
10538       case ImpossibleMove:
10539         if (appData.debugMode)
10540           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10541         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10542                 (forwardMostMove / 2) + 1,
10543                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10544         DisplayError(move, 0);
10545         done = TRUE;
10546         break;
10547     }
10548
10549     if (done) {
10550         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10551             DrawPosition(FALSE, boards[currentMove]);
10552             DisplayBothClocks();
10553             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10554               DisplayComment(currentMove - 1, commentList[currentMove]);
10555         }
10556         (void) StopLoadGameTimer();
10557         gameFileFP = NULL;
10558         cmailOldMove = forwardMostMove;
10559         return FALSE;
10560     } else {
10561         /* currentMoveString is set as a side-effect of yylex */
10562
10563         thinkOutput[0] = NULLCHAR;
10564         MakeMove(fromX, fromY, toX, toY, promoChar);
10565         currentMove = forwardMostMove;
10566         return TRUE;
10567     }
10568 }
10569
10570 /* Load the nth game from the given file */
10571 int
10572 LoadGameFromFile(filename, n, title, useList)
10573      char *filename;
10574      int n;
10575      char *title;
10576      /*Boolean*/ int useList;
10577 {
10578     FILE *f;
10579     char buf[MSG_SIZ];
10580
10581     if (strcmp(filename, "-") == 0) {
10582         f = stdin;
10583         title = "stdin";
10584     } else {
10585         f = fopen(filename, "rb");
10586         if (f == NULL) {
10587           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10588             DisplayError(buf, errno);
10589             return FALSE;
10590         }
10591     }
10592     if (fseek(f, 0, 0) == -1) {
10593         /* f is not seekable; probably a pipe */
10594         useList = FALSE;
10595     }
10596     if (useList && n == 0) {
10597         int error = GameListBuild(f);
10598         if (error) {
10599             DisplayError(_("Cannot build game list"), error);
10600         } else if (!ListEmpty(&gameList) &&
10601                    ((ListGame *) gameList.tailPred)->number > 1) {
10602             GameListPopUp(f, title);
10603             return TRUE;
10604         }
10605         GameListDestroy();
10606         n = 1;
10607     }
10608     if (n == 0) n = 1;
10609     return LoadGame(f, n, title, FALSE);
10610 }
10611
10612
10613 void
10614 MakeRegisteredMove()
10615 {
10616     int fromX, fromY, toX, toY;
10617     char promoChar;
10618     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10619         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10620           case CMAIL_MOVE:
10621           case CMAIL_DRAW:
10622             if (appData.debugMode)
10623               fprintf(debugFP, "Restoring %s for game %d\n",
10624                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10625
10626             thinkOutput[0] = NULLCHAR;
10627             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10628             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10629             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10630             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10631             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10632             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10633             MakeMove(fromX, fromY, toX, toY, promoChar);
10634             ShowMove(fromX, fromY, toX, toY);
10635
10636             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10637               case MT_NONE:
10638               case MT_CHECK:
10639                 break;
10640
10641               case MT_CHECKMATE:
10642               case MT_STAINMATE:
10643                 if (WhiteOnMove(currentMove)) {
10644                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10645                 } else {
10646                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10647                 }
10648                 break;
10649
10650               case MT_STALEMATE:
10651                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10652                 break;
10653             }
10654
10655             break;
10656
10657           case CMAIL_RESIGN:
10658             if (WhiteOnMove(currentMove)) {
10659                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10660             } else {
10661                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10662             }
10663             break;
10664
10665           case CMAIL_ACCEPT:
10666             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10667             break;
10668
10669           default:
10670             break;
10671         }
10672     }
10673
10674     return;
10675 }
10676
10677 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10678 int
10679 CmailLoadGame(f, gameNumber, title, useList)
10680      FILE *f;
10681      int gameNumber;
10682      char *title;
10683      int useList;
10684 {
10685     int retVal;
10686
10687     if (gameNumber > nCmailGames) {
10688         DisplayError(_("No more games in this message"), 0);
10689         return FALSE;
10690     }
10691     if (f == lastLoadGameFP) {
10692         int offset = gameNumber - lastLoadGameNumber;
10693         if (offset == 0) {
10694             cmailMsg[0] = NULLCHAR;
10695             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10696                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10697                 nCmailMovesRegistered--;
10698             }
10699             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10700             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10701                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10702             }
10703         } else {
10704             if (! RegisterMove()) return FALSE;
10705         }
10706     }
10707
10708     retVal = LoadGame(f, gameNumber, title, useList);
10709
10710     /* Make move registered during previous look at this game, if any */
10711     MakeRegisteredMove();
10712
10713     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10714         commentList[currentMove]
10715           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10716         DisplayComment(currentMove - 1, commentList[currentMove]);
10717     }
10718
10719     return retVal;
10720 }
10721
10722 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10723 int
10724 ReloadGame(offset)
10725      int offset;
10726 {
10727     int gameNumber = lastLoadGameNumber + offset;
10728     if (lastLoadGameFP == NULL) {
10729         DisplayError(_("No game has been loaded yet"), 0);
10730         return FALSE;
10731     }
10732     if (gameNumber <= 0) {
10733         DisplayError(_("Can't back up any further"), 0);
10734         return FALSE;
10735     }
10736     if (cmailMsgLoaded) {
10737         return CmailLoadGame(lastLoadGameFP, gameNumber,
10738                              lastLoadGameTitle, lastLoadGameUseList);
10739     } else {
10740         return LoadGame(lastLoadGameFP, gameNumber,
10741                         lastLoadGameTitle, lastLoadGameUseList);
10742     }
10743 }
10744
10745
10746
10747 /* Load the nth game from open file f */
10748 int
10749 LoadGame(f, gameNumber, title, useList)
10750      FILE *f;
10751      int gameNumber;
10752      char *title;
10753      int useList;
10754 {
10755     ChessMove cm;
10756     char buf[MSG_SIZ];
10757     int gn = gameNumber;
10758     ListGame *lg = NULL;
10759     int numPGNTags = 0;
10760     int err;
10761     GameMode oldGameMode;
10762     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10763
10764     if (appData.debugMode)
10765         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10766
10767     if (gameMode == Training )
10768         SetTrainingModeOff();
10769
10770     oldGameMode = gameMode;
10771     if (gameMode != BeginningOfGame) {
10772       Reset(FALSE, TRUE);
10773     }
10774
10775     gameFileFP = f;
10776     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10777         fclose(lastLoadGameFP);
10778     }
10779
10780     if (useList) {
10781         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10782
10783         if (lg) {
10784             fseek(f, lg->offset, 0);
10785             GameListHighlight(gameNumber);
10786             gn = 1;
10787         }
10788         else {
10789             DisplayError(_("Game number out of range"), 0);
10790             return FALSE;
10791         }
10792     } else {
10793         GameListDestroy();
10794         if (fseek(f, 0, 0) == -1) {
10795             if (f == lastLoadGameFP ?
10796                 gameNumber == lastLoadGameNumber + 1 :
10797                 gameNumber == 1) {
10798                 gn = 1;
10799             } else {
10800                 DisplayError(_("Can't seek on game file"), 0);
10801                 return FALSE;
10802             }
10803         }
10804     }
10805     lastLoadGameFP = f;
10806     lastLoadGameNumber = gameNumber;
10807     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10808     lastLoadGameUseList = useList;
10809
10810     yynewfile(f);
10811
10812     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10813       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10814                 lg->gameInfo.black);
10815             DisplayTitle(buf);
10816     } else if (*title != NULLCHAR) {
10817         if (gameNumber > 1) {
10818           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10819             DisplayTitle(buf);
10820         } else {
10821             DisplayTitle(title);
10822         }
10823     }
10824
10825     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10826         gameMode = PlayFromGameFile;
10827         ModeHighlight();
10828     }
10829
10830     currentMove = forwardMostMove = backwardMostMove = 0;
10831     CopyBoard(boards[0], initialPosition);
10832     StopClocks();
10833
10834     /*
10835      * Skip the first gn-1 games in the file.
10836      * Also skip over anything that precedes an identifiable
10837      * start of game marker, to avoid being confused by
10838      * garbage at the start of the file.  Currently
10839      * recognized start of game markers are the move number "1",
10840      * the pattern "gnuchess .* game", the pattern
10841      * "^[#;%] [^ ]* game file", and a PGN tag block.
10842      * A game that starts with one of the latter two patterns
10843      * will also have a move number 1, possibly
10844      * following a position diagram.
10845      * 5-4-02: Let's try being more lenient and allowing a game to
10846      * start with an unnumbered move.  Does that break anything?
10847      */
10848     cm = lastLoadGameStart = EndOfFile;
10849     while (gn > 0) {
10850         yyboardindex = forwardMostMove;
10851         cm = (ChessMove) Myylex();
10852         switch (cm) {
10853           case EndOfFile:
10854             if (cmailMsgLoaded) {
10855                 nCmailGames = CMAIL_MAX_GAMES - gn;
10856             } else {
10857                 Reset(TRUE, TRUE);
10858                 DisplayError(_("Game not found in file"), 0);
10859             }
10860             return FALSE;
10861
10862           case GNUChessGame:
10863           case XBoardGame:
10864             gn--;
10865             lastLoadGameStart = cm;
10866             break;
10867
10868           case MoveNumberOne:
10869             switch (lastLoadGameStart) {
10870               case GNUChessGame:
10871               case XBoardGame:
10872               case PGNTag:
10873                 break;
10874               case MoveNumberOne:
10875               case EndOfFile:
10876                 gn--;           /* count this game */
10877                 lastLoadGameStart = cm;
10878                 break;
10879               default:
10880                 /* impossible */
10881                 break;
10882             }
10883             break;
10884
10885           case PGNTag:
10886             switch (lastLoadGameStart) {
10887               case GNUChessGame:
10888               case PGNTag:
10889               case MoveNumberOne:
10890               case EndOfFile:
10891                 gn--;           /* count this game */
10892                 lastLoadGameStart = cm;
10893                 break;
10894               case XBoardGame:
10895                 lastLoadGameStart = cm; /* game counted already */
10896                 break;
10897               default:
10898                 /* impossible */
10899                 break;
10900             }
10901             if (gn > 0) {
10902                 do {
10903                     yyboardindex = forwardMostMove;
10904                     cm = (ChessMove) Myylex();
10905                 } while (cm == PGNTag || cm == Comment);
10906             }
10907             break;
10908
10909           case WhiteWins:
10910           case BlackWins:
10911           case GameIsDrawn:
10912             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10913                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10914                     != CMAIL_OLD_RESULT) {
10915                     nCmailResults ++ ;
10916                     cmailResult[  CMAIL_MAX_GAMES
10917                                 - gn - 1] = CMAIL_OLD_RESULT;
10918                 }
10919             }
10920             break;
10921
10922           case NormalMove:
10923             /* Only a NormalMove can be at the start of a game
10924              * without a position diagram. */
10925             if (lastLoadGameStart == EndOfFile ) {
10926               gn--;
10927               lastLoadGameStart = MoveNumberOne;
10928             }
10929             break;
10930
10931           default:
10932             break;
10933         }
10934     }
10935
10936     if (appData.debugMode)
10937       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10938
10939     if (cm == XBoardGame) {
10940         /* Skip any header junk before position diagram and/or move 1 */
10941         for (;;) {
10942             yyboardindex = forwardMostMove;
10943             cm = (ChessMove) Myylex();
10944
10945             if (cm == EndOfFile ||
10946                 cm == GNUChessGame || cm == XBoardGame) {
10947                 /* Empty game; pretend end-of-file and handle later */
10948                 cm = EndOfFile;
10949                 break;
10950             }
10951
10952             if (cm == MoveNumberOne || cm == PositionDiagram ||
10953                 cm == PGNTag || cm == Comment)
10954               break;
10955         }
10956     } else if (cm == GNUChessGame) {
10957         if (gameInfo.event != NULL) {
10958             free(gameInfo.event);
10959         }
10960         gameInfo.event = StrSave(yy_text);
10961     }
10962
10963     startedFromSetupPosition = FALSE;
10964     while (cm == PGNTag) {
10965         if (appData.debugMode)
10966           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10967         err = ParsePGNTag(yy_text, &gameInfo);
10968         if (!err) numPGNTags++;
10969
10970         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10971         if(gameInfo.variant != oldVariant) {
10972             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10973             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10974             InitPosition(TRUE);
10975             oldVariant = gameInfo.variant;
10976             if (appData.debugMode)
10977               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10978         }
10979
10980
10981         if (gameInfo.fen != NULL) {
10982           Board initial_position;
10983           startedFromSetupPosition = TRUE;
10984           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10985             Reset(TRUE, TRUE);
10986             DisplayError(_("Bad FEN position in file"), 0);
10987             return FALSE;
10988           }
10989           CopyBoard(boards[0], initial_position);
10990           if (blackPlaysFirst) {
10991             currentMove = forwardMostMove = backwardMostMove = 1;
10992             CopyBoard(boards[1], initial_position);
10993             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10994             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10995             timeRemaining[0][1] = whiteTimeRemaining;
10996             timeRemaining[1][1] = blackTimeRemaining;
10997             if (commentList[0] != NULL) {
10998               commentList[1] = commentList[0];
10999               commentList[0] = NULL;
11000             }
11001           } else {
11002             currentMove = forwardMostMove = backwardMostMove = 0;
11003           }
11004           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11005           {   int i;
11006               initialRulePlies = FENrulePlies;
11007               for( i=0; i< nrCastlingRights; i++ )
11008                   initialRights[i] = initial_position[CASTLING][i];
11009           }
11010           yyboardindex = forwardMostMove;
11011           free(gameInfo.fen);
11012           gameInfo.fen = NULL;
11013         }
11014
11015         yyboardindex = forwardMostMove;
11016         cm = (ChessMove) Myylex();
11017
11018         /* Handle comments interspersed among the tags */
11019         while (cm == Comment) {
11020             char *p;
11021             if (appData.debugMode)
11022               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11023             p = yy_text;
11024             AppendComment(currentMove, p, FALSE);
11025             yyboardindex = forwardMostMove;
11026             cm = (ChessMove) Myylex();
11027         }
11028     }
11029
11030     /* don't rely on existence of Event tag since if game was
11031      * pasted from clipboard the Event tag may not exist
11032      */
11033     if (numPGNTags > 0){
11034         char *tags;
11035         if (gameInfo.variant == VariantNormal) {
11036           VariantClass v = StringToVariant(gameInfo.event);
11037           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11038           if(v < VariantShogi) gameInfo.variant = v;
11039         }
11040         if (!matchMode) {
11041           if( appData.autoDisplayTags ) {
11042             tags = PGNTags(&gameInfo);
11043             TagsPopUp(tags, CmailMsg());
11044             free(tags);
11045           }
11046         }
11047     } else {
11048         /* Make something up, but don't display it now */
11049         SetGameInfo();
11050         TagsPopDown();
11051     }
11052
11053     if (cm == PositionDiagram) {
11054         int i, j;
11055         char *p;
11056         Board initial_position;
11057
11058         if (appData.debugMode)
11059           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11060
11061         if (!startedFromSetupPosition) {
11062             p = yy_text;
11063             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11064               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11065                 switch (*p) {
11066                   case '{':
11067                   case '[':
11068                   case '-':
11069                   case ' ':
11070                   case '\t':
11071                   case '\n':
11072                   case '\r':
11073                     break;
11074                   default:
11075                     initial_position[i][j++] = CharToPiece(*p);
11076                     break;
11077                 }
11078             while (*p == ' ' || *p == '\t' ||
11079                    *p == '\n' || *p == '\r') p++;
11080
11081             if (strncmp(p, "black", strlen("black"))==0)
11082               blackPlaysFirst = TRUE;
11083             else
11084               blackPlaysFirst = FALSE;
11085             startedFromSetupPosition = TRUE;
11086
11087             CopyBoard(boards[0], initial_position);
11088             if (blackPlaysFirst) {
11089                 currentMove = forwardMostMove = backwardMostMove = 1;
11090                 CopyBoard(boards[1], initial_position);
11091                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11092                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11093                 timeRemaining[0][1] = whiteTimeRemaining;
11094                 timeRemaining[1][1] = blackTimeRemaining;
11095                 if (commentList[0] != NULL) {
11096                     commentList[1] = commentList[0];
11097                     commentList[0] = NULL;
11098                 }
11099             } else {
11100                 currentMove = forwardMostMove = backwardMostMove = 0;
11101             }
11102         }
11103         yyboardindex = forwardMostMove;
11104         cm = (ChessMove) Myylex();
11105     }
11106
11107     if (first.pr == NoProc) {
11108         StartChessProgram(&first);
11109     }
11110     InitChessProgram(&first, FALSE);
11111     SendToProgram("force\n", &first);
11112     if (startedFromSetupPosition) {
11113         SendBoard(&first, forwardMostMove);
11114     if (appData.debugMode) {
11115         fprintf(debugFP, "Load Game\n");
11116     }
11117         DisplayBothClocks();
11118     }
11119
11120     /* [HGM] server: flag to write setup moves in broadcast file as one */
11121     loadFlag = appData.suppressLoadMoves;
11122
11123     while (cm == Comment) {
11124         char *p;
11125         if (appData.debugMode)
11126           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11127         p = yy_text;
11128         AppendComment(currentMove, p, FALSE);
11129         yyboardindex = forwardMostMove;
11130         cm = (ChessMove) Myylex();
11131     }
11132
11133     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11134         cm == WhiteWins || cm == BlackWins ||
11135         cm == GameIsDrawn || cm == GameUnfinished) {
11136         DisplayMessage("", _("No moves in game"));
11137         if (cmailMsgLoaded) {
11138             if (appData.debugMode)
11139               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11140             ClearHighlights();
11141             flipView = FALSE;
11142         }
11143         DrawPosition(FALSE, boards[currentMove]);
11144         DisplayBothClocks();
11145         gameMode = EditGame;
11146         ModeHighlight();
11147         gameFileFP = NULL;
11148         cmailOldMove = 0;
11149         return TRUE;
11150     }
11151
11152     // [HGM] PV info: routine tests if comment empty
11153     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11154         DisplayComment(currentMove - 1, commentList[currentMove]);
11155     }
11156     if (!matchMode && appData.timeDelay != 0)
11157       DrawPosition(FALSE, boards[currentMove]);
11158
11159     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11160       programStats.ok_to_send = 1;
11161     }
11162
11163     /* if the first token after the PGN tags is a move
11164      * and not move number 1, retrieve it from the parser
11165      */
11166     if (cm != MoveNumberOne)
11167         LoadGameOneMove(cm);
11168
11169     /* load the remaining moves from the file */
11170     while (LoadGameOneMove(EndOfFile)) {
11171       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11172       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11173     }
11174
11175     /* rewind to the start of the game */
11176     currentMove = backwardMostMove;
11177
11178     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11179
11180     if (oldGameMode == AnalyzeFile ||
11181         oldGameMode == AnalyzeMode) {
11182       AnalyzeFileEvent();
11183     }
11184
11185     if (matchMode || appData.timeDelay == 0) {
11186       ToEndEvent();
11187       gameMode = EditGame;
11188       ModeHighlight();
11189     } else if (appData.timeDelay > 0) {
11190       AutoPlayGameLoop();
11191     }
11192
11193     if (appData.debugMode)
11194         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11195
11196     loadFlag = 0; /* [HGM] true game starts */
11197     return TRUE;
11198 }
11199
11200 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11201 int
11202 ReloadPosition(offset)
11203      int offset;
11204 {
11205     int positionNumber = lastLoadPositionNumber + offset;
11206     if (lastLoadPositionFP == NULL) {
11207         DisplayError(_("No position has been loaded yet"), 0);
11208         return FALSE;
11209     }
11210     if (positionNumber <= 0) {
11211         DisplayError(_("Can't back up any further"), 0);
11212         return FALSE;
11213     }
11214     return LoadPosition(lastLoadPositionFP, positionNumber,
11215                         lastLoadPositionTitle);
11216 }
11217
11218 /* Load the nth position from the given file */
11219 int
11220 LoadPositionFromFile(filename, n, title)
11221      char *filename;
11222      int n;
11223      char *title;
11224 {
11225     FILE *f;
11226     char buf[MSG_SIZ];
11227
11228     if (strcmp(filename, "-") == 0) {
11229         return LoadPosition(stdin, n, "stdin");
11230     } else {
11231         f = fopen(filename, "rb");
11232         if (f == NULL) {
11233             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11234             DisplayError(buf, errno);
11235             return FALSE;
11236         } else {
11237             return LoadPosition(f, n, title);
11238         }
11239     }
11240 }
11241
11242 /* Load the nth position from the given open file, and close it */
11243 int
11244 LoadPosition(f, positionNumber, title)
11245      FILE *f;
11246      int positionNumber;
11247      char *title;
11248 {
11249     char *p, line[MSG_SIZ];
11250     Board initial_position;
11251     int i, j, fenMode, pn;
11252
11253     if (gameMode == Training )
11254         SetTrainingModeOff();
11255
11256     if (gameMode != BeginningOfGame) {
11257         Reset(FALSE, TRUE);
11258     }
11259     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11260         fclose(lastLoadPositionFP);
11261     }
11262     if (positionNumber == 0) positionNumber = 1;
11263     lastLoadPositionFP = f;
11264     lastLoadPositionNumber = positionNumber;
11265     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11266     if (first.pr == NoProc) {
11267       StartChessProgram(&first);
11268       InitChessProgram(&first, FALSE);
11269     }
11270     pn = positionNumber;
11271     if (positionNumber < 0) {
11272         /* Negative position number means to seek to that byte offset */
11273         if (fseek(f, -positionNumber, 0) == -1) {
11274             DisplayError(_("Can't seek on position file"), 0);
11275             return FALSE;
11276         };
11277         pn = 1;
11278     } else {
11279         if (fseek(f, 0, 0) == -1) {
11280             if (f == lastLoadPositionFP ?
11281                 positionNumber == lastLoadPositionNumber + 1 :
11282                 positionNumber == 1) {
11283                 pn = 1;
11284             } else {
11285                 DisplayError(_("Can't seek on position file"), 0);
11286                 return FALSE;
11287             }
11288         }
11289     }
11290     /* See if this file is FEN or old-style xboard */
11291     if (fgets(line, MSG_SIZ, f) == NULL) {
11292         DisplayError(_("Position not found in file"), 0);
11293         return FALSE;
11294     }
11295     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11296     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11297
11298     if (pn >= 2) {
11299         if (fenMode || line[0] == '#') pn--;
11300         while (pn > 0) {
11301             /* skip positions before number pn */
11302             if (fgets(line, MSG_SIZ, f) == NULL) {
11303                 Reset(TRUE, TRUE);
11304                 DisplayError(_("Position not found in file"), 0);
11305                 return FALSE;
11306             }
11307             if (fenMode || line[0] == '#') pn--;
11308         }
11309     }
11310
11311     if (fenMode) {
11312         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11313             DisplayError(_("Bad FEN position in file"), 0);
11314             return FALSE;
11315         }
11316     } else {
11317         (void) fgets(line, MSG_SIZ, f);
11318         (void) fgets(line, MSG_SIZ, f);
11319
11320         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11321             (void) fgets(line, MSG_SIZ, f);
11322             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11323                 if (*p == ' ')
11324                   continue;
11325                 initial_position[i][j++] = CharToPiece(*p);
11326             }
11327         }
11328
11329         blackPlaysFirst = FALSE;
11330         if (!feof(f)) {
11331             (void) fgets(line, MSG_SIZ, f);
11332             if (strncmp(line, "black", strlen("black"))==0)
11333               blackPlaysFirst = TRUE;
11334         }
11335     }
11336     startedFromSetupPosition = TRUE;
11337
11338     SendToProgram("force\n", &first);
11339     CopyBoard(boards[0], initial_position);
11340     if (blackPlaysFirst) {
11341         currentMove = forwardMostMove = backwardMostMove = 1;
11342         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11343         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11344         CopyBoard(boards[1], initial_position);
11345         DisplayMessage("", _("Black to play"));
11346     } else {
11347         currentMove = forwardMostMove = backwardMostMove = 0;
11348         DisplayMessage("", _("White to play"));
11349     }
11350     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11351     SendBoard(&first, forwardMostMove);
11352     if (appData.debugMode) {
11353 int i, j;
11354   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11355   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11356         fprintf(debugFP, "Load Position\n");
11357     }
11358
11359     if (positionNumber > 1) {
11360       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11361         DisplayTitle(line);
11362     } else {
11363         DisplayTitle(title);
11364     }
11365     gameMode = EditGame;
11366     ModeHighlight();
11367     ResetClocks();
11368     timeRemaining[0][1] = whiteTimeRemaining;
11369     timeRemaining[1][1] = blackTimeRemaining;
11370     DrawPosition(FALSE, boards[currentMove]);
11371
11372     return TRUE;
11373 }
11374
11375
11376 void
11377 CopyPlayerNameIntoFileName(dest, src)
11378      char **dest, *src;
11379 {
11380     while (*src != NULLCHAR && *src != ',') {
11381         if (*src == ' ') {
11382             *(*dest)++ = '_';
11383             src++;
11384         } else {
11385             *(*dest)++ = *src++;
11386         }
11387     }
11388 }
11389
11390 char *DefaultFileName(ext)
11391      char *ext;
11392 {
11393     static char def[MSG_SIZ];
11394     char *p;
11395
11396     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11397         p = def;
11398         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11399         *p++ = '-';
11400         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11401         *p++ = '.';
11402         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11403     } else {
11404         def[0] = NULLCHAR;
11405     }
11406     return def;
11407 }
11408
11409 /* Save the current game to the given file */
11410 int
11411 SaveGameToFile(filename, append)
11412      char *filename;
11413      int append;
11414 {
11415     FILE *f;
11416     char buf[MSG_SIZ];
11417     int result;
11418
11419     if (strcmp(filename, "-") == 0) {
11420         return SaveGame(stdout, 0, NULL);
11421     } else {
11422         f = fopen(filename, append ? "a" : "w");
11423         if (f == NULL) {
11424             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11425             DisplayError(buf, errno);
11426             return FALSE;
11427         } else {
11428             safeStrCpy(buf, lastMsg, MSG_SIZ);
11429             DisplayMessage(_("Waiting for access to save file"), "");
11430             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11431             DisplayMessage(_("Saving game"), "");
11432             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11433             result = SaveGame(f, 0, NULL);
11434             DisplayMessage(buf, "");
11435             return result;
11436         }
11437     }
11438 }
11439
11440 char *
11441 SavePart(str)
11442      char *str;
11443 {
11444     static char buf[MSG_SIZ];
11445     char *p;
11446
11447     p = strchr(str, ' ');
11448     if (p == NULL) return str;
11449     strncpy(buf, str, p - str);
11450     buf[p - str] = NULLCHAR;
11451     return buf;
11452 }
11453
11454 #define PGN_MAX_LINE 75
11455
11456 #define PGN_SIDE_WHITE  0
11457 #define PGN_SIDE_BLACK  1
11458
11459 /* [AS] */
11460 static int FindFirstMoveOutOfBook( int side )
11461 {
11462     int result = -1;
11463
11464     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11465         int index = backwardMostMove;
11466         int has_book_hit = 0;
11467
11468         if( (index % 2) != side ) {
11469             index++;
11470         }
11471
11472         while( index < forwardMostMove ) {
11473             /* Check to see if engine is in book */
11474             int depth = pvInfoList[index].depth;
11475             int score = pvInfoList[index].score;
11476             int in_book = 0;
11477
11478             if( depth <= 2 ) {
11479                 in_book = 1;
11480             }
11481             else if( score == 0 && depth == 63 ) {
11482                 in_book = 1; /* Zappa */
11483             }
11484             else if( score == 2 && depth == 99 ) {
11485                 in_book = 1; /* Abrok */
11486             }
11487
11488             has_book_hit += in_book;
11489
11490             if( ! in_book ) {
11491                 result = index;
11492
11493                 break;
11494             }
11495
11496             index += 2;
11497         }
11498     }
11499
11500     return result;
11501 }
11502
11503 /* [AS] */
11504 void GetOutOfBookInfo( char * buf )
11505 {
11506     int oob[2];
11507     int i;
11508     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11509
11510     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11511     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11512
11513     *buf = '\0';
11514
11515     if( oob[0] >= 0 || oob[1] >= 0 ) {
11516         for( i=0; i<2; i++ ) {
11517             int idx = oob[i];
11518
11519             if( idx >= 0 ) {
11520                 if( i > 0 && oob[0] >= 0 ) {
11521                     strcat( buf, "   " );
11522                 }
11523
11524                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11525                 sprintf( buf+strlen(buf), "%s%.2f",
11526                     pvInfoList[idx].score >= 0 ? "+" : "",
11527                     pvInfoList[idx].score / 100.0 );
11528             }
11529         }
11530     }
11531 }
11532
11533 /* Save game in PGN style and close the file */
11534 int
11535 SaveGamePGN(f)
11536      FILE *f;
11537 {
11538     int i, offset, linelen, newblock;
11539     time_t tm;
11540 //    char *movetext;
11541     char numtext[32];
11542     int movelen, numlen, blank;
11543     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11544
11545     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11546
11547     tm = time((time_t *) NULL);
11548
11549     PrintPGNTags(f, &gameInfo);
11550
11551     if (backwardMostMove > 0 || startedFromSetupPosition) {
11552         char *fen = PositionToFEN(backwardMostMove, NULL);
11553         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11554         fprintf(f, "\n{--------------\n");
11555         PrintPosition(f, backwardMostMove);
11556         fprintf(f, "--------------}\n");
11557         free(fen);
11558     }
11559     else {
11560         /* [AS] Out of book annotation */
11561         if( appData.saveOutOfBookInfo ) {
11562             char buf[64];
11563
11564             GetOutOfBookInfo( buf );
11565
11566             if( buf[0] != '\0' ) {
11567                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11568             }
11569         }
11570
11571         fprintf(f, "\n");
11572     }
11573
11574     i = backwardMostMove;
11575     linelen = 0;
11576     newblock = TRUE;
11577
11578     while (i < forwardMostMove) {
11579         /* Print comments preceding this move */
11580         if (commentList[i] != NULL) {
11581             if (linelen > 0) fprintf(f, "\n");
11582             fprintf(f, "%s", commentList[i]);
11583             linelen = 0;
11584             newblock = TRUE;
11585         }
11586
11587         /* Format move number */
11588         if ((i % 2) == 0)
11589           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11590         else
11591           if (newblock)
11592             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11593           else
11594             numtext[0] = NULLCHAR;
11595
11596         numlen = strlen(numtext);
11597         newblock = FALSE;
11598
11599         /* Print move number */
11600         blank = linelen > 0 && numlen > 0;
11601         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11602             fprintf(f, "\n");
11603             linelen = 0;
11604             blank = 0;
11605         }
11606         if (blank) {
11607             fprintf(f, " ");
11608             linelen++;
11609         }
11610         fprintf(f, "%s", numtext);
11611         linelen += numlen;
11612
11613         /* Get move */
11614         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11615         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11616
11617         /* Print move */
11618         blank = linelen > 0 && movelen > 0;
11619         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11620             fprintf(f, "\n");
11621             linelen = 0;
11622             blank = 0;
11623         }
11624         if (blank) {
11625             fprintf(f, " ");
11626             linelen++;
11627         }
11628         fprintf(f, "%s", move_buffer);
11629         linelen += movelen;
11630
11631         /* [AS] Add PV info if present */
11632         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11633             /* [HGM] add time */
11634             char buf[MSG_SIZ]; int seconds;
11635
11636             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11637
11638             if( seconds <= 0)
11639               buf[0] = 0;
11640             else
11641               if( seconds < 30 )
11642                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11643               else
11644                 {
11645                   seconds = (seconds + 4)/10; // round to full seconds
11646                   if( seconds < 60 )
11647                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11648                   else
11649                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11650                 }
11651
11652             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11653                       pvInfoList[i].score >= 0 ? "+" : "",
11654                       pvInfoList[i].score / 100.0,
11655                       pvInfoList[i].depth,
11656                       buf );
11657
11658             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11659
11660             /* Print score/depth */
11661             blank = linelen > 0 && movelen > 0;
11662             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11663                 fprintf(f, "\n");
11664                 linelen = 0;
11665                 blank = 0;
11666             }
11667             if (blank) {
11668                 fprintf(f, " ");
11669                 linelen++;
11670             }
11671             fprintf(f, "%s", move_buffer);
11672             linelen += movelen;
11673         }
11674
11675         i++;
11676     }
11677
11678     /* Start a new line */
11679     if (linelen > 0) fprintf(f, "\n");
11680
11681     /* Print comments after last move */
11682     if (commentList[i] != NULL) {
11683         fprintf(f, "%s\n", commentList[i]);
11684     }
11685
11686     /* Print result */
11687     if (gameInfo.resultDetails != NULL &&
11688         gameInfo.resultDetails[0] != NULLCHAR) {
11689         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11690                 PGNResult(gameInfo.result));
11691     } else {
11692         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11693     }
11694
11695     fclose(f);
11696     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11697     return TRUE;
11698 }
11699
11700 /* Save game in old style and close the file */
11701 int
11702 SaveGameOldStyle(f)
11703      FILE *f;
11704 {
11705     int i, offset;
11706     time_t tm;
11707
11708     tm = time((time_t *) NULL);
11709
11710     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11711     PrintOpponents(f);
11712
11713     if (backwardMostMove > 0 || startedFromSetupPosition) {
11714         fprintf(f, "\n[--------------\n");
11715         PrintPosition(f, backwardMostMove);
11716         fprintf(f, "--------------]\n");
11717     } else {
11718         fprintf(f, "\n");
11719     }
11720
11721     i = backwardMostMove;
11722     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11723
11724     while (i < forwardMostMove) {
11725         if (commentList[i] != NULL) {
11726             fprintf(f, "[%s]\n", commentList[i]);
11727         }
11728
11729         if ((i % 2) == 1) {
11730             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11731             i++;
11732         } else {
11733             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11734             i++;
11735             if (commentList[i] != NULL) {
11736                 fprintf(f, "\n");
11737                 continue;
11738             }
11739             if (i >= forwardMostMove) {
11740                 fprintf(f, "\n");
11741                 break;
11742             }
11743             fprintf(f, "%s\n", parseList[i]);
11744             i++;
11745         }
11746     }
11747
11748     if (commentList[i] != NULL) {
11749         fprintf(f, "[%s]\n", commentList[i]);
11750     }
11751
11752     /* This isn't really the old style, but it's close enough */
11753     if (gameInfo.resultDetails != NULL &&
11754         gameInfo.resultDetails[0] != NULLCHAR) {
11755         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11756                 gameInfo.resultDetails);
11757     } else {
11758         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11759     }
11760
11761     fclose(f);
11762     return TRUE;
11763 }
11764
11765 /* Save the current game to open file f and close the file */
11766 int
11767 SaveGame(f, dummy, dummy2)
11768      FILE *f;
11769      int dummy;
11770      char *dummy2;
11771 {
11772     if (gameMode == EditPosition) EditPositionDone(TRUE);
11773     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11774     if (appData.oldSaveStyle)
11775       return SaveGameOldStyle(f);
11776     else
11777       return SaveGamePGN(f);
11778 }
11779
11780 /* Save the current position to the given file */
11781 int
11782 SavePositionToFile(filename)
11783      char *filename;
11784 {
11785     FILE *f;
11786     char buf[MSG_SIZ];
11787
11788     if (strcmp(filename, "-") == 0) {
11789         return SavePosition(stdout, 0, NULL);
11790     } else {
11791         f = fopen(filename, "a");
11792         if (f == NULL) {
11793             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11794             DisplayError(buf, errno);
11795             return FALSE;
11796         } else {
11797             safeStrCpy(buf, lastMsg, MSG_SIZ);
11798             DisplayMessage(_("Waiting for access to save file"), "");
11799             flock(fileno(f), LOCK_EX); // [HGM] lock
11800             DisplayMessage(_("Saving position"), "");
11801             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11802             SavePosition(f, 0, NULL);
11803             DisplayMessage(buf, "");
11804             return TRUE;
11805         }
11806     }
11807 }
11808
11809 /* Save the current position to the given open file and close the file */
11810 int
11811 SavePosition(f, dummy, dummy2)
11812      FILE *f;
11813      int dummy;
11814      char *dummy2;
11815 {
11816     time_t tm;
11817     char *fen;
11818
11819     if (gameMode == EditPosition) EditPositionDone(TRUE);
11820     if (appData.oldSaveStyle) {
11821         tm = time((time_t *) NULL);
11822
11823         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11824         PrintOpponents(f);
11825         fprintf(f, "[--------------\n");
11826         PrintPosition(f, currentMove);
11827         fprintf(f, "--------------]\n");
11828     } else {
11829         fen = PositionToFEN(currentMove, NULL);
11830         fprintf(f, "%s\n", fen);
11831         free(fen);
11832     }
11833     fclose(f);
11834     return TRUE;
11835 }
11836
11837 void
11838 ReloadCmailMsgEvent(unregister)
11839      int unregister;
11840 {
11841 #if !WIN32
11842     static char *inFilename = NULL;
11843     static char *outFilename;
11844     int i;
11845     struct stat inbuf, outbuf;
11846     int status;
11847
11848     /* Any registered moves are unregistered if unregister is set, */
11849     /* i.e. invoked by the signal handler */
11850     if (unregister) {
11851         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11852             cmailMoveRegistered[i] = FALSE;
11853             if (cmailCommentList[i] != NULL) {
11854                 free(cmailCommentList[i]);
11855                 cmailCommentList[i] = NULL;
11856             }
11857         }
11858         nCmailMovesRegistered = 0;
11859     }
11860
11861     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11862         cmailResult[i] = CMAIL_NOT_RESULT;
11863     }
11864     nCmailResults = 0;
11865
11866     if (inFilename == NULL) {
11867         /* Because the filenames are static they only get malloced once  */
11868         /* and they never get freed                                      */
11869         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11870         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11871
11872         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11873         sprintf(outFilename, "%s.out", appData.cmailGameName);
11874     }
11875
11876     status = stat(outFilename, &outbuf);
11877     if (status < 0) {
11878         cmailMailedMove = FALSE;
11879     } else {
11880         status = stat(inFilename, &inbuf);
11881         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11882     }
11883
11884     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11885        counts the games, notes how each one terminated, etc.
11886
11887        It would be nice to remove this kludge and instead gather all
11888        the information while building the game list.  (And to keep it
11889        in the game list nodes instead of having a bunch of fixed-size
11890        parallel arrays.)  Note this will require getting each game's
11891        termination from the PGN tags, as the game list builder does
11892        not process the game moves.  --mann
11893        */
11894     cmailMsgLoaded = TRUE;
11895     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11896
11897     /* Load first game in the file or popup game menu */
11898     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11899
11900 #endif /* !WIN32 */
11901     return;
11902 }
11903
11904 int
11905 RegisterMove()
11906 {
11907     FILE *f;
11908     char string[MSG_SIZ];
11909
11910     if (   cmailMailedMove
11911         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11912         return TRUE;            /* Allow free viewing  */
11913     }
11914
11915     /* Unregister move to ensure that we don't leave RegisterMove        */
11916     /* with the move registered when the conditions for registering no   */
11917     /* longer hold                                                       */
11918     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11919         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11920         nCmailMovesRegistered --;
11921
11922         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11923           {
11924               free(cmailCommentList[lastLoadGameNumber - 1]);
11925               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11926           }
11927     }
11928
11929     if (cmailOldMove == -1) {
11930         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11931         return FALSE;
11932     }
11933
11934     if (currentMove > cmailOldMove + 1) {
11935         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11936         return FALSE;
11937     }
11938
11939     if (currentMove < cmailOldMove) {
11940         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11941         return FALSE;
11942     }
11943
11944     if (forwardMostMove > currentMove) {
11945         /* Silently truncate extra moves */
11946         TruncateGame();
11947     }
11948
11949     if (   (currentMove == cmailOldMove + 1)
11950         || (   (currentMove == cmailOldMove)
11951             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11952                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11953         if (gameInfo.result != GameUnfinished) {
11954             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11955         }
11956
11957         if (commentList[currentMove] != NULL) {
11958             cmailCommentList[lastLoadGameNumber - 1]
11959               = StrSave(commentList[currentMove]);
11960         }
11961         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11962
11963         if (appData.debugMode)
11964           fprintf(debugFP, "Saving %s for game %d\n",
11965                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11966
11967         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11968
11969         f = fopen(string, "w");
11970         if (appData.oldSaveStyle) {
11971             SaveGameOldStyle(f); /* also closes the file */
11972
11973             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11974             f = fopen(string, "w");
11975             SavePosition(f, 0, NULL); /* also closes the file */
11976         } else {
11977             fprintf(f, "{--------------\n");
11978             PrintPosition(f, currentMove);
11979             fprintf(f, "--------------}\n\n");
11980
11981             SaveGame(f, 0, NULL); /* also closes the file*/
11982         }
11983
11984         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11985         nCmailMovesRegistered ++;
11986     } else if (nCmailGames == 1) {
11987         DisplayError(_("You have not made a move yet"), 0);
11988         return FALSE;
11989     }
11990
11991     return TRUE;
11992 }
11993
11994 void
11995 MailMoveEvent()
11996 {
11997 #if !WIN32
11998     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11999     FILE *commandOutput;
12000     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12001     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12002     int nBuffers;
12003     int i;
12004     int archived;
12005     char *arcDir;
12006
12007     if (! cmailMsgLoaded) {
12008         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12009         return;
12010     }
12011
12012     if (nCmailGames == nCmailResults) {
12013         DisplayError(_("No unfinished games"), 0);
12014         return;
12015     }
12016
12017 #if CMAIL_PROHIBIT_REMAIL
12018     if (cmailMailedMove) {
12019       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);
12020         DisplayError(msg, 0);
12021         return;
12022     }
12023 #endif
12024
12025     if (! (cmailMailedMove || RegisterMove())) return;
12026
12027     if (   cmailMailedMove
12028         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12029       snprintf(string, MSG_SIZ, partCommandString,
12030                appData.debugMode ? " -v" : "", appData.cmailGameName);
12031         commandOutput = popen(string, "r");
12032
12033         if (commandOutput == NULL) {
12034             DisplayError(_("Failed to invoke cmail"), 0);
12035         } else {
12036             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12037                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12038             }
12039             if (nBuffers > 1) {
12040                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12041                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12042                 nBytes = MSG_SIZ - 1;
12043             } else {
12044                 (void) memcpy(msg, buffer, nBytes);
12045             }
12046             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12047
12048             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12049                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12050
12051                 archived = TRUE;
12052                 for (i = 0; i < nCmailGames; i ++) {
12053                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12054                         archived = FALSE;
12055                     }
12056                 }
12057                 if (   archived
12058                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12059                         != NULL)) {
12060                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12061                            arcDir,
12062                            appData.cmailGameName,
12063                            gameInfo.date);
12064                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12065                     cmailMsgLoaded = FALSE;
12066                 }
12067             }
12068
12069             DisplayInformation(msg);
12070             pclose(commandOutput);
12071         }
12072     } else {
12073         if ((*cmailMsg) != '\0') {
12074             DisplayInformation(cmailMsg);
12075         }
12076     }
12077
12078     return;
12079 #endif /* !WIN32 */
12080 }
12081
12082 char *
12083 CmailMsg()
12084 {
12085 #if WIN32
12086     return NULL;
12087 #else
12088     int  prependComma = 0;
12089     char number[5];
12090     char string[MSG_SIZ];       /* Space for game-list */
12091     int  i;
12092
12093     if (!cmailMsgLoaded) return "";
12094
12095     if (cmailMailedMove) {
12096       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12097     } else {
12098         /* Create a list of games left */
12099       snprintf(string, MSG_SIZ, "[");
12100         for (i = 0; i < nCmailGames; i ++) {
12101             if (! (   cmailMoveRegistered[i]
12102                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12103                 if (prependComma) {
12104                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12105                 } else {
12106                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12107                     prependComma = 1;
12108                 }
12109
12110                 strcat(string, number);
12111             }
12112         }
12113         strcat(string, "]");
12114
12115         if (nCmailMovesRegistered + nCmailResults == 0) {
12116             switch (nCmailGames) {
12117               case 1:
12118                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12119                 break;
12120
12121               case 2:
12122                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12123                 break;
12124
12125               default:
12126                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12127                          nCmailGames);
12128                 break;
12129             }
12130         } else {
12131             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12132               case 1:
12133                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12134                          string);
12135                 break;
12136
12137               case 0:
12138                 if (nCmailResults == nCmailGames) {
12139                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12140                 } else {
12141                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12142                 }
12143                 break;
12144
12145               default:
12146                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12147                          string);
12148             }
12149         }
12150     }
12151     return cmailMsg;
12152 #endif /* WIN32 */
12153 }
12154
12155 void
12156 ResetGameEvent()
12157 {
12158     if (gameMode == Training)
12159       SetTrainingModeOff();
12160
12161     Reset(TRUE, TRUE);
12162     cmailMsgLoaded = FALSE;
12163     if (appData.icsActive) {
12164       SendToICS(ics_prefix);
12165       SendToICS("refresh\n");
12166     }
12167 }
12168
12169 void
12170 ExitEvent(status)
12171      int status;
12172 {
12173     exiting++;
12174     if (exiting > 2) {
12175       /* Give up on clean exit */
12176       exit(status);
12177     }
12178     if (exiting > 1) {
12179       /* Keep trying for clean exit */
12180       return;
12181     }
12182
12183     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12184
12185     if (telnetISR != NULL) {
12186       RemoveInputSource(telnetISR);
12187     }
12188     if (icsPR != NoProc) {
12189       DestroyChildProcess(icsPR, TRUE);
12190     }
12191
12192     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12193     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12194
12195     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12196     /* make sure this other one finishes before killing it!                  */
12197     if(endingGame) { int count = 0;
12198         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12199         while(endingGame && count++ < 10) DoSleep(1);
12200         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12201     }
12202
12203     /* Kill off chess programs */
12204     if (first.pr != NoProc) {
12205         ExitAnalyzeMode();
12206
12207         DoSleep( appData.delayBeforeQuit );
12208         SendToProgram("quit\n", &first);
12209         DoSleep( appData.delayAfterQuit );
12210         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12211     }
12212     if (second.pr != NoProc) {
12213         DoSleep( appData.delayBeforeQuit );
12214         SendToProgram("quit\n", &second);
12215         DoSleep( appData.delayAfterQuit );
12216         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12217     }
12218     if (first.isr != NULL) {
12219         RemoveInputSource(first.isr);
12220     }
12221     if (second.isr != NULL) {
12222         RemoveInputSource(second.isr);
12223     }
12224
12225     ShutDownFrontEnd();
12226     exit(status);
12227 }
12228
12229 void
12230 PauseEvent()
12231 {
12232     if (appData.debugMode)
12233         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12234     if (pausing) {
12235         pausing = FALSE;
12236         ModeHighlight();
12237         if (gameMode == MachinePlaysWhite ||
12238             gameMode == MachinePlaysBlack) {
12239             StartClocks();
12240         } else {
12241             DisplayBothClocks();
12242         }
12243         if (gameMode == PlayFromGameFile) {
12244             if (appData.timeDelay >= 0)
12245                 AutoPlayGameLoop();
12246         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12247             Reset(FALSE, TRUE);
12248             SendToICS(ics_prefix);
12249             SendToICS("refresh\n");
12250         } else if (currentMove < forwardMostMove) {
12251             ForwardInner(forwardMostMove);
12252         }
12253         pauseExamInvalid = FALSE;
12254     } else {
12255         switch (gameMode) {
12256           default:
12257             return;
12258           case IcsExamining:
12259             pauseExamForwardMostMove = forwardMostMove;
12260             pauseExamInvalid = FALSE;
12261             /* fall through */
12262           case IcsObserving:
12263           case IcsPlayingWhite:
12264           case IcsPlayingBlack:
12265             pausing = TRUE;
12266             ModeHighlight();
12267             return;
12268           case PlayFromGameFile:
12269             (void) StopLoadGameTimer();
12270             pausing = TRUE;
12271             ModeHighlight();
12272             break;
12273           case BeginningOfGame:
12274             if (appData.icsActive) return;
12275             /* else fall through */
12276           case MachinePlaysWhite:
12277           case MachinePlaysBlack:
12278           case TwoMachinesPlay:
12279             if (forwardMostMove == 0)
12280               return;           /* don't pause if no one has moved */
12281             if ((gameMode == MachinePlaysWhite &&
12282                  !WhiteOnMove(forwardMostMove)) ||
12283                 (gameMode == MachinePlaysBlack &&
12284                  WhiteOnMove(forwardMostMove))) {
12285                 StopClocks();
12286             }
12287             pausing = TRUE;
12288             ModeHighlight();
12289             break;
12290         }
12291     }
12292 }
12293
12294 void
12295 EditCommentEvent()
12296 {
12297     char title[MSG_SIZ];
12298
12299     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12300       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12301     } else {
12302       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12303                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12304                parseList[currentMove - 1]);
12305     }
12306
12307     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12308 }
12309
12310
12311 void
12312 EditTagsEvent()
12313 {
12314     char *tags = PGNTags(&gameInfo);
12315     bookUp = FALSE;
12316     EditTagsPopUp(tags, NULL);
12317     free(tags);
12318 }
12319
12320 void
12321 AnalyzeModeEvent()
12322 {
12323     if (appData.noChessProgram || gameMode == AnalyzeMode)
12324       return;
12325
12326     if (gameMode != AnalyzeFile) {
12327         if (!appData.icsEngineAnalyze) {
12328                EditGameEvent();
12329                if (gameMode != EditGame) return;
12330         }
12331         ResurrectChessProgram();
12332         SendToProgram("analyze\n", &first);
12333         first.analyzing = TRUE;
12334         /*first.maybeThinking = TRUE;*/
12335         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12336         EngineOutputPopUp();
12337     }
12338     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12339     pausing = FALSE;
12340     ModeHighlight();
12341     SetGameInfo();
12342
12343     StartAnalysisClock();
12344     GetTimeMark(&lastNodeCountTime);
12345     lastNodeCount = 0;
12346 }
12347
12348 void
12349 AnalyzeFileEvent()
12350 {
12351     if (appData.noChessProgram || gameMode == AnalyzeFile)
12352       return;
12353
12354     if (gameMode != AnalyzeMode) {
12355         EditGameEvent();
12356         if (gameMode != EditGame) return;
12357         ResurrectChessProgram();
12358         SendToProgram("analyze\n", &first);
12359         first.analyzing = TRUE;
12360         /*first.maybeThinking = TRUE;*/
12361         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12362         EngineOutputPopUp();
12363     }
12364     gameMode = AnalyzeFile;
12365     pausing = FALSE;
12366     ModeHighlight();
12367     SetGameInfo();
12368
12369     StartAnalysisClock();
12370     GetTimeMark(&lastNodeCountTime);
12371     lastNodeCount = 0;
12372 }
12373
12374 void
12375 MachineWhiteEvent()
12376 {
12377     char buf[MSG_SIZ];
12378     char *bookHit = NULL;
12379
12380     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12381       return;
12382
12383
12384     if (gameMode == PlayFromGameFile ||
12385         gameMode == TwoMachinesPlay  ||
12386         gameMode == Training         ||
12387         gameMode == AnalyzeMode      ||
12388         gameMode == EndOfGame)
12389         EditGameEvent();
12390
12391     if (gameMode == EditPosition)
12392         EditPositionDone(TRUE);
12393
12394     if (!WhiteOnMove(currentMove)) {
12395         DisplayError(_("It is not White's turn"), 0);
12396         return;
12397     }
12398
12399     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12400       ExitAnalyzeMode();
12401
12402     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12403         gameMode == AnalyzeFile)
12404         TruncateGame();
12405
12406     ResurrectChessProgram();    /* in case it isn't running */
12407     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12408         gameMode = MachinePlaysWhite;
12409         ResetClocks();
12410     } else
12411     gameMode = MachinePlaysWhite;
12412     pausing = FALSE;
12413     ModeHighlight();
12414     SetGameInfo();
12415     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12416     DisplayTitle(buf);
12417     if (first.sendName) {
12418       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12419       SendToProgram(buf, &first);
12420     }
12421     if (first.sendTime) {
12422       if (first.useColors) {
12423         SendToProgram("black\n", &first); /*gnu kludge*/
12424       }
12425       SendTimeRemaining(&first, TRUE);
12426     }
12427     if (first.useColors) {
12428       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12429     }
12430     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12431     SetMachineThinkingEnables();
12432     first.maybeThinking = TRUE;
12433     StartClocks();
12434     firstMove = FALSE;
12435
12436     if (appData.autoFlipView && !flipView) {
12437       flipView = !flipView;
12438       DrawPosition(FALSE, NULL);
12439       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12440     }
12441
12442     if(bookHit) { // [HGM] book: simulate book reply
12443         static char bookMove[MSG_SIZ]; // a bit generous?
12444
12445         programStats.nodes = programStats.depth = programStats.time =
12446         programStats.score = programStats.got_only_move = 0;
12447         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12448
12449         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12450         strcat(bookMove, bookHit);
12451         HandleMachineMove(bookMove, &first);
12452     }
12453 }
12454
12455 void
12456 MachineBlackEvent()
12457 {
12458   char buf[MSG_SIZ];
12459   char *bookHit = NULL;
12460
12461     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12462         return;
12463
12464
12465     if (gameMode == PlayFromGameFile ||
12466         gameMode == TwoMachinesPlay  ||
12467         gameMode == Training         ||
12468         gameMode == AnalyzeMode      ||
12469         gameMode == EndOfGame)
12470         EditGameEvent();
12471
12472     if (gameMode == EditPosition)
12473         EditPositionDone(TRUE);
12474
12475     if (WhiteOnMove(currentMove)) {
12476         DisplayError(_("It is not Black's turn"), 0);
12477         return;
12478     }
12479
12480     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12481       ExitAnalyzeMode();
12482
12483     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12484         gameMode == AnalyzeFile)
12485         TruncateGame();
12486
12487     ResurrectChessProgram();    /* in case it isn't running */
12488     gameMode = MachinePlaysBlack;
12489     pausing = FALSE;
12490     ModeHighlight();
12491     SetGameInfo();
12492     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12493     DisplayTitle(buf);
12494     if (first.sendName) {
12495       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12496       SendToProgram(buf, &first);
12497     }
12498     if (first.sendTime) {
12499       if (first.useColors) {
12500         SendToProgram("white\n", &first); /*gnu kludge*/
12501       }
12502       SendTimeRemaining(&first, FALSE);
12503     }
12504     if (first.useColors) {
12505       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12506     }
12507     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12508     SetMachineThinkingEnables();
12509     first.maybeThinking = TRUE;
12510     StartClocks();
12511
12512     if (appData.autoFlipView && flipView) {
12513       flipView = !flipView;
12514       DrawPosition(FALSE, NULL);
12515       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12516     }
12517     if(bookHit) { // [HGM] book: simulate book reply
12518         static char bookMove[MSG_SIZ]; // a bit generous?
12519
12520         programStats.nodes = programStats.depth = programStats.time =
12521         programStats.score = programStats.got_only_move = 0;
12522         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12523
12524         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12525         strcat(bookMove, bookHit);
12526         HandleMachineMove(bookMove, &first);
12527     }
12528 }
12529
12530
12531 void
12532 DisplayTwoMachinesTitle()
12533 {
12534     char buf[MSG_SIZ];
12535     if (appData.matchGames > 0) {
12536         if (first.twoMachinesColor[0] == 'w') {
12537           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12538                    gameInfo.white, gameInfo.black,
12539                    first.matchWins, second.matchWins,
12540                    matchGame - 1 - (first.matchWins + second.matchWins));
12541         } else {
12542           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12543                    gameInfo.white, gameInfo.black,
12544                    second.matchWins, first.matchWins,
12545                    matchGame - 1 - (first.matchWins + second.matchWins));
12546         }
12547     } else {
12548       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12549     }
12550     DisplayTitle(buf);
12551 }
12552
12553 void
12554 SettingsMenuIfReady()
12555 {
12556   if (second.lastPing != second.lastPong) {
12557     DisplayMessage("", _("Waiting for second chess program"));
12558     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12559     return;
12560   }
12561   ThawUI();
12562   DisplayMessage("", "");
12563   SettingsPopUp(&second);
12564 }
12565
12566 int
12567 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12568 {
12569     char buf[MSG_SIZ];
12570     if (cps->pr == NULL) {
12571         StartChessProgram(cps);
12572         if (cps->protocolVersion == 1) {
12573           retry();
12574         } else {
12575           /* kludge: allow timeout for initial "feature" command */
12576           FreezeUI();
12577           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12578           DisplayMessage("", buf);
12579           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12580         }
12581         return 1;
12582     }
12583     return 0;
12584 }
12585
12586 void
12587 TwoMachinesEvent P((void))
12588 {
12589     int i;
12590     char buf[MSG_SIZ];
12591     ChessProgramState *onmove;
12592     char *bookHit = NULL;
12593     static int stalling = 0;
12594     TimeMark now;
12595     long wait;
12596
12597     if (appData.noChessProgram) return;
12598
12599     switch (gameMode) {
12600       case TwoMachinesPlay:
12601         return;
12602       case MachinePlaysWhite:
12603       case MachinePlaysBlack:
12604         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12605             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12606             return;
12607         }
12608         /* fall through */
12609       case BeginningOfGame:
12610       case PlayFromGameFile:
12611       case EndOfGame:
12612         EditGameEvent();
12613         if (gameMode != EditGame) return;
12614         break;
12615       case EditPosition:
12616         EditPositionDone(TRUE);
12617         break;
12618       case AnalyzeMode:
12619       case AnalyzeFile:
12620         ExitAnalyzeMode();
12621         break;
12622       case EditGame:
12623       default:
12624         break;
12625     }
12626
12627 //    forwardMostMove = currentMove;
12628     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12629
12630     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12631
12632     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12633     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12634       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12635       return;
12636     }
12637     if(!stalling) {
12638       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12639       SendToProgram("force\n", &second);
12640       stalling = 1;
12641       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12642       return;
12643     }
12644     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12645     if(appData.matchPause>10000 || appData.matchPause<10)
12646                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12647     wait = SubtractTimeMarks(&now, &pauseStart);
12648     if(wait < appData.matchPause) {
12649         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12650         return;
12651     }
12652     stalling = 0;
12653     DisplayMessage("", "");
12654     if (startedFromSetupPosition) {
12655         SendBoard(&second, backwardMostMove);
12656     if (appData.debugMode) {
12657         fprintf(debugFP, "Two Machines\n");
12658     }
12659     }
12660     for (i = backwardMostMove; i < forwardMostMove; i++) {
12661         SendMoveToProgram(i, &second);
12662     }
12663
12664     gameMode = TwoMachinesPlay;
12665     pausing = FALSE;
12666     ModeHighlight();
12667     SetGameInfo();
12668     DisplayTwoMachinesTitle();
12669     firstMove = TRUE;
12670     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12671         onmove = &first;
12672     } else {
12673         onmove = &second;
12674     }
12675     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12676     SendToProgram(first.computerString, &first);
12677     if (first.sendName) {
12678       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12679       SendToProgram(buf, &first);
12680     }
12681     SendToProgram(second.computerString, &second);
12682     if (second.sendName) {
12683       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12684       SendToProgram(buf, &second);
12685     }
12686
12687     ResetClocks();
12688     if (!first.sendTime || !second.sendTime) {
12689         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12690         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12691     }
12692     if (onmove->sendTime) {
12693       if (onmove->useColors) {
12694         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12695       }
12696       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12697     }
12698     if (onmove->useColors) {
12699       SendToProgram(onmove->twoMachinesColor, onmove);
12700     }
12701     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12702 //    SendToProgram("go\n", onmove);
12703     onmove->maybeThinking = TRUE;
12704     SetMachineThinkingEnables();
12705
12706     StartClocks();
12707
12708     if(bookHit) { // [HGM] book: simulate book reply
12709         static char bookMove[MSG_SIZ]; // a bit generous?
12710
12711         programStats.nodes = programStats.depth = programStats.time =
12712         programStats.score = programStats.got_only_move = 0;
12713         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12714
12715         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12716         strcat(bookMove, bookHit);
12717         savedMessage = bookMove; // args for deferred call
12718         savedState = onmove;
12719         ScheduleDelayedEvent(DeferredBookMove, 1);
12720     }
12721 }
12722
12723 void
12724 TrainingEvent()
12725 {
12726     if (gameMode == Training) {
12727       SetTrainingModeOff();
12728       gameMode = PlayFromGameFile;
12729       DisplayMessage("", _("Training mode off"));
12730     } else {
12731       gameMode = Training;
12732       animateTraining = appData.animate;
12733
12734       /* make sure we are not already at the end of the game */
12735       if (currentMove < forwardMostMove) {
12736         SetTrainingModeOn();
12737         DisplayMessage("", _("Training mode on"));
12738       } else {
12739         gameMode = PlayFromGameFile;
12740         DisplayError(_("Already at end of game"), 0);
12741       }
12742     }
12743     ModeHighlight();
12744 }
12745
12746 void
12747 IcsClientEvent()
12748 {
12749     if (!appData.icsActive) return;
12750     switch (gameMode) {
12751       case IcsPlayingWhite:
12752       case IcsPlayingBlack:
12753       case IcsObserving:
12754       case IcsIdle:
12755       case BeginningOfGame:
12756       case IcsExamining:
12757         return;
12758
12759       case EditGame:
12760         break;
12761
12762       case EditPosition:
12763         EditPositionDone(TRUE);
12764         break;
12765
12766       case AnalyzeMode:
12767       case AnalyzeFile:
12768         ExitAnalyzeMode();
12769         break;
12770
12771       default:
12772         EditGameEvent();
12773         break;
12774     }
12775
12776     gameMode = IcsIdle;
12777     ModeHighlight();
12778     return;
12779 }
12780
12781
12782 void
12783 EditGameEvent()
12784 {
12785     int i;
12786
12787     switch (gameMode) {
12788       case Training:
12789         SetTrainingModeOff();
12790         break;
12791       case MachinePlaysWhite:
12792       case MachinePlaysBlack:
12793       case BeginningOfGame:
12794         SendToProgram("force\n", &first);
12795         SetUserThinkingEnables();
12796         break;
12797       case PlayFromGameFile:
12798         (void) StopLoadGameTimer();
12799         if (gameFileFP != NULL) {
12800             gameFileFP = NULL;
12801         }
12802         break;
12803       case EditPosition:
12804         EditPositionDone(TRUE);
12805         break;
12806       case AnalyzeMode:
12807       case AnalyzeFile:
12808         ExitAnalyzeMode();
12809         SendToProgram("force\n", &first);
12810         break;
12811       case TwoMachinesPlay:
12812         GameEnds(EndOfFile, NULL, GE_PLAYER);
12813         ResurrectChessProgram();
12814         SetUserThinkingEnables();
12815         break;
12816       case EndOfGame:
12817         ResurrectChessProgram();
12818         break;
12819       case IcsPlayingBlack:
12820       case IcsPlayingWhite:
12821         DisplayError(_("Warning: You are still playing a game"), 0);
12822         break;
12823       case IcsObserving:
12824         DisplayError(_("Warning: You are still observing a game"), 0);
12825         break;
12826       case IcsExamining:
12827         DisplayError(_("Warning: You are still examining a game"), 0);
12828         break;
12829       case IcsIdle:
12830         break;
12831       case EditGame:
12832       default:
12833         return;
12834     }
12835
12836     pausing = FALSE;
12837     StopClocks();
12838     first.offeredDraw = second.offeredDraw = 0;
12839
12840     if (gameMode == PlayFromGameFile) {
12841         whiteTimeRemaining = timeRemaining[0][currentMove];
12842         blackTimeRemaining = timeRemaining[1][currentMove];
12843         DisplayTitle("");
12844     }
12845
12846     if (gameMode == MachinePlaysWhite ||
12847         gameMode == MachinePlaysBlack ||
12848         gameMode == TwoMachinesPlay ||
12849         gameMode == EndOfGame) {
12850         i = forwardMostMove;
12851         while (i > currentMove) {
12852             SendToProgram("undo\n", &first);
12853             i--;
12854         }
12855         whiteTimeRemaining = timeRemaining[0][currentMove];
12856         blackTimeRemaining = timeRemaining[1][currentMove];
12857         DisplayBothClocks();
12858         if (whiteFlag || blackFlag) {
12859             whiteFlag = blackFlag = 0;
12860         }
12861         DisplayTitle("");
12862     }
12863
12864     gameMode = EditGame;
12865     ModeHighlight();
12866     SetGameInfo();
12867 }
12868
12869
12870 void
12871 EditPositionEvent()
12872 {
12873     if (gameMode == EditPosition) {
12874         EditGameEvent();
12875         return;
12876     }
12877
12878     EditGameEvent();
12879     if (gameMode != EditGame) return;
12880
12881     gameMode = EditPosition;
12882     ModeHighlight();
12883     SetGameInfo();
12884     if (currentMove > 0)
12885       CopyBoard(boards[0], boards[currentMove]);
12886
12887     blackPlaysFirst = !WhiteOnMove(currentMove);
12888     ResetClocks();
12889     currentMove = forwardMostMove = backwardMostMove = 0;
12890     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12891     DisplayMove(-1);
12892 }
12893
12894 void
12895 ExitAnalyzeMode()
12896 {
12897     /* [DM] icsEngineAnalyze - possible call from other functions */
12898     if (appData.icsEngineAnalyze) {
12899         appData.icsEngineAnalyze = FALSE;
12900
12901         DisplayMessage("",_("Close ICS engine analyze..."));
12902     }
12903     if (first.analysisSupport && first.analyzing) {
12904       SendToProgram("exit\n", &first);
12905       first.analyzing = FALSE;
12906     }
12907     thinkOutput[0] = NULLCHAR;
12908 }
12909
12910 void
12911 EditPositionDone(Boolean fakeRights)
12912 {
12913     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12914
12915     startedFromSetupPosition = TRUE;
12916     InitChessProgram(&first, FALSE);
12917     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12918       boards[0][EP_STATUS] = EP_NONE;
12919       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12920     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12921         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12922         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12923       } else boards[0][CASTLING][2] = NoRights;
12924     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12925         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12926         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12927       } else boards[0][CASTLING][5] = NoRights;
12928     }
12929     SendToProgram("force\n", &first);
12930     if (blackPlaysFirst) {
12931         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12932         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12933         currentMove = forwardMostMove = backwardMostMove = 1;
12934         CopyBoard(boards[1], boards[0]);
12935     } else {
12936         currentMove = forwardMostMove = backwardMostMove = 0;
12937     }
12938     SendBoard(&first, forwardMostMove);
12939     if (appData.debugMode) {
12940         fprintf(debugFP, "EditPosDone\n");
12941     }
12942     DisplayTitle("");
12943     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12944     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12945     gameMode = EditGame;
12946     ModeHighlight();
12947     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12948     ClearHighlights(); /* [AS] */
12949 }
12950
12951 /* Pause for `ms' milliseconds */
12952 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12953 void
12954 TimeDelay(ms)
12955      long ms;
12956 {
12957     TimeMark m1, m2;
12958
12959     GetTimeMark(&m1);
12960     do {
12961         GetTimeMark(&m2);
12962     } while (SubtractTimeMarks(&m2, &m1) < ms);
12963 }
12964
12965 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12966 void
12967 SendMultiLineToICS(buf)
12968      char *buf;
12969 {
12970     char temp[MSG_SIZ+1], *p;
12971     int len;
12972
12973     len = strlen(buf);
12974     if (len > MSG_SIZ)
12975       len = MSG_SIZ;
12976
12977     strncpy(temp, buf, len);
12978     temp[len] = 0;
12979
12980     p = temp;
12981     while (*p) {
12982         if (*p == '\n' || *p == '\r')
12983           *p = ' ';
12984         ++p;
12985     }
12986
12987     strcat(temp, "\n");
12988     SendToICS(temp);
12989     SendToPlayer(temp, strlen(temp));
12990 }
12991
12992 void
12993 SetWhiteToPlayEvent()
12994 {
12995     if (gameMode == EditPosition) {
12996         blackPlaysFirst = FALSE;
12997         DisplayBothClocks();    /* works because currentMove is 0 */
12998     } else if (gameMode == IcsExamining) {
12999         SendToICS(ics_prefix);
13000         SendToICS("tomove white\n");
13001     }
13002 }
13003
13004 void
13005 SetBlackToPlayEvent()
13006 {
13007     if (gameMode == EditPosition) {
13008         blackPlaysFirst = TRUE;
13009         currentMove = 1;        /* kludge */
13010         DisplayBothClocks();
13011         currentMove = 0;
13012     } else if (gameMode == IcsExamining) {
13013         SendToICS(ics_prefix);
13014         SendToICS("tomove black\n");
13015     }
13016 }
13017
13018 void
13019 EditPositionMenuEvent(selection, x, y)
13020      ChessSquare selection;
13021      int x, y;
13022 {
13023     char buf[MSG_SIZ];
13024     ChessSquare piece = boards[0][y][x];
13025
13026     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13027
13028     switch (selection) {
13029       case ClearBoard:
13030         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13031             SendToICS(ics_prefix);
13032             SendToICS("bsetup clear\n");
13033         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13034             SendToICS(ics_prefix);
13035             SendToICS("clearboard\n");
13036         } else {
13037             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13038                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13039                 for (y = 0; y < BOARD_HEIGHT; y++) {
13040                     if (gameMode == IcsExamining) {
13041                         if (boards[currentMove][y][x] != EmptySquare) {
13042                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13043                                     AAA + x, ONE + y);
13044                             SendToICS(buf);
13045                         }
13046                     } else {
13047                         boards[0][y][x] = p;
13048                     }
13049                 }
13050             }
13051         }
13052         if (gameMode == EditPosition) {
13053             DrawPosition(FALSE, boards[0]);
13054         }
13055         break;
13056
13057       case WhitePlay:
13058         SetWhiteToPlayEvent();
13059         break;
13060
13061       case BlackPlay:
13062         SetBlackToPlayEvent();
13063         break;
13064
13065       case EmptySquare:
13066         if (gameMode == IcsExamining) {
13067             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13068             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13069             SendToICS(buf);
13070         } else {
13071             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13072                 if(x == BOARD_LEFT-2) {
13073                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13074                     boards[0][y][1] = 0;
13075                 } else
13076                 if(x == BOARD_RGHT+1) {
13077                     if(y >= gameInfo.holdingsSize) break;
13078                     boards[0][y][BOARD_WIDTH-2] = 0;
13079                 } else break;
13080             }
13081             boards[0][y][x] = EmptySquare;
13082             DrawPosition(FALSE, boards[0]);
13083         }
13084         break;
13085
13086       case PromotePiece:
13087         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13088            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13089             selection = (ChessSquare) (PROMOTED piece);
13090         } else if(piece == EmptySquare) selection = WhiteSilver;
13091         else selection = (ChessSquare)((int)piece - 1);
13092         goto defaultlabel;
13093
13094       case DemotePiece:
13095         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13096            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13097             selection = (ChessSquare) (DEMOTED piece);
13098         } else if(piece == EmptySquare) selection = BlackSilver;
13099         else selection = (ChessSquare)((int)piece + 1);
13100         goto defaultlabel;
13101
13102       case WhiteQueen:
13103       case BlackQueen:
13104         if(gameInfo.variant == VariantShatranj ||
13105            gameInfo.variant == VariantXiangqi  ||
13106            gameInfo.variant == VariantCourier  ||
13107            gameInfo.variant == VariantMakruk     )
13108             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13109         goto defaultlabel;
13110
13111       case WhiteKing:
13112       case BlackKing:
13113         if(gameInfo.variant == VariantXiangqi)
13114             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13115         if(gameInfo.variant == VariantKnightmate)
13116             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13117       default:
13118         defaultlabel:
13119         if (gameMode == IcsExamining) {
13120             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13121             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13122                      PieceToChar(selection), AAA + x, ONE + y);
13123             SendToICS(buf);
13124         } else {
13125             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13126                 int n;
13127                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13128                     n = PieceToNumber(selection - BlackPawn);
13129                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13130                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13131                     boards[0][BOARD_HEIGHT-1-n][1]++;
13132                 } else
13133                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13134                     n = PieceToNumber(selection);
13135                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13136                     boards[0][n][BOARD_WIDTH-1] = selection;
13137                     boards[0][n][BOARD_WIDTH-2]++;
13138                 }
13139             } else
13140             boards[0][y][x] = selection;
13141             DrawPosition(TRUE, boards[0]);
13142         }
13143         break;
13144     }
13145 }
13146
13147
13148 void
13149 DropMenuEvent(selection, x, y)
13150      ChessSquare selection;
13151      int x, y;
13152 {
13153     ChessMove moveType;
13154
13155     switch (gameMode) {
13156       case IcsPlayingWhite:
13157       case MachinePlaysBlack:
13158         if (!WhiteOnMove(currentMove)) {
13159             DisplayMoveError(_("It is Black's turn"));
13160             return;
13161         }
13162         moveType = WhiteDrop;
13163         break;
13164       case IcsPlayingBlack:
13165       case MachinePlaysWhite:
13166         if (WhiteOnMove(currentMove)) {
13167             DisplayMoveError(_("It is White's turn"));
13168             return;
13169         }
13170         moveType = BlackDrop;
13171         break;
13172       case EditGame:
13173         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13174         break;
13175       default:
13176         return;
13177     }
13178
13179     if (moveType == BlackDrop && selection < BlackPawn) {
13180       selection = (ChessSquare) ((int) selection
13181                                  + (int) BlackPawn - (int) WhitePawn);
13182     }
13183     if (boards[currentMove][y][x] != EmptySquare) {
13184         DisplayMoveError(_("That square is occupied"));
13185         return;
13186     }
13187
13188     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13189 }
13190
13191 void
13192 AcceptEvent()
13193 {
13194     /* Accept a pending offer of any kind from opponent */
13195
13196     if (appData.icsActive) {
13197         SendToICS(ics_prefix);
13198         SendToICS("accept\n");
13199     } else if (cmailMsgLoaded) {
13200         if (currentMove == cmailOldMove &&
13201             commentList[cmailOldMove] != NULL &&
13202             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13203                    "Black offers a draw" : "White offers a draw")) {
13204             TruncateGame();
13205             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13206             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13207         } else {
13208             DisplayError(_("There is no pending offer on this move"), 0);
13209             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13210         }
13211     } else {
13212         /* Not used for offers from chess program */
13213     }
13214 }
13215
13216 void
13217 DeclineEvent()
13218 {
13219     /* Decline a pending offer of any kind from opponent */
13220
13221     if (appData.icsActive) {
13222         SendToICS(ics_prefix);
13223         SendToICS("decline\n");
13224     } else if (cmailMsgLoaded) {
13225         if (currentMove == cmailOldMove &&
13226             commentList[cmailOldMove] != NULL &&
13227             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13228                    "Black offers a draw" : "White offers a draw")) {
13229 #ifdef NOTDEF
13230             AppendComment(cmailOldMove, "Draw declined", TRUE);
13231             DisplayComment(cmailOldMove - 1, "Draw declined");
13232 #endif /*NOTDEF*/
13233         } else {
13234             DisplayError(_("There is no pending offer on this move"), 0);
13235         }
13236     } else {
13237         /* Not used for offers from chess program */
13238     }
13239 }
13240
13241 void
13242 RematchEvent()
13243 {
13244     /* Issue ICS rematch command */
13245     if (appData.icsActive) {
13246         SendToICS(ics_prefix);
13247         SendToICS("rematch\n");
13248     }
13249 }
13250
13251 void
13252 CallFlagEvent()
13253 {
13254     /* Call your opponent's flag (claim a win on time) */
13255     if (appData.icsActive) {
13256         SendToICS(ics_prefix);
13257         SendToICS("flag\n");
13258     } else {
13259         switch (gameMode) {
13260           default:
13261             return;
13262           case MachinePlaysWhite:
13263             if (whiteFlag) {
13264                 if (blackFlag)
13265                   GameEnds(GameIsDrawn, "Both players ran out of time",
13266                            GE_PLAYER);
13267                 else
13268                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13269             } else {
13270                 DisplayError(_("Your opponent is not out of time"), 0);
13271             }
13272             break;
13273           case MachinePlaysBlack:
13274             if (blackFlag) {
13275                 if (whiteFlag)
13276                   GameEnds(GameIsDrawn, "Both players ran out of time",
13277                            GE_PLAYER);
13278                 else
13279                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13280             } else {
13281                 DisplayError(_("Your opponent is not out of time"), 0);
13282             }
13283             break;
13284         }
13285     }
13286 }
13287
13288 void
13289 ClockClick(int which)
13290 {       // [HGM] code moved to back-end from winboard.c
13291         if(which) { // black clock
13292           if (gameMode == EditPosition || gameMode == IcsExamining) {
13293             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13294             SetBlackToPlayEvent();
13295           } else if (gameMode == EditGame || shiftKey) {
13296             AdjustClock(which, -1);
13297           } else if (gameMode == IcsPlayingWhite ||
13298                      gameMode == MachinePlaysBlack) {
13299             CallFlagEvent();
13300           }
13301         } else { // white clock
13302           if (gameMode == EditPosition || gameMode == IcsExamining) {
13303             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13304             SetWhiteToPlayEvent();
13305           } else if (gameMode == EditGame || shiftKey) {
13306             AdjustClock(which, -1);
13307           } else if (gameMode == IcsPlayingBlack ||
13308                    gameMode == MachinePlaysWhite) {
13309             CallFlagEvent();
13310           }
13311         }
13312 }
13313
13314 void
13315 DrawEvent()
13316 {
13317     /* Offer draw or accept pending draw offer from opponent */
13318
13319     if (appData.icsActive) {
13320         /* Note: tournament rules require draw offers to be
13321            made after you make your move but before you punch
13322            your clock.  Currently ICS doesn't let you do that;
13323            instead, you immediately punch your clock after making
13324            a move, but you can offer a draw at any time. */
13325
13326         SendToICS(ics_prefix);
13327         SendToICS("draw\n");
13328         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13329     } else if (cmailMsgLoaded) {
13330         if (currentMove == cmailOldMove &&
13331             commentList[cmailOldMove] != NULL &&
13332             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13333                    "Black offers a draw" : "White offers a draw")) {
13334             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13335             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13336         } else if (currentMove == cmailOldMove + 1) {
13337             char *offer = WhiteOnMove(cmailOldMove) ?
13338               "White offers a draw" : "Black offers a draw";
13339             AppendComment(currentMove, offer, TRUE);
13340             DisplayComment(currentMove - 1, offer);
13341             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13342         } else {
13343             DisplayError(_("You must make your move before offering a draw"), 0);
13344             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13345         }
13346     } else if (first.offeredDraw) {
13347         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13348     } else {
13349         if (first.sendDrawOffers) {
13350             SendToProgram("draw\n", &first);
13351             userOfferedDraw = TRUE;
13352         }
13353     }
13354 }
13355
13356 void
13357 AdjournEvent()
13358 {
13359     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13360
13361     if (appData.icsActive) {
13362         SendToICS(ics_prefix);
13363         SendToICS("adjourn\n");
13364     } else {
13365         /* Currently GNU Chess doesn't offer or accept Adjourns */
13366     }
13367 }
13368
13369
13370 void
13371 AbortEvent()
13372 {
13373     /* Offer Abort or accept pending Abort offer from opponent */
13374
13375     if (appData.icsActive) {
13376         SendToICS(ics_prefix);
13377         SendToICS("abort\n");
13378     } else {
13379         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13380     }
13381 }
13382
13383 void
13384 ResignEvent()
13385 {
13386     /* Resign.  You can do this even if it's not your turn. */
13387
13388     if (appData.icsActive) {
13389         SendToICS(ics_prefix);
13390         SendToICS("resign\n");
13391     } else {
13392         switch (gameMode) {
13393           case MachinePlaysWhite:
13394             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13395             break;
13396           case MachinePlaysBlack:
13397             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13398             break;
13399           case EditGame:
13400             if (cmailMsgLoaded) {
13401                 TruncateGame();
13402                 if (WhiteOnMove(cmailOldMove)) {
13403                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13404                 } else {
13405                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13406                 }
13407                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13408             }
13409             break;
13410           default:
13411             break;
13412         }
13413     }
13414 }
13415
13416
13417 void
13418 StopObservingEvent()
13419 {
13420     /* Stop observing current games */
13421     SendToICS(ics_prefix);
13422     SendToICS("unobserve\n");
13423 }
13424
13425 void
13426 StopExaminingEvent()
13427 {
13428     /* Stop observing current game */
13429     SendToICS(ics_prefix);
13430     SendToICS("unexamine\n");
13431 }
13432
13433 void
13434 ForwardInner(target)
13435      int target;
13436 {
13437     int limit;
13438
13439     if (appData.debugMode)
13440         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13441                 target, currentMove, forwardMostMove);
13442
13443     if (gameMode == EditPosition)
13444       return;
13445
13446     if (gameMode == PlayFromGameFile && !pausing)
13447       PauseEvent();
13448
13449     if (gameMode == IcsExamining && pausing)
13450       limit = pauseExamForwardMostMove;
13451     else
13452       limit = forwardMostMove;
13453
13454     if (target > limit) target = limit;
13455
13456     if (target > 0 && moveList[target - 1][0]) {
13457         int fromX, fromY, toX, toY;
13458         toX = moveList[target - 1][2] - AAA;
13459         toY = moveList[target - 1][3] - ONE;
13460         if (moveList[target - 1][1] == '@') {
13461             if (appData.highlightLastMove) {
13462                 SetHighlights(-1, -1, toX, toY);
13463             }
13464         } else {
13465             fromX = moveList[target - 1][0] - AAA;
13466             fromY = moveList[target - 1][1] - ONE;
13467             if (target == currentMove + 1) {
13468                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13469             }
13470             if (appData.highlightLastMove) {
13471                 SetHighlights(fromX, fromY, toX, toY);
13472             }
13473         }
13474     }
13475     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13476         gameMode == Training || gameMode == PlayFromGameFile ||
13477         gameMode == AnalyzeFile) {
13478         while (currentMove < target) {
13479             SendMoveToProgram(currentMove++, &first);
13480         }
13481     } else {
13482         currentMove = target;
13483     }
13484
13485     if (gameMode == EditGame || gameMode == EndOfGame) {
13486         whiteTimeRemaining = timeRemaining[0][currentMove];
13487         blackTimeRemaining = timeRemaining[1][currentMove];
13488     }
13489     DisplayBothClocks();
13490     DisplayMove(currentMove - 1);
13491     DrawPosition(FALSE, boards[currentMove]);
13492     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13493     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13494         DisplayComment(currentMove - 1, commentList[currentMove]);
13495     }
13496     DisplayBook(currentMove);
13497 }
13498
13499
13500 void
13501 ForwardEvent()
13502 {
13503     if (gameMode == IcsExamining && !pausing) {
13504         SendToICS(ics_prefix);
13505         SendToICS("forward\n");
13506     } else {
13507         ForwardInner(currentMove + 1);
13508     }
13509 }
13510
13511 void
13512 ToEndEvent()
13513 {
13514     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13515         /* to optimze, we temporarily turn off analysis mode while we feed
13516          * the remaining moves to the engine. Otherwise we get analysis output
13517          * after each move.
13518          */
13519         if (first.analysisSupport) {
13520           SendToProgram("exit\nforce\n", &first);
13521           first.analyzing = FALSE;
13522         }
13523     }
13524
13525     if (gameMode == IcsExamining && !pausing) {
13526         SendToICS(ics_prefix);
13527         SendToICS("forward 999999\n");
13528     } else {
13529         ForwardInner(forwardMostMove);
13530     }
13531
13532     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13533         /* we have fed all the moves, so reactivate analysis mode */
13534         SendToProgram("analyze\n", &first);
13535         first.analyzing = TRUE;
13536         /*first.maybeThinking = TRUE;*/
13537         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13538     }
13539 }
13540
13541 void
13542 BackwardInner(target)
13543      int target;
13544 {
13545     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13546
13547     if (appData.debugMode)
13548         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13549                 target, currentMove, forwardMostMove);
13550
13551     if (gameMode == EditPosition) return;
13552     if (currentMove <= backwardMostMove) {
13553         ClearHighlights();
13554         DrawPosition(full_redraw, boards[currentMove]);
13555         return;
13556     }
13557     if (gameMode == PlayFromGameFile && !pausing)
13558       PauseEvent();
13559
13560     if (moveList[target][0]) {
13561         int fromX, fromY, toX, toY;
13562         toX = moveList[target][2] - AAA;
13563         toY = moveList[target][3] - ONE;
13564         if (moveList[target][1] == '@') {
13565             if (appData.highlightLastMove) {
13566                 SetHighlights(-1, -1, toX, toY);
13567             }
13568         } else {
13569             fromX = moveList[target][0] - AAA;
13570             fromY = moveList[target][1] - ONE;
13571             if (target == currentMove - 1) {
13572                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13573             }
13574             if (appData.highlightLastMove) {
13575                 SetHighlights(fromX, fromY, toX, toY);
13576             }
13577         }
13578     }
13579     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13580         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13581         while (currentMove > target) {
13582             SendToProgram("undo\n", &first);
13583             currentMove--;
13584         }
13585     } else {
13586         currentMove = target;
13587     }
13588
13589     if (gameMode == EditGame || gameMode == EndOfGame) {
13590         whiteTimeRemaining = timeRemaining[0][currentMove];
13591         blackTimeRemaining = timeRemaining[1][currentMove];
13592     }
13593     DisplayBothClocks();
13594     DisplayMove(currentMove - 1);
13595     DrawPosition(full_redraw, boards[currentMove]);
13596     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13597     // [HGM] PV info: routine tests if comment empty
13598     DisplayComment(currentMove - 1, commentList[currentMove]);
13599     DisplayBook(currentMove);
13600 }
13601
13602 void
13603 BackwardEvent()
13604 {
13605     if (gameMode == IcsExamining && !pausing) {
13606         SendToICS(ics_prefix);
13607         SendToICS("backward\n");
13608     } else {
13609         BackwardInner(currentMove - 1);
13610     }
13611 }
13612
13613 void
13614 ToStartEvent()
13615 {
13616     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13617         /* to optimize, we temporarily turn off analysis mode while we undo
13618          * all the moves. Otherwise we get analysis output after each undo.
13619          */
13620         if (first.analysisSupport) {
13621           SendToProgram("exit\nforce\n", &first);
13622           first.analyzing = FALSE;
13623         }
13624     }
13625
13626     if (gameMode == IcsExamining && !pausing) {
13627         SendToICS(ics_prefix);
13628         SendToICS("backward 999999\n");
13629     } else {
13630         BackwardInner(backwardMostMove);
13631     }
13632
13633     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13634         /* we have fed all the moves, so reactivate analysis mode */
13635         SendToProgram("analyze\n", &first);
13636         first.analyzing = TRUE;
13637         /*first.maybeThinking = TRUE;*/
13638         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13639     }
13640 }
13641
13642 void
13643 ToNrEvent(int to)
13644 {
13645   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13646   if (to >= forwardMostMove) to = forwardMostMove;
13647   if (to <= backwardMostMove) to = backwardMostMove;
13648   if (to < currentMove) {
13649     BackwardInner(to);
13650   } else {
13651     ForwardInner(to);
13652   }
13653 }
13654
13655 void
13656 RevertEvent(Boolean annotate)
13657 {
13658     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13659         return;
13660     }
13661     if (gameMode != IcsExamining) {
13662         DisplayError(_("You are not examining a game"), 0);
13663         return;
13664     }
13665     if (pausing) {
13666         DisplayError(_("You can't revert while pausing"), 0);
13667         return;
13668     }
13669     SendToICS(ics_prefix);
13670     SendToICS("revert\n");
13671 }
13672
13673 void
13674 RetractMoveEvent()
13675 {
13676     switch (gameMode) {
13677       case MachinePlaysWhite:
13678       case MachinePlaysBlack:
13679         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13680             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13681             return;
13682         }
13683         if (forwardMostMove < 2) return;
13684         currentMove = forwardMostMove = forwardMostMove - 2;
13685         whiteTimeRemaining = timeRemaining[0][currentMove];
13686         blackTimeRemaining = timeRemaining[1][currentMove];
13687         DisplayBothClocks();
13688         DisplayMove(currentMove - 1);
13689         ClearHighlights();/*!! could figure this out*/
13690         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13691         SendToProgram("remove\n", &first);
13692         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13693         break;
13694
13695       case BeginningOfGame:
13696       default:
13697         break;
13698
13699       case IcsPlayingWhite:
13700       case IcsPlayingBlack:
13701         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13702             SendToICS(ics_prefix);
13703             SendToICS("takeback 2\n");
13704         } else {
13705             SendToICS(ics_prefix);
13706             SendToICS("takeback 1\n");
13707         }
13708         break;
13709     }
13710 }
13711
13712 void
13713 MoveNowEvent()
13714 {
13715     ChessProgramState *cps;
13716
13717     switch (gameMode) {
13718       case MachinePlaysWhite:
13719         if (!WhiteOnMove(forwardMostMove)) {
13720             DisplayError(_("It is your turn"), 0);
13721             return;
13722         }
13723         cps = &first;
13724         break;
13725       case MachinePlaysBlack:
13726         if (WhiteOnMove(forwardMostMove)) {
13727             DisplayError(_("It is your turn"), 0);
13728             return;
13729         }
13730         cps = &first;
13731         break;
13732       case TwoMachinesPlay:
13733         if (WhiteOnMove(forwardMostMove) ==
13734             (first.twoMachinesColor[0] == 'w')) {
13735             cps = &first;
13736         } else {
13737             cps = &second;
13738         }
13739         break;
13740       case BeginningOfGame:
13741       default:
13742         return;
13743     }
13744     SendToProgram("?\n", cps);
13745 }
13746
13747 void
13748 TruncateGameEvent()
13749 {
13750     EditGameEvent();
13751     if (gameMode != EditGame) return;
13752     TruncateGame();
13753 }
13754
13755 void
13756 TruncateGame()
13757 {
13758     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13759     if (forwardMostMove > currentMove) {
13760         if (gameInfo.resultDetails != NULL) {
13761             free(gameInfo.resultDetails);
13762             gameInfo.resultDetails = NULL;
13763             gameInfo.result = GameUnfinished;
13764         }
13765         forwardMostMove = currentMove;
13766         HistorySet(parseList, backwardMostMove, forwardMostMove,
13767                    currentMove-1);
13768     }
13769 }
13770
13771 void
13772 HintEvent()
13773 {
13774     if (appData.noChessProgram) return;
13775     switch (gameMode) {
13776       case MachinePlaysWhite:
13777         if (WhiteOnMove(forwardMostMove)) {
13778             DisplayError(_("Wait until your turn"), 0);
13779             return;
13780         }
13781         break;
13782       case BeginningOfGame:
13783       case MachinePlaysBlack:
13784         if (!WhiteOnMove(forwardMostMove)) {
13785             DisplayError(_("Wait until your turn"), 0);
13786             return;
13787         }
13788         break;
13789       default:
13790         DisplayError(_("No hint available"), 0);
13791         return;
13792     }
13793     SendToProgram("hint\n", &first);
13794     hintRequested = TRUE;
13795 }
13796
13797 void
13798 BookEvent()
13799 {
13800     if (appData.noChessProgram) return;
13801     switch (gameMode) {
13802       case MachinePlaysWhite:
13803         if (WhiteOnMove(forwardMostMove)) {
13804             DisplayError(_("Wait until your turn"), 0);
13805             return;
13806         }
13807         break;
13808       case BeginningOfGame:
13809       case MachinePlaysBlack:
13810         if (!WhiteOnMove(forwardMostMove)) {
13811             DisplayError(_("Wait until your turn"), 0);
13812             return;
13813         }
13814         break;
13815       case EditPosition:
13816         EditPositionDone(TRUE);
13817         break;
13818       case TwoMachinesPlay:
13819         return;
13820       default:
13821         break;
13822     }
13823     SendToProgram("bk\n", &first);
13824     bookOutput[0] = NULLCHAR;
13825     bookRequested = TRUE;
13826 }
13827
13828 void
13829 AboutGameEvent()
13830 {
13831     char *tags = PGNTags(&gameInfo);
13832     TagsPopUp(tags, CmailMsg());
13833     free(tags);
13834 }
13835
13836 /* end button procedures */
13837
13838 void
13839 PrintPosition(fp, move)
13840      FILE *fp;
13841      int move;
13842 {
13843     int i, j;
13844
13845     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13846         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13847             char c = PieceToChar(boards[move][i][j]);
13848             fputc(c == 'x' ? '.' : c, fp);
13849             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13850         }
13851     }
13852     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13853       fprintf(fp, "white to play\n");
13854     else
13855       fprintf(fp, "black to play\n");
13856 }
13857
13858 void
13859 PrintOpponents(fp)
13860      FILE *fp;
13861 {
13862     if (gameInfo.white != NULL) {
13863         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13864     } else {
13865         fprintf(fp, "\n");
13866     }
13867 }
13868
13869 /* Find last component of program's own name, using some heuristics */
13870 void
13871 TidyProgramName(prog, host, buf)
13872      char *prog, *host, buf[MSG_SIZ];
13873 {
13874     char *p, *q;
13875     int local = (strcmp(host, "localhost") == 0);
13876     while (!local && (p = strchr(prog, ';')) != NULL) {
13877         p++;
13878         while (*p == ' ') p++;
13879         prog = p;
13880     }
13881     if (*prog == '"' || *prog == '\'') {
13882         q = strchr(prog + 1, *prog);
13883     } else {
13884         q = strchr(prog, ' ');
13885     }
13886     if (q == NULL) q = prog + strlen(prog);
13887     p = q;
13888     while (p >= prog && *p != '/' && *p != '\\') p--;
13889     p++;
13890     if(p == prog && *p == '"') p++;
13891     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13892     memcpy(buf, p, q - p);
13893     buf[q - p] = NULLCHAR;
13894     if (!local) {
13895         strcat(buf, "@");
13896         strcat(buf, host);
13897     }
13898 }
13899
13900 char *
13901 TimeControlTagValue()
13902 {
13903     char buf[MSG_SIZ];
13904     if (!appData.clockMode) {
13905       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13906     } else if (movesPerSession > 0) {
13907       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13908     } else if (timeIncrement == 0) {
13909       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13910     } else {
13911       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13912     }
13913     return StrSave(buf);
13914 }
13915
13916 void
13917 SetGameInfo()
13918 {
13919     /* This routine is used only for certain modes */
13920     VariantClass v = gameInfo.variant;
13921     ChessMove r = GameUnfinished;
13922     char *p = NULL;
13923
13924     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13925         r = gameInfo.result;
13926         p = gameInfo.resultDetails;
13927         gameInfo.resultDetails = NULL;
13928     }
13929     ClearGameInfo(&gameInfo);
13930     gameInfo.variant = v;
13931
13932     switch (gameMode) {
13933       case MachinePlaysWhite:
13934         gameInfo.event = StrSave( appData.pgnEventHeader );
13935         gameInfo.site = StrSave(HostName());
13936         gameInfo.date = PGNDate();
13937         gameInfo.round = StrSave("-");
13938         gameInfo.white = StrSave(first.tidy);
13939         gameInfo.black = StrSave(UserName());
13940         gameInfo.timeControl = TimeControlTagValue();
13941         break;
13942
13943       case MachinePlaysBlack:
13944         gameInfo.event = StrSave( appData.pgnEventHeader );
13945         gameInfo.site = StrSave(HostName());
13946         gameInfo.date = PGNDate();
13947         gameInfo.round = StrSave("-");
13948         gameInfo.white = StrSave(UserName());
13949         gameInfo.black = StrSave(first.tidy);
13950         gameInfo.timeControl = TimeControlTagValue();
13951         break;
13952
13953       case TwoMachinesPlay:
13954         gameInfo.event = StrSave( appData.pgnEventHeader );
13955         gameInfo.site = StrSave(HostName());
13956         gameInfo.date = PGNDate();
13957         if (roundNr > 0) {
13958             char buf[MSG_SIZ];
13959             snprintf(buf, MSG_SIZ, "%d", roundNr);
13960             gameInfo.round = StrSave(buf);
13961         } else {
13962             gameInfo.round = StrSave("-");
13963         }
13964         if (first.twoMachinesColor[0] == 'w') {
13965             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13966             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13967         } else {
13968             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13969             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13970         }
13971         gameInfo.timeControl = TimeControlTagValue();
13972         break;
13973
13974       case EditGame:
13975         gameInfo.event = StrSave("Edited game");
13976         gameInfo.site = StrSave(HostName());
13977         gameInfo.date = PGNDate();
13978         gameInfo.round = StrSave("-");
13979         gameInfo.white = StrSave("-");
13980         gameInfo.black = StrSave("-");
13981         gameInfo.result = r;
13982         gameInfo.resultDetails = p;
13983         break;
13984
13985       case EditPosition:
13986         gameInfo.event = StrSave("Edited position");
13987         gameInfo.site = StrSave(HostName());
13988         gameInfo.date = PGNDate();
13989         gameInfo.round = StrSave("-");
13990         gameInfo.white = StrSave("-");
13991         gameInfo.black = StrSave("-");
13992         break;
13993
13994       case IcsPlayingWhite:
13995       case IcsPlayingBlack:
13996       case IcsObserving:
13997       case IcsExamining:
13998         break;
13999
14000       case PlayFromGameFile:
14001         gameInfo.event = StrSave("Game from non-PGN file");
14002         gameInfo.site = StrSave(HostName());
14003         gameInfo.date = PGNDate();
14004         gameInfo.round = StrSave("-");
14005         gameInfo.white = StrSave("?");
14006         gameInfo.black = StrSave("?");
14007         break;
14008
14009       default:
14010         break;
14011     }
14012 }
14013
14014 void
14015 ReplaceComment(index, text)
14016      int index;
14017      char *text;
14018 {
14019     int len;
14020     char *p;
14021     float score;
14022
14023     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14024        pvInfoList[index-1].depth == len &&
14025        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14026        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14027     while (*text == '\n') text++;
14028     len = strlen(text);
14029     while (len > 0 && text[len - 1] == '\n') len--;
14030
14031     if (commentList[index] != NULL)
14032       free(commentList[index]);
14033
14034     if (len == 0) {
14035         commentList[index] = NULL;
14036         return;
14037     }
14038   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14039       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14040       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14041     commentList[index] = (char *) malloc(len + 2);
14042     strncpy(commentList[index], text, len);
14043     commentList[index][len] = '\n';
14044     commentList[index][len + 1] = NULLCHAR;
14045   } else {
14046     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14047     char *p;
14048     commentList[index] = (char *) malloc(len + 7);
14049     safeStrCpy(commentList[index], "{\n", 3);
14050     safeStrCpy(commentList[index]+2, text, len+1);
14051     commentList[index][len+2] = NULLCHAR;
14052     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14053     strcat(commentList[index], "\n}\n");
14054   }
14055 }
14056
14057 void
14058 CrushCRs(text)
14059      char *text;
14060 {
14061   char *p = text;
14062   char *q = text;
14063   char ch;
14064
14065   do {
14066     ch = *p++;
14067     if (ch == '\r') continue;
14068     *q++ = ch;
14069   } while (ch != '\0');
14070 }
14071
14072 void
14073 AppendComment(index, text, addBraces)
14074      int index;
14075      char *text;
14076      Boolean addBraces; // [HGM] braces: tells if we should add {}
14077 {
14078     int oldlen, len;
14079     char *old;
14080
14081 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14082     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14083
14084     CrushCRs(text);
14085     while (*text == '\n') text++;
14086     len = strlen(text);
14087     while (len > 0 && text[len - 1] == '\n') len--;
14088
14089     if (len == 0) return;
14090
14091     if (commentList[index] != NULL) {
14092         old = commentList[index];
14093         oldlen = strlen(old);
14094         while(commentList[index][oldlen-1] ==  '\n')
14095           commentList[index][--oldlen] = NULLCHAR;
14096         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14097         safeStrCpy(commentList[index], old, oldlen + len + 6);
14098         free(old);
14099         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14100         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14101           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14102           while (*text == '\n') { text++; len--; }
14103           commentList[index][--oldlen] = NULLCHAR;
14104       }
14105         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14106         else          strcat(commentList[index], "\n");
14107         strcat(commentList[index], text);
14108         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14109         else          strcat(commentList[index], "\n");
14110     } else {
14111         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14112         if(addBraces)
14113           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14114         else commentList[index][0] = NULLCHAR;
14115         strcat(commentList[index], text);
14116         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14117         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14118     }
14119 }
14120
14121 static char * FindStr( char * text, char * sub_text )
14122 {
14123     char * result = strstr( text, sub_text );
14124
14125     if( result != NULL ) {
14126         result += strlen( sub_text );
14127     }
14128
14129     return result;
14130 }
14131
14132 /* [AS] Try to extract PV info from PGN comment */
14133 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14134 char *GetInfoFromComment( int index, char * text )
14135 {
14136     char * sep = text, *p;
14137
14138     if( text != NULL && index > 0 ) {
14139         int score = 0;
14140         int depth = 0;
14141         int time = -1, sec = 0, deci;
14142         char * s_eval = FindStr( text, "[%eval " );
14143         char * s_emt = FindStr( text, "[%emt " );
14144
14145         if( s_eval != NULL || s_emt != NULL ) {
14146             /* New style */
14147             char delim;
14148
14149             if( s_eval != NULL ) {
14150                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14151                     return text;
14152                 }
14153
14154                 if( delim != ']' ) {
14155                     return text;
14156                 }
14157             }
14158
14159             if( s_emt != NULL ) {
14160             }
14161                 return text;
14162         }
14163         else {
14164             /* We expect something like: [+|-]nnn.nn/dd */
14165             int score_lo = 0;
14166
14167             if(*text != '{') return text; // [HGM] braces: must be normal comment
14168
14169             sep = strchr( text, '/' );
14170             if( sep == NULL || sep < (text+4) ) {
14171                 return text;
14172             }
14173
14174             p = text;
14175             if(p[1] == '(') { // comment starts with PV
14176                p = strchr(p, ')'); // locate end of PV
14177                if(p == NULL || sep < p+5) return text;
14178                // at this point we have something like "{(.*) +0.23/6 ..."
14179                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14180                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14181                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14182             }
14183             time = -1; sec = -1; deci = -1;
14184             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14185                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14186                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14187                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14188                 return text;
14189             }
14190
14191             if( score_lo < 0 || score_lo >= 100 ) {
14192                 return text;
14193             }
14194
14195             if(sec >= 0) time = 600*time + 10*sec; else
14196             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14197
14198             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14199
14200             /* [HGM] PV time: now locate end of PV info */
14201             while( *++sep >= '0' && *sep <= '9'); // strip depth
14202             if(time >= 0)
14203             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14204             if(sec >= 0)
14205             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14206             if(deci >= 0)
14207             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14208             while(*sep == ' ') sep++;
14209         }
14210
14211         if( depth <= 0 ) {
14212             return text;
14213         }
14214
14215         if( time < 0 ) {
14216             time = -1;
14217         }
14218
14219         pvInfoList[index-1].depth = depth;
14220         pvInfoList[index-1].score = score;
14221         pvInfoList[index-1].time  = 10*time; // centi-sec
14222         if(*sep == '}') *sep = 0; else *--sep = '{';
14223         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14224     }
14225     return sep;
14226 }
14227
14228 void
14229 SendToProgram(message, cps)
14230      char *message;
14231      ChessProgramState *cps;
14232 {
14233     int count, outCount, error;
14234     char buf[MSG_SIZ];
14235
14236     if (cps->pr == NULL) return;
14237     Attention(cps);
14238
14239     if (appData.debugMode) {
14240         TimeMark now;
14241         GetTimeMark(&now);
14242         fprintf(debugFP, "%ld >%-6s: %s",
14243                 SubtractTimeMarks(&now, &programStartTime),
14244                 cps->which, message);
14245     }
14246
14247     count = strlen(message);
14248     outCount = OutputToProcess(cps->pr, message, count, &error);
14249     if (outCount < count && !exiting
14250                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14251       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14252       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14253         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14254             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14255                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14256                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14257                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14258             } else {
14259                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14260                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14261                 gameInfo.result = res;
14262             }
14263             gameInfo.resultDetails = StrSave(buf);
14264         }
14265         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14266         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14267     }
14268 }
14269
14270 void
14271 ReceiveFromProgram(isr, closure, message, count, error)
14272      InputSourceRef isr;
14273      VOIDSTAR closure;
14274      char *message;
14275      int count;
14276      int error;
14277 {
14278     char *end_str;
14279     char buf[MSG_SIZ];
14280     ChessProgramState *cps = (ChessProgramState *)closure;
14281
14282     if (isr != cps->isr) return; /* Killed intentionally */
14283     if (count <= 0) {
14284         if (count == 0) {
14285             RemoveInputSource(cps->isr);
14286             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14287                     _(cps->which), cps->program);
14288         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14289                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14290                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14291                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14292                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14293                 } else {
14294                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14295                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14296                     gameInfo.result = res;
14297                 }
14298                 gameInfo.resultDetails = StrSave(buf);
14299             }
14300             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14301             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14302         } else {
14303             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14304                     _(cps->which), cps->program);
14305             RemoveInputSource(cps->isr);
14306
14307             /* [AS] Program is misbehaving badly... kill it */
14308             if( count == -2 ) {
14309                 DestroyChildProcess( cps->pr, 9 );
14310                 cps->pr = NoProc;
14311             }
14312
14313             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14314         }
14315         return;
14316     }
14317
14318     if ((end_str = strchr(message, '\r')) != NULL)
14319       *end_str = NULLCHAR;
14320     if ((end_str = strchr(message, '\n')) != NULL)
14321       *end_str = NULLCHAR;
14322
14323     if (appData.debugMode) {
14324         TimeMark now; int print = 1;
14325         char *quote = ""; char c; int i;
14326
14327         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14328                 char start = message[0];
14329                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14330                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14331                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14332                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14333                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14334                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14335                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14336                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14337                    sscanf(message, "hint: %c", &c)!=1 && 
14338                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14339                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14340                     print = (appData.engineComments >= 2);
14341                 }
14342                 message[0] = start; // restore original message
14343         }
14344         if(print) {
14345                 GetTimeMark(&now);
14346                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14347                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14348                         quote,
14349                         message);
14350         }
14351     }
14352
14353     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14354     if (appData.icsEngineAnalyze) {
14355         if (strstr(message, "whisper") != NULL ||
14356              strstr(message, "kibitz") != NULL ||
14357             strstr(message, "tellics") != NULL) return;
14358     }
14359
14360     HandleMachineMove(message, cps);
14361 }
14362
14363
14364 void
14365 SendTimeControl(cps, mps, tc, inc, sd, st)
14366      ChessProgramState *cps;
14367      int mps, inc, sd, st;
14368      long tc;
14369 {
14370     char buf[MSG_SIZ];
14371     int seconds;
14372
14373     if( timeControl_2 > 0 ) {
14374         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14375             tc = timeControl_2;
14376         }
14377     }
14378     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14379     inc /= cps->timeOdds;
14380     st  /= cps->timeOdds;
14381
14382     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14383
14384     if (st > 0) {
14385       /* Set exact time per move, normally using st command */
14386       if (cps->stKludge) {
14387         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14388         seconds = st % 60;
14389         if (seconds == 0) {
14390           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14391         } else {
14392           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14393         }
14394       } else {
14395         snprintf(buf, MSG_SIZ, "st %d\n", st);
14396       }
14397     } else {
14398       /* Set conventional or incremental time control, using level command */
14399       if (seconds == 0) {
14400         /* Note old gnuchess bug -- minutes:seconds used to not work.
14401            Fixed in later versions, but still avoid :seconds
14402            when seconds is 0. */
14403         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14404       } else {
14405         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14406                  seconds, inc/1000.);
14407       }
14408     }
14409     SendToProgram(buf, cps);
14410
14411     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14412     /* Orthogonally, limit search to given depth */
14413     if (sd > 0) {
14414       if (cps->sdKludge) {
14415         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14416       } else {
14417         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14418       }
14419       SendToProgram(buf, cps);
14420     }
14421
14422     if(cps->nps >= 0) { /* [HGM] nps */
14423         if(cps->supportsNPS == FALSE)
14424           cps->nps = -1; // don't use if engine explicitly says not supported!
14425         else {
14426           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14427           SendToProgram(buf, cps);
14428         }
14429     }
14430 }
14431
14432 ChessProgramState *WhitePlayer()
14433 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14434 {
14435     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14436        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14437         return &second;
14438     return &first;
14439 }
14440
14441 void
14442 SendTimeRemaining(cps, machineWhite)
14443      ChessProgramState *cps;
14444      int /*boolean*/ machineWhite;
14445 {
14446     char message[MSG_SIZ];
14447     long time, otime;
14448
14449     /* Note: this routine must be called when the clocks are stopped
14450        or when they have *just* been set or switched; otherwise
14451        it will be off by the time since the current tick started.
14452     */
14453     if (machineWhite) {
14454         time = whiteTimeRemaining / 10;
14455         otime = blackTimeRemaining / 10;
14456     } else {
14457         time = blackTimeRemaining / 10;
14458         otime = whiteTimeRemaining / 10;
14459     }
14460     /* [HGM] translate opponent's time by time-odds factor */
14461     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14462     if (appData.debugMode) {
14463         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14464     }
14465
14466     if (time <= 0) time = 1;
14467     if (otime <= 0) otime = 1;
14468
14469     snprintf(message, MSG_SIZ, "time %ld\n", time);
14470     SendToProgram(message, cps);
14471
14472     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14473     SendToProgram(message, cps);
14474 }
14475
14476 int
14477 BoolFeature(p, name, loc, cps)
14478      char **p;
14479      char *name;
14480      int *loc;
14481      ChessProgramState *cps;
14482 {
14483   char buf[MSG_SIZ];
14484   int len = strlen(name);
14485   int val;
14486
14487   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14488     (*p) += len + 1;
14489     sscanf(*p, "%d", &val);
14490     *loc = (val != 0);
14491     while (**p && **p != ' ')
14492       (*p)++;
14493     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14494     SendToProgram(buf, cps);
14495     return TRUE;
14496   }
14497   return FALSE;
14498 }
14499
14500 int
14501 IntFeature(p, name, loc, cps)
14502      char **p;
14503      char *name;
14504      int *loc;
14505      ChessProgramState *cps;
14506 {
14507   char buf[MSG_SIZ];
14508   int len = strlen(name);
14509   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14510     (*p) += len + 1;
14511     sscanf(*p, "%d", loc);
14512     while (**p && **p != ' ') (*p)++;
14513     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14514     SendToProgram(buf, cps);
14515     return TRUE;
14516   }
14517   return FALSE;
14518 }
14519
14520 int
14521 StringFeature(p, name, loc, cps)
14522      char **p;
14523      char *name;
14524      char loc[];
14525      ChessProgramState *cps;
14526 {
14527   char buf[MSG_SIZ];
14528   int len = strlen(name);
14529   if (strncmp((*p), name, len) == 0
14530       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14531     (*p) += len + 2;
14532     sscanf(*p, "%[^\"]", loc);
14533     while (**p && **p != '\"') (*p)++;
14534     if (**p == '\"') (*p)++;
14535     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14536     SendToProgram(buf, cps);
14537     return TRUE;
14538   }
14539   return FALSE;
14540 }
14541
14542 int
14543 ParseOption(Option *opt, ChessProgramState *cps)
14544 // [HGM] options: process the string that defines an engine option, and determine
14545 // name, type, default value, and allowed value range
14546 {
14547         char *p, *q, buf[MSG_SIZ];
14548         int n, min = (-1)<<31, max = 1<<31, def;
14549
14550         if(p = strstr(opt->name, " -spin ")) {
14551             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14552             if(max < min) max = min; // enforce consistency
14553             if(def < min) def = min;
14554             if(def > max) def = max;
14555             opt->value = def;
14556             opt->min = min;
14557             opt->max = max;
14558             opt->type = Spin;
14559         } else if((p = strstr(opt->name, " -slider "))) {
14560             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14561             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14562             if(max < min) max = min; // enforce consistency
14563             if(def < min) def = min;
14564             if(def > max) def = max;
14565             opt->value = def;
14566             opt->min = min;
14567             opt->max = max;
14568             opt->type = Spin; // Slider;
14569         } else if((p = strstr(opt->name, " -string "))) {
14570             opt->textValue = p+9;
14571             opt->type = TextBox;
14572         } else if((p = strstr(opt->name, " -file "))) {
14573             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14574             opt->textValue = p+7;
14575             opt->type = FileName; // FileName;
14576         } else if((p = strstr(opt->name, " -path "))) {
14577             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14578             opt->textValue = p+7;
14579             opt->type = PathName; // PathName;
14580         } else if(p = strstr(opt->name, " -check ")) {
14581             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14582             opt->value = (def != 0);
14583             opt->type = CheckBox;
14584         } else if(p = strstr(opt->name, " -combo ")) {
14585             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14586             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14587             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14588             opt->value = n = 0;
14589             while(q = StrStr(q, " /// ")) {
14590                 n++; *q = 0;    // count choices, and null-terminate each of them
14591                 q += 5;
14592                 if(*q == '*') { // remember default, which is marked with * prefix
14593                     q++;
14594                     opt->value = n;
14595                 }
14596                 cps->comboList[cps->comboCnt++] = q;
14597             }
14598             cps->comboList[cps->comboCnt++] = NULL;
14599             opt->max = n + 1;
14600             opt->type = ComboBox;
14601         } else if(p = strstr(opt->name, " -button")) {
14602             opt->type = Button;
14603         } else if(p = strstr(opt->name, " -save")) {
14604             opt->type = SaveButton;
14605         } else return FALSE;
14606         *p = 0; // terminate option name
14607         // now look if the command-line options define a setting for this engine option.
14608         if(cps->optionSettings && cps->optionSettings[0])
14609             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14610         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14611           snprintf(buf, MSG_SIZ, "option %s", p);
14612                 if(p = strstr(buf, ",")) *p = 0;
14613                 if(q = strchr(buf, '=')) switch(opt->type) {
14614                     case ComboBox:
14615                         for(n=0; n<opt->max; n++)
14616                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14617                         break;
14618                     case TextBox:
14619                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14620                         break;
14621                     case Spin:
14622                     case CheckBox:
14623                         opt->value = atoi(q+1);
14624                     default:
14625                         break;
14626                 }
14627                 strcat(buf, "\n");
14628                 SendToProgram(buf, cps);
14629         }
14630         return TRUE;
14631 }
14632
14633 void
14634 FeatureDone(cps, val)
14635      ChessProgramState* cps;
14636      int val;
14637 {
14638   DelayedEventCallback cb = GetDelayedEvent();
14639   if ((cb == InitBackEnd3 && cps == &first) ||
14640       (cb == SettingsMenuIfReady && cps == &second) ||
14641       (cb == LoadEngine) ||
14642       (cb == TwoMachinesEventIfReady)) {
14643     CancelDelayedEvent();
14644     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14645   }
14646   cps->initDone = val;
14647 }
14648
14649 /* Parse feature command from engine */
14650 void
14651 ParseFeatures(args, cps)
14652      char* args;
14653      ChessProgramState *cps;
14654 {
14655   char *p = args;
14656   char *q;
14657   int val;
14658   char buf[MSG_SIZ];
14659
14660   for (;;) {
14661     while (*p == ' ') p++;
14662     if (*p == NULLCHAR) return;
14663
14664     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14665     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14666     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14667     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14668     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14669     if (BoolFeature(&p, "reuse", &val, cps)) {
14670       /* Engine can disable reuse, but can't enable it if user said no */
14671       if (!val) cps->reuse = FALSE;
14672       continue;
14673     }
14674     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14675     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14676       if (gameMode == TwoMachinesPlay) {
14677         DisplayTwoMachinesTitle();
14678       } else {
14679         DisplayTitle("");
14680       }
14681       continue;
14682     }
14683     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14684     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14685     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14686     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14687     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14688     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14689     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14690     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14691     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14692     if (IntFeature(&p, "done", &val, cps)) {
14693       FeatureDone(cps, val);
14694       continue;
14695     }
14696     /* Added by Tord: */
14697     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14698     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14699     /* End of additions by Tord */
14700
14701     /* [HGM] added features: */
14702     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14703     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14704     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14705     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14706     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14707     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14708     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14709         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14710           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14711             SendToProgram(buf, cps);
14712             continue;
14713         }
14714         if(cps->nrOptions >= MAX_OPTIONS) {
14715             cps->nrOptions--;
14716             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14717             DisplayError(buf, 0);
14718         }
14719         continue;
14720     }
14721     /* End of additions by HGM */
14722
14723     /* unknown feature: complain and skip */
14724     q = p;
14725     while (*q && *q != '=') q++;
14726     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14727     SendToProgram(buf, cps);
14728     p = q;
14729     if (*p == '=') {
14730       p++;
14731       if (*p == '\"') {
14732         p++;
14733         while (*p && *p != '\"') p++;
14734         if (*p == '\"') p++;
14735       } else {
14736         while (*p && *p != ' ') p++;
14737       }
14738     }
14739   }
14740
14741 }
14742
14743 void
14744 PeriodicUpdatesEvent(newState)
14745      int newState;
14746 {
14747     if (newState == appData.periodicUpdates)
14748       return;
14749
14750     appData.periodicUpdates=newState;
14751
14752     /* Display type changes, so update it now */
14753 //    DisplayAnalysis();
14754
14755     /* Get the ball rolling again... */
14756     if (newState) {
14757         AnalysisPeriodicEvent(1);
14758         StartAnalysisClock();
14759     }
14760 }
14761
14762 void
14763 PonderNextMoveEvent(newState)
14764      int newState;
14765 {
14766     if (newState == appData.ponderNextMove) return;
14767     if (gameMode == EditPosition) EditPositionDone(TRUE);
14768     if (newState) {
14769         SendToProgram("hard\n", &first);
14770         if (gameMode == TwoMachinesPlay) {
14771             SendToProgram("hard\n", &second);
14772         }
14773     } else {
14774         SendToProgram("easy\n", &first);
14775         thinkOutput[0] = NULLCHAR;
14776         if (gameMode == TwoMachinesPlay) {
14777             SendToProgram("easy\n", &second);
14778         }
14779     }
14780     appData.ponderNextMove = newState;
14781 }
14782
14783 void
14784 NewSettingEvent(option, feature, command, value)
14785      char *command;
14786      int option, value, *feature;
14787 {
14788     char buf[MSG_SIZ];
14789
14790     if (gameMode == EditPosition) EditPositionDone(TRUE);
14791     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14792     if(feature == NULL || *feature) SendToProgram(buf, &first);
14793     if (gameMode == TwoMachinesPlay) {
14794         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14795     }
14796 }
14797
14798 void
14799 ShowThinkingEvent()
14800 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14801 {
14802     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14803     int newState = appData.showThinking
14804         // [HGM] thinking: other features now need thinking output as well
14805         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14806
14807     if (oldState == newState) return;
14808     oldState = newState;
14809     if (gameMode == EditPosition) EditPositionDone(TRUE);
14810     if (oldState) {
14811         SendToProgram("post\n", &first);
14812         if (gameMode == TwoMachinesPlay) {
14813             SendToProgram("post\n", &second);
14814         }
14815     } else {
14816         SendToProgram("nopost\n", &first);
14817         thinkOutput[0] = NULLCHAR;
14818         if (gameMode == TwoMachinesPlay) {
14819             SendToProgram("nopost\n", &second);
14820         }
14821     }
14822 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14823 }
14824
14825 void
14826 AskQuestionEvent(title, question, replyPrefix, which)
14827      char *title; char *question; char *replyPrefix; char *which;
14828 {
14829   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14830   if (pr == NoProc) return;
14831   AskQuestion(title, question, replyPrefix, pr);
14832 }
14833
14834 void
14835 TypeInEvent(char firstChar)
14836 {
14837     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14838         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14839         gameMode == AnalyzeMode || gameMode == EditGame || \r
14840         gameMode == EditPosition || gameMode == IcsExamining ||\r
14841         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14842         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14843                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14844                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14845         gameMode == Training) PopUpMoveDialog(firstChar);
14846 }
14847
14848 void
14849 TypeInDoneEvent(char *move)
14850 {
14851         Board board;
14852         int n, fromX, fromY, toX, toY;
14853         char promoChar;
14854         ChessMove moveType;\r
14855
14856         // [HGM] FENedit\r
14857         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14858                 EditPositionPasteFEN(move);\r
14859                 return;\r
14860         }\r
14861         // [HGM] movenum: allow move number to be typed in any mode\r
14862         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14863           ToNrEvent(2*n-1);\r
14864           return;\r
14865         }\r
14866
14867       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14868         gameMode != Training) {\r
14869         DisplayMoveError(_("Displayed move is not current"));\r
14870       } else {\r
14871         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14872           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14873         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14874         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14875           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14876           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14877         } else {\r
14878           DisplayMoveError(_("Could not parse move"));\r
14879         }
14880       }\r
14881 }\r
14882
14883 void
14884 DisplayMove(moveNumber)
14885      int moveNumber;
14886 {
14887     char message[MSG_SIZ];
14888     char res[MSG_SIZ];
14889     char cpThinkOutput[MSG_SIZ];
14890
14891     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14892
14893     if (moveNumber == forwardMostMove - 1 ||
14894         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14895
14896         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14897
14898         if (strchr(cpThinkOutput, '\n')) {
14899             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14900         }
14901     } else {
14902         *cpThinkOutput = NULLCHAR;
14903     }
14904
14905     /* [AS] Hide thinking from human user */
14906     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14907         *cpThinkOutput = NULLCHAR;
14908         if( thinkOutput[0] != NULLCHAR ) {
14909             int i;
14910
14911             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14912                 cpThinkOutput[i] = '.';
14913             }
14914             cpThinkOutput[i] = NULLCHAR;
14915             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14916         }
14917     }
14918
14919     if (moveNumber == forwardMostMove - 1 &&
14920         gameInfo.resultDetails != NULL) {
14921         if (gameInfo.resultDetails[0] == NULLCHAR) {
14922           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14923         } else {
14924           snprintf(res, MSG_SIZ, " {%s} %s",
14925                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14926         }
14927     } else {
14928         res[0] = NULLCHAR;
14929     }
14930
14931     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14932         DisplayMessage(res, cpThinkOutput);
14933     } else {
14934       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14935                 WhiteOnMove(moveNumber) ? " " : ".. ",
14936                 parseList[moveNumber], res);
14937         DisplayMessage(message, cpThinkOutput);
14938     }
14939 }
14940
14941 void
14942 DisplayComment(moveNumber, text)
14943      int moveNumber;
14944      char *text;
14945 {
14946     char title[MSG_SIZ];
14947     char buf[8000]; // comment can be long!
14948     int score, depth;
14949
14950     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14951       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14952     } else {
14953       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14954               WhiteOnMove(moveNumber) ? " " : ".. ",
14955               parseList[moveNumber]);
14956     }
14957     // [HGM] PV info: display PV info together with (or as) comment
14958     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14959       if(text == NULL) text = "";
14960       score = pvInfoList[moveNumber].score;
14961       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14962               depth, (pvInfoList[moveNumber].time+50)/100, text);
14963       text = buf;
14964     }
14965     if (text != NULL && (appData.autoDisplayComment || commentUp))
14966         CommentPopUp(title, text);
14967 }
14968
14969 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14970  * might be busy thinking or pondering.  It can be omitted if your
14971  * gnuchess is configured to stop thinking immediately on any user
14972  * input.  However, that gnuchess feature depends on the FIONREAD
14973  * ioctl, which does not work properly on some flavors of Unix.
14974  */
14975 void
14976 Attention(cps)
14977      ChessProgramState *cps;
14978 {
14979 #if ATTENTION
14980     if (!cps->useSigint) return;
14981     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14982     switch (gameMode) {
14983       case MachinePlaysWhite:
14984       case MachinePlaysBlack:
14985       case TwoMachinesPlay:
14986       case IcsPlayingWhite:
14987       case IcsPlayingBlack:
14988       case AnalyzeMode:
14989       case AnalyzeFile:
14990         /* Skip if we know it isn't thinking */
14991         if (!cps->maybeThinking) return;
14992         if (appData.debugMode)
14993           fprintf(debugFP, "Interrupting %s\n", cps->which);
14994         InterruptChildProcess(cps->pr);
14995         cps->maybeThinking = FALSE;
14996         break;
14997       default:
14998         break;
14999     }
15000 #endif /*ATTENTION*/
15001 }
15002
15003 int
15004 CheckFlags()
15005 {
15006     if (whiteTimeRemaining <= 0) {
15007         if (!whiteFlag) {
15008             whiteFlag = TRUE;
15009             if (appData.icsActive) {
15010                 if (appData.autoCallFlag &&
15011                     gameMode == IcsPlayingBlack && !blackFlag) {
15012                   SendToICS(ics_prefix);
15013                   SendToICS("flag\n");
15014                 }
15015             } else {
15016                 if (blackFlag) {
15017                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15018                 } else {
15019                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15020                     if (appData.autoCallFlag) {
15021                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15022                         return TRUE;
15023                     }
15024                 }
15025             }
15026         }
15027     }
15028     if (blackTimeRemaining <= 0) {
15029         if (!blackFlag) {
15030             blackFlag = TRUE;
15031             if (appData.icsActive) {
15032                 if (appData.autoCallFlag &&
15033                     gameMode == IcsPlayingWhite && !whiteFlag) {
15034                   SendToICS(ics_prefix);
15035                   SendToICS("flag\n");
15036                 }
15037             } else {
15038                 if (whiteFlag) {
15039                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15040                 } else {
15041                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15042                     if (appData.autoCallFlag) {
15043                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15044                         return TRUE;
15045                     }
15046                 }
15047             }
15048         }
15049     }
15050     return FALSE;
15051 }
15052
15053 void
15054 CheckTimeControl()
15055 {
15056     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15057         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15058
15059     /*
15060      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15061      */
15062     if ( !WhiteOnMove(forwardMostMove) ) {
15063         /* White made time control */
15064         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15065         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15066         /* [HGM] time odds: correct new time quota for time odds! */
15067                                             / WhitePlayer()->timeOdds;
15068         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15069     } else {
15070         lastBlack -= blackTimeRemaining;
15071         /* Black made time control */
15072         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15073                                             / WhitePlayer()->other->timeOdds;
15074         lastWhite = whiteTimeRemaining;
15075     }
15076 }
15077
15078 void
15079 DisplayBothClocks()
15080 {
15081     int wom = gameMode == EditPosition ?
15082       !blackPlaysFirst : WhiteOnMove(currentMove);
15083     DisplayWhiteClock(whiteTimeRemaining, wom);
15084     DisplayBlackClock(blackTimeRemaining, !wom);
15085 }
15086
15087
15088 /* Timekeeping seems to be a portability nightmare.  I think everyone
15089    has ftime(), but I'm really not sure, so I'm including some ifdefs
15090    to use other calls if you don't.  Clocks will be less accurate if
15091    you have neither ftime nor gettimeofday.
15092 */
15093
15094 /* VS 2008 requires the #include outside of the function */
15095 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15096 #include <sys/timeb.h>
15097 #endif
15098
15099 /* Get the current time as a TimeMark */
15100 void
15101 GetTimeMark(tm)
15102      TimeMark *tm;
15103 {
15104 #if HAVE_GETTIMEOFDAY
15105
15106     struct timeval timeVal;
15107     struct timezone timeZone;
15108
15109     gettimeofday(&timeVal, &timeZone);
15110     tm->sec = (long) timeVal.tv_sec;
15111     tm->ms = (int) (timeVal.tv_usec / 1000L);
15112
15113 #else /*!HAVE_GETTIMEOFDAY*/
15114 #if HAVE_FTIME
15115
15116 // include <sys/timeb.h> / moved to just above start of function
15117     struct timeb timeB;
15118
15119     ftime(&timeB);
15120     tm->sec = (long) timeB.time;
15121     tm->ms = (int) timeB.millitm;
15122
15123 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15124     tm->sec = (long) time(NULL);
15125     tm->ms = 0;
15126 #endif
15127 #endif
15128 }
15129
15130 /* Return the difference in milliseconds between two
15131    time marks.  We assume the difference will fit in a long!
15132 */
15133 long
15134 SubtractTimeMarks(tm2, tm1)
15135      TimeMark *tm2, *tm1;
15136 {
15137     return 1000L*(tm2->sec - tm1->sec) +
15138            (long) (tm2->ms - tm1->ms);
15139 }
15140
15141
15142 /*
15143  * Code to manage the game clocks.
15144  *
15145  * In tournament play, black starts the clock and then white makes a move.
15146  * We give the human user a slight advantage if he is playing white---the
15147  * clocks don't run until he makes his first move, so it takes zero time.
15148  * Also, we don't account for network lag, so we could get out of sync
15149  * with GNU Chess's clock -- but then, referees are always right.
15150  */
15151
15152 static TimeMark tickStartTM;
15153 static long intendedTickLength;
15154
15155 long
15156 NextTickLength(timeRemaining)
15157      long timeRemaining;
15158 {
15159     long nominalTickLength, nextTickLength;
15160
15161     if (timeRemaining > 0L && timeRemaining <= 10000L)
15162       nominalTickLength = 100L;
15163     else
15164       nominalTickLength = 1000L;
15165     nextTickLength = timeRemaining % nominalTickLength;
15166     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15167
15168     return nextTickLength;
15169 }
15170
15171 /* Adjust clock one minute up or down */
15172 void
15173 AdjustClock(Boolean which, int dir)
15174 {
15175     if(which) blackTimeRemaining += 60000*dir;
15176     else      whiteTimeRemaining += 60000*dir;
15177     DisplayBothClocks();
15178 }
15179
15180 /* Stop clocks and reset to a fresh time control */
15181 void
15182 ResetClocks()
15183 {
15184     (void) StopClockTimer();
15185     if (appData.icsActive) {
15186         whiteTimeRemaining = blackTimeRemaining = 0;
15187     } else if (searchTime) {
15188         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15189         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15190     } else { /* [HGM] correct new time quote for time odds */
15191         whiteTC = blackTC = fullTimeControlString;
15192         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15193         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15194     }
15195     if (whiteFlag || blackFlag) {
15196         DisplayTitle("");
15197         whiteFlag = blackFlag = FALSE;
15198     }
15199     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15200     DisplayBothClocks();
15201 }
15202
15203 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15204
15205 /* Decrement running clock by amount of time that has passed */
15206 void
15207 DecrementClocks()
15208 {
15209     long timeRemaining;
15210     long lastTickLength, fudge;
15211     TimeMark now;
15212
15213     if (!appData.clockMode) return;
15214     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15215
15216     GetTimeMark(&now);
15217
15218     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15219
15220     /* Fudge if we woke up a little too soon */
15221     fudge = intendedTickLength - lastTickLength;
15222     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15223
15224     if (WhiteOnMove(forwardMostMove)) {
15225         if(whiteNPS >= 0) lastTickLength = 0;
15226         timeRemaining = whiteTimeRemaining -= lastTickLength;
15227         if(timeRemaining < 0 && !appData.icsActive) {
15228             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15229             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15230                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15231                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15232             }
15233         }
15234         DisplayWhiteClock(whiteTimeRemaining - fudge,
15235                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15236     } else {
15237         if(blackNPS >= 0) lastTickLength = 0;
15238         timeRemaining = blackTimeRemaining -= lastTickLength;
15239         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15240             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15241             if(suddenDeath) {
15242                 blackStartMove = forwardMostMove;
15243                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15244             }
15245         }
15246         DisplayBlackClock(blackTimeRemaining - fudge,
15247                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15248     }
15249     if (CheckFlags()) return;
15250
15251     tickStartTM = now;
15252     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15253     StartClockTimer(intendedTickLength);
15254
15255     /* if the time remaining has fallen below the alarm threshold, sound the
15256      * alarm. if the alarm has sounded and (due to a takeback or time control
15257      * with increment) the time remaining has increased to a level above the
15258      * threshold, reset the alarm so it can sound again.
15259      */
15260
15261     if (appData.icsActive && appData.icsAlarm) {
15262
15263         /* make sure we are dealing with the user's clock */
15264         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15265                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15266            )) return;
15267
15268         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15269             alarmSounded = FALSE;
15270         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15271             PlayAlarmSound();
15272             alarmSounded = TRUE;
15273         }
15274     }
15275 }
15276
15277
15278 /* A player has just moved, so stop the previously running
15279    clock and (if in clock mode) start the other one.
15280    We redisplay both clocks in case we're in ICS mode, because
15281    ICS gives us an update to both clocks after every move.
15282    Note that this routine is called *after* forwardMostMove
15283    is updated, so the last fractional tick must be subtracted
15284    from the color that is *not* on move now.
15285 */
15286 void
15287 SwitchClocks(int newMoveNr)
15288 {
15289     long lastTickLength;
15290     TimeMark now;
15291     int flagged = FALSE;
15292
15293     GetTimeMark(&now);
15294
15295     if (StopClockTimer() && appData.clockMode) {
15296         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15297         if (!WhiteOnMove(forwardMostMove)) {
15298             if(blackNPS >= 0) lastTickLength = 0;
15299             blackTimeRemaining -= lastTickLength;
15300            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15301 //         if(pvInfoList[forwardMostMove].time == -1)
15302                  pvInfoList[forwardMostMove].time =               // use GUI time
15303                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15304         } else {
15305            if(whiteNPS >= 0) lastTickLength = 0;
15306            whiteTimeRemaining -= lastTickLength;
15307            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15308 //         if(pvInfoList[forwardMostMove].time == -1)
15309                  pvInfoList[forwardMostMove].time =
15310                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15311         }
15312         flagged = CheckFlags();
15313     }
15314     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15315     CheckTimeControl();
15316
15317     if (flagged || !appData.clockMode) return;
15318
15319     switch (gameMode) {
15320       case MachinePlaysBlack:
15321       case MachinePlaysWhite:
15322       case BeginningOfGame:
15323         if (pausing) return;
15324         break;
15325
15326       case EditGame:
15327       case PlayFromGameFile:
15328       case IcsExamining:
15329         return;
15330
15331       default:
15332         break;
15333     }
15334
15335     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15336         if(WhiteOnMove(forwardMostMove))
15337              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15338         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15339     }
15340
15341     tickStartTM = now;
15342     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15343       whiteTimeRemaining : blackTimeRemaining);
15344     StartClockTimer(intendedTickLength);
15345 }
15346
15347
15348 /* Stop both clocks */
15349 void
15350 StopClocks()
15351 {
15352     long lastTickLength;
15353     TimeMark now;
15354
15355     if (!StopClockTimer()) return;
15356     if (!appData.clockMode) return;
15357
15358     GetTimeMark(&now);
15359
15360     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15361     if (WhiteOnMove(forwardMostMove)) {
15362         if(whiteNPS >= 0) lastTickLength = 0;
15363         whiteTimeRemaining -= lastTickLength;
15364         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15365     } else {
15366         if(blackNPS >= 0) lastTickLength = 0;
15367         blackTimeRemaining -= lastTickLength;
15368         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15369     }
15370     CheckFlags();
15371 }
15372
15373 /* Start clock of player on move.  Time may have been reset, so
15374    if clock is already running, stop and restart it. */
15375 void
15376 StartClocks()
15377 {
15378     (void) StopClockTimer(); /* in case it was running already */
15379     DisplayBothClocks();
15380     if (CheckFlags()) return;
15381
15382     if (!appData.clockMode) return;
15383     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15384
15385     GetTimeMark(&tickStartTM);
15386     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15387       whiteTimeRemaining : blackTimeRemaining);
15388
15389    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15390     whiteNPS = blackNPS = -1;
15391     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15392        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15393         whiteNPS = first.nps;
15394     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15395        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15396         blackNPS = first.nps;
15397     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15398         whiteNPS = second.nps;
15399     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15400         blackNPS = second.nps;
15401     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15402
15403     StartClockTimer(intendedTickLength);
15404 }
15405
15406 char *
15407 TimeString(ms)
15408      long ms;
15409 {
15410     long second, minute, hour, day;
15411     char *sign = "";
15412     static char buf[32];
15413
15414     if (ms > 0 && ms <= 9900) {
15415       /* convert milliseconds to tenths, rounding up */
15416       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15417
15418       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15419       return buf;
15420     }
15421
15422     /* convert milliseconds to seconds, rounding up */
15423     /* use floating point to avoid strangeness of integer division
15424        with negative dividends on many machines */
15425     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15426
15427     if (second < 0) {
15428         sign = "-";
15429         second = -second;
15430     }
15431
15432     day = second / (60 * 60 * 24);
15433     second = second % (60 * 60 * 24);
15434     hour = second / (60 * 60);
15435     second = second % (60 * 60);
15436     minute = second / 60;
15437     second = second % 60;
15438
15439     if (day > 0)
15440       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15441               sign, day, hour, minute, second);
15442     else if (hour > 0)
15443       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15444     else
15445       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15446
15447     return buf;
15448 }
15449
15450
15451 /*
15452  * This is necessary because some C libraries aren't ANSI C compliant yet.
15453  */
15454 char *
15455 StrStr(string, match)
15456      char *string, *match;
15457 {
15458     int i, length;
15459
15460     length = strlen(match);
15461
15462     for (i = strlen(string) - length; i >= 0; i--, string++)
15463       if (!strncmp(match, string, length))
15464         return string;
15465
15466     return NULL;
15467 }
15468
15469 char *
15470 StrCaseStr(string, match)
15471      char *string, *match;
15472 {
15473     int i, j, length;
15474
15475     length = strlen(match);
15476
15477     for (i = strlen(string) - length; i >= 0; i--, string++) {
15478         for (j = 0; j < length; j++) {
15479             if (ToLower(match[j]) != ToLower(string[j]))
15480               break;
15481         }
15482         if (j == length) return string;
15483     }
15484
15485     return NULL;
15486 }
15487
15488 #ifndef _amigados
15489 int
15490 StrCaseCmp(s1, s2)
15491      char *s1, *s2;
15492 {
15493     char c1, c2;
15494
15495     for (;;) {
15496         c1 = ToLower(*s1++);
15497         c2 = ToLower(*s2++);
15498         if (c1 > c2) return 1;
15499         if (c1 < c2) return -1;
15500         if (c1 == NULLCHAR) return 0;
15501     }
15502 }
15503
15504
15505 int
15506 ToLower(c)
15507      int c;
15508 {
15509     return isupper(c) ? tolower(c) : c;
15510 }
15511
15512
15513 int
15514 ToUpper(c)
15515      int c;
15516 {
15517     return islower(c) ? toupper(c) : c;
15518 }
15519 #endif /* !_amigados    */
15520
15521 char *
15522 StrSave(s)
15523      char *s;
15524 {
15525   char *ret;
15526
15527   if ((ret = (char *) malloc(strlen(s) + 1)))
15528     {
15529       safeStrCpy(ret, s, strlen(s)+1);
15530     }
15531   return ret;
15532 }
15533
15534 char *
15535 StrSavePtr(s, savePtr)
15536      char *s, **savePtr;
15537 {
15538     if (*savePtr) {
15539         free(*savePtr);
15540     }
15541     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15542       safeStrCpy(*savePtr, s, strlen(s)+1);
15543     }
15544     return(*savePtr);
15545 }
15546
15547 char *
15548 PGNDate()
15549 {
15550     time_t clock;
15551     struct tm *tm;
15552     char buf[MSG_SIZ];
15553
15554     clock = time((time_t *)NULL);
15555     tm = localtime(&clock);
15556     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15557             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15558     return StrSave(buf);
15559 }
15560
15561
15562 char *
15563 PositionToFEN(move, overrideCastling)
15564      int move;
15565      char *overrideCastling;
15566 {
15567     int i, j, fromX, fromY, toX, toY;
15568     int whiteToPlay;
15569     char buf[128];
15570     char *p, *q;
15571     int emptycount;
15572     ChessSquare piece;
15573
15574     whiteToPlay = (gameMode == EditPosition) ?
15575       !blackPlaysFirst : (move % 2 == 0);
15576     p = buf;
15577
15578     /* Piece placement data */
15579     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15580         emptycount = 0;
15581         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15582             if (boards[move][i][j] == EmptySquare) {
15583                 emptycount++;
15584             } else { ChessSquare piece = boards[move][i][j];
15585                 if (emptycount > 0) {
15586                     if(emptycount<10) /* [HGM] can be >= 10 */
15587                         *p++ = '0' + emptycount;
15588                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15589                     emptycount = 0;
15590                 }
15591                 if(PieceToChar(piece) == '+') {
15592                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15593                     *p++ = '+';
15594                     piece = (ChessSquare)(DEMOTED piece);
15595                 }
15596                 *p++ = PieceToChar(piece);
15597                 if(p[-1] == '~') {
15598                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15599                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15600                     *p++ = '~';
15601                 }
15602             }
15603         }
15604         if (emptycount > 0) {
15605             if(emptycount<10) /* [HGM] can be >= 10 */
15606                 *p++ = '0' + emptycount;
15607             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15608             emptycount = 0;
15609         }
15610         *p++ = '/';
15611     }
15612     *(p - 1) = ' ';
15613
15614     /* [HGM] print Crazyhouse or Shogi holdings */
15615     if( gameInfo.holdingsWidth ) {
15616         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15617         q = p;
15618         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15619             piece = boards[move][i][BOARD_WIDTH-1];
15620             if( piece != EmptySquare )
15621               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15622                   *p++ = PieceToChar(piece);
15623         }
15624         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15625             piece = boards[move][BOARD_HEIGHT-i-1][0];
15626             if( piece != EmptySquare )
15627               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15628                   *p++ = PieceToChar(piece);
15629         }
15630
15631         if( q == p ) *p++ = '-';
15632         *p++ = ']';
15633         *p++ = ' ';
15634     }
15635
15636     /* Active color */
15637     *p++ = whiteToPlay ? 'w' : 'b';
15638     *p++ = ' ';
15639
15640   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15641     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15642   } else {
15643   if(nrCastlingRights) {
15644      q = p;
15645      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15646        /* [HGM] write directly from rights */
15647            if(boards[move][CASTLING][2] != NoRights &&
15648               boards[move][CASTLING][0] != NoRights   )
15649                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15650            if(boards[move][CASTLING][2] != NoRights &&
15651               boards[move][CASTLING][1] != NoRights   )
15652                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15653            if(boards[move][CASTLING][5] != NoRights &&
15654               boards[move][CASTLING][3] != NoRights   )
15655                 *p++ = boards[move][CASTLING][3] + AAA;
15656            if(boards[move][CASTLING][5] != NoRights &&
15657               boards[move][CASTLING][4] != NoRights   )
15658                 *p++ = boards[move][CASTLING][4] + AAA;
15659      } else {
15660
15661         /* [HGM] write true castling rights */
15662         if( nrCastlingRights == 6 ) {
15663             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15664                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15665             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15666                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15667             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15668                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15669             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15670                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15671         }
15672      }
15673      if (q == p) *p++ = '-'; /* No castling rights */
15674      *p++ = ' ';
15675   }
15676
15677   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15678      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15679     /* En passant target square */
15680     if (move > backwardMostMove) {
15681         fromX = moveList[move - 1][0] - AAA;
15682         fromY = moveList[move - 1][1] - ONE;
15683         toX = moveList[move - 1][2] - AAA;
15684         toY = moveList[move - 1][3] - ONE;
15685         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15686             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15687             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15688             fromX == toX) {
15689             /* 2-square pawn move just happened */
15690             *p++ = toX + AAA;
15691             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15692         } else {
15693             *p++ = '-';
15694         }
15695     } else if(move == backwardMostMove) {
15696         // [HGM] perhaps we should always do it like this, and forget the above?
15697         if((signed char)boards[move][EP_STATUS] >= 0) {
15698             *p++ = boards[move][EP_STATUS] + AAA;
15699             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15700         } else {
15701             *p++ = '-';
15702         }
15703     } else {
15704         *p++ = '-';
15705     }
15706     *p++ = ' ';
15707   }
15708   }
15709
15710     /* [HGM] find reversible plies */
15711     {   int i = 0, j=move;
15712
15713         if (appData.debugMode) { int k;
15714             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15715             for(k=backwardMostMove; k<=forwardMostMove; k++)
15716                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15717
15718         }
15719
15720         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15721         if( j == backwardMostMove ) i += initialRulePlies;
15722         sprintf(p, "%d ", i);
15723         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15724     }
15725     /* Fullmove number */
15726     sprintf(p, "%d", (move / 2) + 1);
15727
15728     return StrSave(buf);
15729 }
15730
15731 Boolean
15732 ParseFEN(board, blackPlaysFirst, fen)
15733     Board board;
15734      int *blackPlaysFirst;
15735      char *fen;
15736 {
15737     int i, j;
15738     char *p, c;
15739     int emptycount;
15740     ChessSquare piece;
15741
15742     p = fen;
15743
15744     /* [HGM] by default clear Crazyhouse holdings, if present */
15745     if(gameInfo.holdingsWidth) {
15746        for(i=0; i<BOARD_HEIGHT; i++) {
15747            board[i][0]             = EmptySquare; /* black holdings */
15748            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15749            board[i][1]             = (ChessSquare) 0; /* black counts */
15750            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15751        }
15752     }
15753
15754     /* Piece placement data */
15755     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15756         j = 0;
15757         for (;;) {
15758             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15759                 if (*p == '/') p++;
15760                 emptycount = gameInfo.boardWidth - j;
15761                 while (emptycount--)
15762                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15763                 break;
15764 #if(BOARD_FILES >= 10)
15765             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15766                 p++; emptycount=10;
15767                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15768                 while (emptycount--)
15769                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15770 #endif
15771             } else if (isdigit(*p)) {
15772                 emptycount = *p++ - '0';
15773                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15774                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15775                 while (emptycount--)
15776                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15777             } else if (*p == '+' || isalpha(*p)) {
15778                 if (j >= gameInfo.boardWidth) return FALSE;
15779                 if(*p=='+') {
15780                     piece = CharToPiece(*++p);
15781                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15782                     piece = (ChessSquare) (PROMOTED piece ); p++;
15783                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15784                 } else piece = CharToPiece(*p++);
15785
15786                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15787                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15788                     piece = (ChessSquare) (PROMOTED piece);
15789                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15790                     p++;
15791                 }
15792                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15793             } else {
15794                 return FALSE;
15795             }
15796         }
15797     }
15798     while (*p == '/' || *p == ' ') p++;
15799
15800     /* [HGM] look for Crazyhouse holdings here */
15801     while(*p==' ') p++;
15802     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15803         if(*p == '[') p++;
15804         if(*p == '-' ) p++; /* empty holdings */ else {
15805             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15806             /* if we would allow FEN reading to set board size, we would   */
15807             /* have to add holdings and shift the board read so far here   */
15808             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15809                 p++;
15810                 if((int) piece >= (int) BlackPawn ) {
15811                     i = (int)piece - (int)BlackPawn;
15812                     i = PieceToNumber((ChessSquare)i);
15813                     if( i >= gameInfo.holdingsSize ) return FALSE;
15814                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15815                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15816                 } else {
15817                     i = (int)piece - (int)WhitePawn;
15818                     i = PieceToNumber((ChessSquare)i);
15819                     if( i >= gameInfo.holdingsSize ) return FALSE;
15820                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15821                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15822                 }
15823             }
15824         }
15825         if(*p == ']') p++;
15826     }
15827
15828     while(*p == ' ') p++;
15829
15830     /* Active color */
15831     c = *p++;
15832     if(appData.colorNickNames) {
15833       if( c == appData.colorNickNames[0] ) c = 'w'; else
15834       if( c == appData.colorNickNames[1] ) c = 'b';
15835     }
15836     switch (c) {
15837       case 'w':
15838         *blackPlaysFirst = FALSE;
15839         break;
15840       case 'b':
15841         *blackPlaysFirst = TRUE;
15842         break;
15843       default:
15844         return FALSE;
15845     }
15846
15847     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15848     /* return the extra info in global variiables             */
15849
15850     /* set defaults in case FEN is incomplete */
15851     board[EP_STATUS] = EP_UNKNOWN;
15852     for(i=0; i<nrCastlingRights; i++ ) {
15853         board[CASTLING][i] =
15854             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15855     }   /* assume possible unless obviously impossible */
15856     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15857     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15858     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15859                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15860     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15861     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15862     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15863                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15864     FENrulePlies = 0;
15865
15866     while(*p==' ') p++;
15867     if(nrCastlingRights) {
15868       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15869           /* castling indicator present, so default becomes no castlings */
15870           for(i=0; i<nrCastlingRights; i++ ) {
15871                  board[CASTLING][i] = NoRights;
15872           }
15873       }
15874       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15875              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15876              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15877              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15878         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15879
15880         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15881             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15882             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15883         }
15884         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15885             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15886         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15887                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15888         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15889                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15890         switch(c) {
15891           case'K':
15892               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15893               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15894               board[CASTLING][2] = whiteKingFile;
15895               break;
15896           case'Q':
15897               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15898               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15899               board[CASTLING][2] = whiteKingFile;
15900               break;
15901           case'k':
15902               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15903               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15904               board[CASTLING][5] = blackKingFile;
15905               break;
15906           case'q':
15907               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15908               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15909               board[CASTLING][5] = blackKingFile;
15910           case '-':
15911               break;
15912           default: /* FRC castlings */
15913               if(c >= 'a') { /* black rights */
15914                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15915                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15916                   if(i == BOARD_RGHT) break;
15917                   board[CASTLING][5] = i;
15918                   c -= AAA;
15919                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15920                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15921                   if(c > i)
15922                       board[CASTLING][3] = c;
15923                   else
15924                       board[CASTLING][4] = c;
15925               } else { /* white rights */
15926                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15927                     if(board[0][i] == WhiteKing) break;
15928                   if(i == BOARD_RGHT) break;
15929                   board[CASTLING][2] = i;
15930                   c -= AAA - 'a' + 'A';
15931                   if(board[0][c] >= WhiteKing) break;
15932                   if(c > i)
15933                       board[CASTLING][0] = c;
15934                   else
15935                       board[CASTLING][1] = c;
15936               }
15937         }
15938       }
15939       for(i=0; i<nrCastlingRights; i++)
15940         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15941     if (appData.debugMode) {
15942         fprintf(debugFP, "FEN castling rights:");
15943         for(i=0; i<nrCastlingRights; i++)
15944         fprintf(debugFP, " %d", board[CASTLING][i]);
15945         fprintf(debugFP, "\n");
15946     }
15947
15948       while(*p==' ') p++;
15949     }
15950
15951     /* read e.p. field in games that know e.p. capture */
15952     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15953        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15954       if(*p=='-') {
15955         p++; board[EP_STATUS] = EP_NONE;
15956       } else {
15957          char c = *p++ - AAA;
15958
15959          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15960          if(*p >= '0' && *p <='9') p++;
15961          board[EP_STATUS] = c;
15962       }
15963     }
15964
15965
15966     if(sscanf(p, "%d", &i) == 1) {
15967         FENrulePlies = i; /* 50-move ply counter */
15968         /* (The move number is still ignored)    */
15969     }
15970
15971     return TRUE;
15972 }
15973
15974 void
15975 EditPositionPasteFEN(char *fen)
15976 {
15977   if (fen != NULL) {
15978     Board initial_position;
15979
15980     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15981       DisplayError(_("Bad FEN position in clipboard"), 0);
15982       return ;
15983     } else {
15984       int savedBlackPlaysFirst = blackPlaysFirst;
15985       EditPositionEvent();
15986       blackPlaysFirst = savedBlackPlaysFirst;
15987       CopyBoard(boards[0], initial_position);
15988       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15989       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15990       DisplayBothClocks();
15991       DrawPosition(FALSE, boards[currentMove]);
15992     }
15993   }
15994 }
15995
15996 static char cseq[12] = "\\   ";
15997
15998 Boolean set_cont_sequence(char *new_seq)
15999 {
16000     int len;
16001     Boolean ret;
16002
16003     // handle bad attempts to set the sequence
16004         if (!new_seq)
16005                 return 0; // acceptable error - no debug
16006
16007     len = strlen(new_seq);
16008     ret = (len > 0) && (len < sizeof(cseq));
16009     if (ret)
16010       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16011     else if (appData.debugMode)
16012       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16013     return ret;
16014 }
16015
16016 /*
16017     reformat a source message so words don't cross the width boundary.  internal
16018     newlines are not removed.  returns the wrapped size (no null character unless
16019     included in source message).  If dest is NULL, only calculate the size required
16020     for the dest buffer.  lp argument indicats line position upon entry, and it's
16021     passed back upon exit.
16022 */
16023 int wrap(char *dest, char *src, int count, int width, int *lp)
16024 {
16025     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16026
16027     cseq_len = strlen(cseq);
16028     old_line = line = *lp;
16029     ansi = len = clen = 0;
16030
16031     for (i=0; i < count; i++)
16032     {
16033         if (src[i] == '\033')
16034             ansi = 1;
16035
16036         // if we hit the width, back up
16037         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16038         {
16039             // store i & len in case the word is too long
16040             old_i = i, old_len = len;
16041
16042             // find the end of the last word
16043             while (i && src[i] != ' ' && src[i] != '\n')
16044             {
16045                 i--;
16046                 len--;
16047             }
16048
16049             // word too long?  restore i & len before splitting it
16050             if ((old_i-i+clen) >= width)
16051             {
16052                 i = old_i;
16053                 len = old_len;
16054             }
16055
16056             // extra space?
16057             if (i && src[i-1] == ' ')
16058                 len--;
16059
16060             if (src[i] != ' ' && src[i] != '\n')
16061             {
16062                 i--;
16063                 if (len)
16064                     len--;
16065             }
16066
16067             // now append the newline and continuation sequence
16068             if (dest)
16069                 dest[len] = '\n';
16070             len++;
16071             if (dest)
16072                 strncpy(dest+len, cseq, cseq_len);
16073             len += cseq_len;
16074             line = cseq_len;
16075             clen = cseq_len;
16076             continue;
16077         }
16078
16079         if (dest)
16080             dest[len] = src[i];
16081         len++;
16082         if (!ansi)
16083             line++;
16084         if (src[i] == '\n')
16085             line = 0;
16086         if (src[i] == 'm')
16087             ansi = 0;
16088     }
16089     if (dest && appData.debugMode)
16090     {
16091         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16092             count, width, line, len, *lp);
16093         show_bytes(debugFP, src, count);
16094         fprintf(debugFP, "\ndest: ");
16095         show_bytes(debugFP, dest, len);
16096         fprintf(debugFP, "\n");
16097     }
16098     *lp = dest ? line : old_line;
16099
16100     return len;
16101 }
16102
16103 // [HGM] vari: routines for shelving variations
16104
16105 void
16106 PushInner(int firstMove, int lastMove)
16107 {
16108         int i, j, nrMoves = lastMove - firstMove;
16109
16110         // push current tail of game on stack
16111         savedResult[storedGames] = gameInfo.result;
16112         savedDetails[storedGames] = gameInfo.resultDetails;
16113         gameInfo.resultDetails = NULL;
16114         savedFirst[storedGames] = firstMove;
16115         savedLast [storedGames] = lastMove;
16116         savedFramePtr[storedGames] = framePtr;
16117         framePtr -= nrMoves; // reserve space for the boards
16118         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16119             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16120             for(j=0; j<MOVE_LEN; j++)
16121                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16122             for(j=0; j<2*MOVE_LEN; j++)
16123                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16124             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16125             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16126             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16127             pvInfoList[firstMove+i-1].depth = 0;
16128             commentList[framePtr+i] = commentList[firstMove+i];
16129             commentList[firstMove+i] = NULL;
16130         }
16131
16132         storedGames++;
16133         forwardMostMove = firstMove; // truncate game so we can start variation
16134 }
16135
16136 void
16137 PushTail(int firstMove, int lastMove)
16138 {
16139         if(appData.icsActive) { // only in local mode
16140                 forwardMostMove = currentMove; // mimic old ICS behavior
16141                 return;
16142         }
16143         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16144
16145         PushInner(firstMove, lastMove);
16146         if(storedGames == 1) GreyRevert(FALSE);
16147 }
16148
16149 void
16150 PopInner(Boolean annotate)
16151 {
16152         int i, j, nrMoves;
16153         char buf[8000], moveBuf[20];
16154
16155         storedGames--;
16156         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16157         nrMoves = savedLast[storedGames] - currentMove;
16158         if(annotate) {
16159                 int cnt = 10;
16160                 if(!WhiteOnMove(currentMove))
16161                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16162                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16163                 for(i=currentMove; i<forwardMostMove; i++) {
16164                         if(WhiteOnMove(i))
16165                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16166                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16167                         strcat(buf, moveBuf);
16168                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16169                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16170                 }
16171                 strcat(buf, ")");
16172         }
16173         for(i=1; i<=nrMoves; i++) { // copy last variation back
16174             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16175             for(j=0; j<MOVE_LEN; j++)
16176                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16177             for(j=0; j<2*MOVE_LEN; j++)
16178                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16179             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16180             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16181             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16182             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16183             commentList[currentMove+i] = commentList[framePtr+i];
16184             commentList[framePtr+i] = NULL;
16185         }
16186         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16187         framePtr = savedFramePtr[storedGames];
16188         gameInfo.result = savedResult[storedGames];
16189         if(gameInfo.resultDetails != NULL) {
16190             free(gameInfo.resultDetails);
16191       }
16192         gameInfo.resultDetails = savedDetails[storedGames];
16193         forwardMostMove = currentMove + nrMoves;
16194 }
16195
16196 Boolean
16197 PopTail(Boolean annotate)
16198 {
16199         if(appData.icsActive) return FALSE; // only in local mode
16200         if(!storedGames) return FALSE; // sanity
16201         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16202
16203         PopInner(annotate);
16204
16205         if(storedGames == 0) GreyRevert(TRUE);
16206         return TRUE;
16207 }
16208
16209 void
16210 CleanupTail()
16211 {       // remove all shelved variations
16212         int i;
16213         for(i=0; i<storedGames; i++) {
16214             if(savedDetails[i])
16215                 free(savedDetails[i]);
16216             savedDetails[i] = NULL;
16217         }
16218         for(i=framePtr; i<MAX_MOVES; i++) {
16219                 if(commentList[i]) free(commentList[i]);
16220                 commentList[i] = NULL;
16221         }
16222         framePtr = MAX_MOVES-1;
16223         storedGames = 0;
16224 }
16225
16226 void
16227 LoadVariation(int index, char *text)
16228 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16229         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16230         int level = 0, move;
16231
16232         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16233         // first find outermost bracketing variation
16234         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16235             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16236                 if(*p == '{') wait = '}'; else
16237                 if(*p == '[') wait = ']'; else
16238                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16239                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16240             }
16241             if(*p == wait) wait = NULLCHAR; // closing ]} found
16242             p++;
16243         }
16244         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16245         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16246         end[1] = NULLCHAR; // clip off comment beyond variation
16247         ToNrEvent(currentMove-1);
16248         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16249         // kludge: use ParsePV() to append variation to game
16250         move = currentMove;
16251         ParsePV(start, TRUE);
16252         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16253         ClearPremoveHighlights();
16254         CommentPopDown();
16255         ToNrEvent(currentMove+1);
16256 }
16257