Plug memory leak, filenames relative to installDir
[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;
866
867 void
868 Load(ChessProgramState *cps, int i)
869 {
870     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
871     if(engineLine[0]) { // an engine was selected from the combo box
872         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
873         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
874         ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
875         ParseArgsFromString(buf);
876         SwapEngines(i);
877         ReplaceEngine(cps, i);
878         return;
879     }
880     p = engineName;
881     while(q = strchr(p, SLASH)) p = q+1;
882     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
883     if(engineDir[0] != NULLCHAR)
884         appData.directory[i] = engineDir;
885     else if(p != engineName) { // derive directory from engine path, when not given
886         p[-1] = 0;
887         appData.directory[i] = strdup(engineName);
888         p[-1] = SLASH;
889     } else appData.directory[i] = ".";
890     if(params[0]) {
891         snprintf(command, MSG_SIZ, "%s %s", p, params);
892         p = command;
893     }
894     appData.chessProgram[i] = strdup(p);
895     appData.isUCI[i] = isUCI;
896     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
897     appData.hasOwnBookUCI[i] = hasBook;
898     if(addToList) {
899         int len;
900         q = firstChessProgramNames;
901         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
902         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i], 
903                         v1 ? " -firstProtocolVersion 1" : "",
904                         hasBook ? "" : " -fNoOwnBookUCI",
905                         isUCI ? " -fUCI" : "",
906                         storeVariant ? " -variant " : "",
907                         storeVariant ? VariantName(gameInfo.variant) : "");
908         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
909         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
910         if(q)   free(q);
911     }
912     ReplaceEngine(cps, i);
913 }
914
915 void
916 InitBackEnd1()
917 {
918     int matched, min, sec;
919
920     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
921     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
922
923     GetTimeMark(&programStartTime);
924     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
925     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
926
927     ClearProgramStats();
928     programStats.ok_to_send = 1;
929     programStats.seen_stat = 0;
930
931     /*
932      * Initialize game list
933      */
934     ListNew(&gameList);
935
936
937     /*
938      * Internet chess server status
939      */
940     if (appData.icsActive) {
941         appData.matchMode = FALSE;
942         appData.matchGames = 0;
943 #if ZIPPY
944         appData.noChessProgram = !appData.zippyPlay;
945 #else
946         appData.zippyPlay = FALSE;
947         appData.zippyTalk = FALSE;
948         appData.noChessProgram = TRUE;
949 #endif
950         if (*appData.icsHelper != NULLCHAR) {
951             appData.useTelnet = TRUE;
952             appData.telnetProgram = appData.icsHelper;
953         }
954     } else {
955         appData.zippyTalk = appData.zippyPlay = FALSE;
956     }
957
958     /* [AS] Initialize pv info list [HGM] and game state */
959     {
960         int i, j;
961
962         for( i=0; i<=framePtr; i++ ) {
963             pvInfoList[i].depth = -1;
964             boards[i][EP_STATUS] = EP_NONE;
965             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
966         }
967     }
968
969     /*
970      * Parse timeControl resource
971      */
972     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
973                           appData.movesPerSession)) {
974         char buf[MSG_SIZ];
975         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
976         DisplayFatalError(buf, 0, 2);
977     }
978
979     /*
980      * Parse searchTime resource
981      */
982     if (*appData.searchTime != NULLCHAR) {
983         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
984         if (matched == 1) {
985             searchTime = min * 60;
986         } else if (matched == 2) {
987             searchTime = min * 60 + sec;
988         } else {
989             char buf[MSG_SIZ];
990             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
991             DisplayFatalError(buf, 0, 2);
992         }
993     }
994
995     /* [AS] Adjudication threshold */
996     adjudicateLossThreshold = appData.adjudicateLossThreshold;
997
998     InitEngine(&first, 0);
999     InitEngine(&second, 1);
1000     CommonEngineInit();
1001
1002     if (appData.icsActive) {
1003         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1004     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1005         appData.clockMode = FALSE;
1006         first.sendTime = second.sendTime = 0;
1007     }
1008
1009 #if ZIPPY
1010     /* Override some settings from environment variables, for backward
1011        compatibility.  Unfortunately it's not feasible to have the env
1012        vars just set defaults, at least in xboard.  Ugh.
1013     */
1014     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1015       ZippyInit();
1016     }
1017 #endif
1018
1019     if (!appData.icsActive) {
1020       char buf[MSG_SIZ];
1021       int len;
1022
1023       /* Check for variants that are supported only in ICS mode,
1024          or not at all.  Some that are accepted here nevertheless
1025          have bugs; see comments below.
1026       */
1027       VariantClass variant = StringToVariant(appData.variant);
1028       switch (variant) {
1029       case VariantBughouse:     /* need four players and two boards */
1030       case VariantKriegspiel:   /* need to hide pieces and move details */
1031         /* case VariantFischeRandom: (Fabien: moved below) */
1032         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1033         if( (len > MSG_SIZ) && appData.debugMode )
1034           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1035
1036         DisplayFatalError(buf, 0, 2);
1037         return;
1038
1039       case VariantUnknown:
1040       case VariantLoadable:
1041       case Variant29:
1042       case Variant30:
1043       case Variant31:
1044       case Variant32:
1045       case Variant33:
1046       case Variant34:
1047       case Variant35:
1048       case Variant36:
1049       default:
1050         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1051         if( (len > MSG_SIZ) && appData.debugMode )
1052           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1053
1054         DisplayFatalError(buf, 0, 2);
1055         return;
1056
1057       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1058       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1059       case VariantGothic:     /* [HGM] should work */
1060       case VariantCapablanca: /* [HGM] should work */
1061       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1062       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1063       case VariantKnightmate: /* [HGM] should work */
1064       case VariantCylinder:   /* [HGM] untested */
1065       case VariantFalcon:     /* [HGM] untested */
1066       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1067                                  offboard interposition not understood */
1068       case VariantNormal:     /* definitely works! */
1069       case VariantWildCastle: /* pieces not automatically shuffled */
1070       case VariantNoCastle:   /* pieces not automatically shuffled */
1071       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1072       case VariantLosers:     /* should work except for win condition,
1073                                  and doesn't know captures are mandatory */
1074       case VariantSuicide:    /* should work except for win condition,
1075                                  and doesn't know captures are mandatory */
1076       case VariantGiveaway:   /* should work except for win condition,
1077                                  and doesn't know captures are mandatory */
1078       case VariantTwoKings:   /* should work */
1079       case VariantAtomic:     /* should work except for win condition */
1080       case Variant3Check:     /* should work except for win condition */
1081       case VariantShatranj:   /* should work except for all win conditions */
1082       case VariantMakruk:     /* should work except for daw countdown */
1083       case VariantBerolina:   /* might work if TestLegality is off */
1084       case VariantCapaRandom: /* should work */
1085       case VariantJanus:      /* should work */
1086       case VariantSuper:      /* experimental */
1087       case VariantGreat:      /* experimental, requires legality testing to be off */
1088       case VariantSChess:     /* S-Chess, should work */
1089       case VariantSpartan:    /* should work */
1090         break;
1091       }
1092     }
1093
1094 }
1095
1096 int NextIntegerFromString( char ** str, long * value )
1097 {
1098     int result = -1;
1099     char * s = *str;
1100
1101     while( *s == ' ' || *s == '\t' ) {
1102         s++;
1103     }
1104
1105     *value = 0;
1106
1107     if( *s >= '0' && *s <= '9' ) {
1108         while( *s >= '0' && *s <= '9' ) {
1109             *value = *value * 10 + (*s - '0');
1110             s++;
1111         }
1112
1113         result = 0;
1114     }
1115
1116     *str = s;
1117
1118     return result;
1119 }
1120
1121 int NextTimeControlFromString( char ** str, long * value )
1122 {
1123     long temp;
1124     int result = NextIntegerFromString( str, &temp );
1125
1126     if( result == 0 ) {
1127         *value = temp * 60; /* Minutes */
1128         if( **str == ':' ) {
1129             (*str)++;
1130             result = NextIntegerFromString( str, &temp );
1131             *value += temp; /* Seconds */
1132         }
1133     }
1134
1135     return result;
1136 }
1137
1138 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1139 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1140     int result = -1, type = 0; long temp, temp2;
1141
1142     if(**str != ':') return -1; // old params remain in force!
1143     (*str)++;
1144     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1145     if( NextIntegerFromString( str, &temp ) ) return -1;
1146     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1147
1148     if(**str != '/') {
1149         /* time only: incremental or sudden-death time control */
1150         if(**str == '+') { /* increment follows; read it */
1151             (*str)++;
1152             if(**str == '!') type = *(*str)++; // Bronstein TC
1153             if(result = NextIntegerFromString( str, &temp2)) return -1;
1154             *inc = temp2 * 1000;
1155             if(**str == '.') { // read fraction of increment
1156                 char *start = ++(*str);
1157                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1158                 temp2 *= 1000;
1159                 while(start++ < *str) temp2 /= 10;
1160                 *inc += temp2;
1161             }
1162         } else *inc = 0;
1163         *moves = 0; *tc = temp * 1000; *incType = type;
1164         return 0;
1165     }
1166
1167     (*str)++; /* classical time control */
1168     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1169
1170     if(result == 0) {
1171         *moves = temp;
1172         *tc    = temp2 * 1000;
1173         *inc   = 0;
1174         *incType = type;
1175     }
1176     return result;
1177 }
1178
1179 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1180 {   /* [HGM] get time to add from the multi-session time-control string */
1181     int incType, moves=1; /* kludge to force reading of first session */
1182     long time, increment;
1183     char *s = tcString;
1184
1185     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1186     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1187     do {
1188         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1189         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1190         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1191         if(movenr == -1) return time;    /* last move before new session     */
1192         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1193         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1194         if(!moves) return increment;     /* current session is incremental   */
1195         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1196     } while(movenr >= -1);               /* try again for next session       */
1197
1198     return 0; // no new time quota on this move
1199 }
1200
1201 int
1202 ParseTimeControl(tc, ti, mps)
1203      char *tc;
1204      float ti;
1205      int mps;
1206 {
1207   long tc1;
1208   long tc2;
1209   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1210   int min, sec=0;
1211
1212   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1213   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1214       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1215   if(ti > 0) {
1216
1217     if(mps)
1218       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1219     else 
1220       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1221   } else {
1222     if(mps)
1223       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1224     else 
1225       snprintf(buf, MSG_SIZ, ":%s", mytc);
1226   }
1227   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1228   
1229   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1230     return FALSE;
1231   }
1232
1233   if( *tc == '/' ) {
1234     /* Parse second time control */
1235     tc++;
1236
1237     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1238       return FALSE;
1239     }
1240
1241     if( tc2 == 0 ) {
1242       return FALSE;
1243     }
1244
1245     timeControl_2 = tc2 * 1000;
1246   }
1247   else {
1248     timeControl_2 = 0;
1249   }
1250
1251   if( tc1 == 0 ) {
1252     return FALSE;
1253   }
1254
1255   timeControl = tc1 * 1000;
1256
1257   if (ti >= 0) {
1258     timeIncrement = ti * 1000;  /* convert to ms */
1259     movesPerSession = 0;
1260   } else {
1261     timeIncrement = 0;
1262     movesPerSession = mps;
1263   }
1264   return TRUE;
1265 }
1266
1267 void
1268 InitBackEnd2()
1269 {
1270     if (appData.debugMode) {
1271         fprintf(debugFP, "%s\n", programVersion);
1272     }
1273
1274     set_cont_sequence(appData.wrapContSeq);
1275     if (appData.matchGames > 0) {
1276         appData.matchMode = TRUE;
1277     } else if (appData.matchMode) {
1278         appData.matchGames = 1;
1279     }
1280     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1281         appData.matchGames = appData.sameColorGames;
1282     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1283         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1284         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1285     }
1286     Reset(TRUE, FALSE);
1287     if (appData.noChessProgram || first.protocolVersion == 1) {
1288       InitBackEnd3();
1289     } else {
1290       /* kludge: allow timeout for initial "feature" commands */
1291       FreezeUI();
1292       DisplayMessage("", _("Starting chess program"));
1293       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1294     }
1295 }
1296
1297 int
1298 CalculateIndex(int index, int gameNr)
1299 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1300     int res;
1301     if(index > 0) return index; // fixed nmber
1302     if(index == 0) return 1;
1303     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1304     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1305     return res;
1306 }
1307
1308 int
1309 LoadGameOrPosition(int gameNr)
1310 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1311     if (*appData.loadGameFile != NULLCHAR) {
1312         if (!LoadGameFromFile(appData.loadGameFile,
1313                 CalculateIndex(appData.loadGameIndex, gameNr),
1314                               appData.loadGameFile, FALSE)) {
1315             DisplayFatalError(_("Bad game file"), 0, 1);
1316             return 0;
1317         }
1318     } else if (*appData.loadPositionFile != NULLCHAR) {
1319         if (!LoadPositionFromFile(appData.loadPositionFile,
1320                 CalculateIndex(appData.loadPositionIndex, gameNr),
1321                                   appData.loadPositionFile)) {
1322             DisplayFatalError(_("Bad position file"), 0, 1);
1323             return 0;
1324         }
1325     }
1326     return 1;
1327 }
1328
1329 void
1330 ReserveGame(int gameNr, char resChar)
1331 {
1332     FILE *tf = fopen(appData.tourneyFile, "r+");
1333     char *p, *q, c, buf[MSG_SIZ];
1334     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1335     safeStrCpy(buf, lastMsg, MSG_SIZ);
1336     DisplayMessage(_("Pick new game"), "");
1337     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1338     ParseArgsFromFile(tf);
1339     p = q = appData.results;
1340     if(appData.debugMode) {
1341       char *r = appData.participants;
1342       fprintf(debugFP, "results = '%s'\n", p);
1343       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1344       fprintf(debugFP, "\n");
1345     }
1346     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1347     nextGame = q - p;
1348     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1349     safeStrCpy(q, p, strlen(p) + 2);
1350     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1351     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1352     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1353         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1354         q[nextGame] = '*';
1355     }
1356     fseek(tf, -(strlen(p)+4), SEEK_END);
1357     c = fgetc(tf);
1358     if(c != '"') // depending on DOS or Unix line endings we can be one off
1359          fseek(tf, -(strlen(p)+2), SEEK_END);
1360     else fseek(tf, -(strlen(p)+3), SEEK_END);
1361     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1362     DisplayMessage(buf, "");
1363     free(p); appData.results = q;
1364     if(nextGame <= appData.matchGames && resChar != ' ' &&
1365        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1366         UnloadEngine(&first);  // next game belongs to other pairing;
1367         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1368     }
1369 }
1370
1371 void
1372 MatchEvent(int mode)
1373 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1374         int dummy;
1375         if(matchMode) { // already in match mode: switch it off
1376             abortMatch = TRUE;
1377             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1378             ModeHighlight(); // kludgey way to remove checkmark...
1379             return;
1380         }
1381 //      if(gameMode != BeginningOfGame) {
1382 //          DisplayError(_("You can only start a match from the initial position."), 0);
1383 //          return;
1384 //      }
1385         abortMatch = FALSE;
1386         appData.matchGames = appData.defaultMatchGames;
1387         /* Set up machine vs. machine match */
1388         nextGame = 0;
1389         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1390         if(appData.tourneyFile[0]) {
1391             ReserveGame(-1, 0);
1392             if(nextGame > appData.matchGames) {
1393                 char buf[MSG_SIZ];
1394                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1395                 DisplayError(buf, 0);
1396                 appData.tourneyFile[0] = 0;
1397                 return;
1398             }
1399         } else
1400         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1401             DisplayFatalError(_("Can't have a match with no chess programs"),
1402                               0, 2);
1403             return;
1404         }
1405         matchMode = mode;
1406         matchGame = roundNr = 1;
1407         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1408         NextMatchGame();
1409 }
1410
1411 void
1412 InitBackEnd3 P((void))
1413 {
1414     GameMode initialMode;
1415     char buf[MSG_SIZ];
1416     int err, len;
1417
1418     InitChessProgram(&first, startedFromSetupPosition);
1419
1420     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1421         free(programVersion);
1422         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1423         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1424     }
1425
1426     if (appData.icsActive) {
1427 #ifdef WIN32
1428         /* [DM] Make a console window if needed [HGM] merged ifs */
1429         ConsoleCreate();
1430 #endif
1431         err = establish();
1432         if (err != 0)
1433           {
1434             if (*appData.icsCommPort != NULLCHAR)
1435               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1436                              appData.icsCommPort);
1437             else
1438               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1439                         appData.icsHost, appData.icsPort);
1440
1441             if( (len > MSG_SIZ) && appData.debugMode )
1442               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1443
1444             DisplayFatalError(buf, err, 1);
1445             return;
1446         }
1447         SetICSMode();
1448         telnetISR =
1449           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1450         fromUserISR =
1451           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1452         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1453             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454     } else if (appData.noChessProgram) {
1455         SetNCPMode();
1456     } else {
1457         SetGNUMode();
1458     }
1459
1460     if (*appData.cmailGameName != NULLCHAR) {
1461         SetCmailMode();
1462         OpenLoopback(&cmailPR);
1463         cmailISR =
1464           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1465     }
1466
1467     ThawUI();
1468     DisplayMessage("", "");
1469     if (StrCaseCmp(appData.initialMode, "") == 0) {
1470       initialMode = BeginningOfGame;
1471       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1472         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1473         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1474         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1475         ModeHighlight();
1476       }
1477     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1478       initialMode = TwoMachinesPlay;
1479     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1480       initialMode = AnalyzeFile;
1481     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1482       initialMode = AnalyzeMode;
1483     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1484       initialMode = MachinePlaysWhite;
1485     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1486       initialMode = MachinePlaysBlack;
1487     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1488       initialMode = EditGame;
1489     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1490       initialMode = EditPosition;
1491     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1492       initialMode = Training;
1493     } else {
1494       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1495       if( (len > MSG_SIZ) && appData.debugMode )
1496         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1497
1498       DisplayFatalError(buf, 0, 2);
1499       return;
1500     }
1501
1502     if (appData.matchMode) {
1503         if(appData.tourneyFile[0]) { // start tourney from command line
1504             FILE *f;
1505             if(f = fopen(appData.tourneyFile, "r")) {
1506                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1507                 fclose(f);
1508             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1509         }
1510         MatchEvent(TRUE);
1511     } else if (*appData.cmailGameName != NULLCHAR) {
1512         /* Set up cmail mode */
1513         ReloadCmailMsgEvent(TRUE);
1514     } else {
1515         /* Set up other modes */
1516         if (initialMode == AnalyzeFile) {
1517           if (*appData.loadGameFile == NULLCHAR) {
1518             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1519             return;
1520           }
1521         }
1522         if (*appData.loadGameFile != NULLCHAR) {
1523             (void) LoadGameFromFile(appData.loadGameFile,
1524                                     appData.loadGameIndex,
1525                                     appData.loadGameFile, TRUE);
1526         } else if (*appData.loadPositionFile != NULLCHAR) {
1527             (void) LoadPositionFromFile(appData.loadPositionFile,
1528                                         appData.loadPositionIndex,
1529                                         appData.loadPositionFile);
1530             /* [HGM] try to make self-starting even after FEN load */
1531             /* to allow automatic setup of fairy variants with wtm */
1532             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1533                 gameMode = BeginningOfGame;
1534                 setboardSpoiledMachineBlack = 1;
1535             }
1536             /* [HGM] loadPos: make that every new game uses the setup */
1537             /* from file as long as we do not switch variant          */
1538             if(!blackPlaysFirst) {
1539                 startedFromPositionFile = TRUE;
1540                 CopyBoard(filePosition, boards[0]);
1541             }
1542         }
1543         if (initialMode == AnalyzeMode) {
1544           if (appData.noChessProgram) {
1545             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1546             return;
1547           }
1548           if (appData.icsActive) {
1549             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1550             return;
1551           }
1552           AnalyzeModeEvent();
1553         } else if (initialMode == AnalyzeFile) {
1554           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1555           ShowThinkingEvent();
1556           AnalyzeFileEvent();
1557           AnalysisPeriodicEvent(1);
1558         } else if (initialMode == MachinePlaysWhite) {
1559           if (appData.noChessProgram) {
1560             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1561                               0, 2);
1562             return;
1563           }
1564           if (appData.icsActive) {
1565             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1566                               0, 2);
1567             return;
1568           }
1569           MachineWhiteEvent();
1570         } else if (initialMode == MachinePlaysBlack) {
1571           if (appData.noChessProgram) {
1572             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1573                               0, 2);
1574             return;
1575           }
1576           if (appData.icsActive) {
1577             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1578                               0, 2);
1579             return;
1580           }
1581           MachineBlackEvent();
1582         } else if (initialMode == TwoMachinesPlay) {
1583           if (appData.noChessProgram) {
1584             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1585                               0, 2);
1586             return;
1587           }
1588           if (appData.icsActive) {
1589             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1590                               0, 2);
1591             return;
1592           }
1593           TwoMachinesEvent();
1594         } else if (initialMode == EditGame) {
1595           EditGameEvent();
1596         } else if (initialMode == EditPosition) {
1597           EditPositionEvent();
1598         } else if (initialMode == Training) {
1599           if (*appData.loadGameFile == NULLCHAR) {
1600             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1601             return;
1602           }
1603           TrainingEvent();
1604         }
1605     }
1606 }
1607
1608 /*
1609  * Establish will establish a contact to a remote host.port.
1610  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1611  *  used to talk to the host.
1612  * Returns 0 if okay, error code if not.
1613  */
1614 int
1615 establish()
1616 {
1617     char buf[MSG_SIZ];
1618
1619     if (*appData.icsCommPort != NULLCHAR) {
1620         /* Talk to the host through a serial comm port */
1621         return OpenCommPort(appData.icsCommPort, &icsPR);
1622
1623     } else if (*appData.gateway != NULLCHAR) {
1624         if (*appData.remoteShell == NULLCHAR) {
1625             /* Use the rcmd protocol to run telnet program on a gateway host */
1626             snprintf(buf, sizeof(buf), "%s %s %s",
1627                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1628             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1629
1630         } else {
1631             /* Use the rsh program to run telnet program on a gateway host */
1632             if (*appData.remoteUser == NULLCHAR) {
1633                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1634                         appData.gateway, appData.telnetProgram,
1635                         appData.icsHost, appData.icsPort);
1636             } else {
1637                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1638                         appData.remoteShell, appData.gateway,
1639                         appData.remoteUser, appData.telnetProgram,
1640                         appData.icsHost, appData.icsPort);
1641             }
1642             return StartChildProcess(buf, "", &icsPR);
1643
1644         }
1645     } else if (appData.useTelnet) {
1646         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1647
1648     } else {
1649         /* TCP socket interface differs somewhat between
1650            Unix and NT; handle details in the front end.
1651            */
1652         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1653     }
1654 }
1655
1656 void EscapeExpand(char *p, char *q)
1657 {       // [HGM] initstring: routine to shape up string arguments
1658         while(*p++ = *q++) if(p[-1] == '\\')
1659             switch(*q++) {
1660                 case 'n': p[-1] = '\n'; break;
1661                 case 'r': p[-1] = '\r'; break;
1662                 case 't': p[-1] = '\t'; break;
1663                 case '\\': p[-1] = '\\'; break;
1664                 case 0: *p = 0; return;
1665                 default: p[-1] = q[-1]; break;
1666             }
1667 }
1668
1669 void
1670 show_bytes(fp, buf, count)
1671      FILE *fp;
1672      char *buf;
1673      int count;
1674 {
1675     while (count--) {
1676         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1677             fprintf(fp, "\\%03o", *buf & 0xff);
1678         } else {
1679             putc(*buf, fp);
1680         }
1681         buf++;
1682     }
1683     fflush(fp);
1684 }
1685
1686 /* Returns an errno value */
1687 int
1688 OutputMaybeTelnet(pr, message, count, outError)
1689      ProcRef pr;
1690      char *message;
1691      int count;
1692      int *outError;
1693 {
1694     char buf[8192], *p, *q, *buflim;
1695     int left, newcount, outcount;
1696
1697     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1698         *appData.gateway != NULLCHAR) {
1699         if (appData.debugMode) {
1700             fprintf(debugFP, ">ICS: ");
1701             show_bytes(debugFP, message, count);
1702             fprintf(debugFP, "\n");
1703         }
1704         return OutputToProcess(pr, message, count, outError);
1705     }
1706
1707     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1708     p = message;
1709     q = buf;
1710     left = count;
1711     newcount = 0;
1712     while (left) {
1713         if (q >= buflim) {
1714             if (appData.debugMode) {
1715                 fprintf(debugFP, ">ICS: ");
1716                 show_bytes(debugFP, buf, newcount);
1717                 fprintf(debugFP, "\n");
1718             }
1719             outcount = OutputToProcess(pr, buf, newcount, outError);
1720             if (outcount < newcount) return -1; /* to be sure */
1721             q = buf;
1722             newcount = 0;
1723         }
1724         if (*p == '\n') {
1725             *q++ = '\r';
1726             newcount++;
1727         } else if (((unsigned char) *p) == TN_IAC) {
1728             *q++ = (char) TN_IAC;
1729             newcount ++;
1730         }
1731         *q++ = *p++;
1732         newcount++;
1733         left--;
1734     }
1735     if (appData.debugMode) {
1736         fprintf(debugFP, ">ICS: ");
1737         show_bytes(debugFP, buf, newcount);
1738         fprintf(debugFP, "\n");
1739     }
1740     outcount = OutputToProcess(pr, buf, newcount, outError);
1741     if (outcount < newcount) return -1; /* to be sure */
1742     return count;
1743 }
1744
1745 void
1746 read_from_player(isr, closure, message, count, error)
1747      InputSourceRef isr;
1748      VOIDSTAR closure;
1749      char *message;
1750      int count;
1751      int error;
1752 {
1753     int outError, outCount;
1754     static int gotEof = 0;
1755
1756     /* Pass data read from player on to ICS */
1757     if (count > 0) {
1758         gotEof = 0;
1759         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1760         if (outCount < count) {
1761             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1762         }
1763     } else if (count < 0) {
1764         RemoveInputSource(isr);
1765         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1766     } else if (gotEof++ > 0) {
1767         RemoveInputSource(isr);
1768         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1769     }
1770 }
1771
1772 void
1773 KeepAlive()
1774 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1775     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1776     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1777     SendToICS("date\n");
1778     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1779 }
1780
1781 /* added routine for printf style output to ics */
1782 void ics_printf(char *format, ...)
1783 {
1784     char buffer[MSG_SIZ];
1785     va_list args;
1786
1787     va_start(args, format);
1788     vsnprintf(buffer, sizeof(buffer), format, args);
1789     buffer[sizeof(buffer)-1] = '\0';
1790     SendToICS(buffer);
1791     va_end(args);
1792 }
1793
1794 void
1795 SendToICS(s)
1796      char *s;
1797 {
1798     int count, outCount, outError;
1799
1800     if (icsPR == NULL) return;
1801
1802     count = strlen(s);
1803     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1804     if (outCount < count) {
1805         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1806     }
1807 }
1808
1809 /* This is used for sending logon scripts to the ICS. Sending
1810    without a delay causes problems when using timestamp on ICC
1811    (at least on my machine). */
1812 void
1813 SendToICSDelayed(s,msdelay)
1814      char *s;
1815      long msdelay;
1816 {
1817     int count, outCount, outError;
1818
1819     if (icsPR == NULL) return;
1820
1821     count = strlen(s);
1822     if (appData.debugMode) {
1823         fprintf(debugFP, ">ICS: ");
1824         show_bytes(debugFP, s, count);
1825         fprintf(debugFP, "\n");
1826     }
1827     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1828                                       msdelay);
1829     if (outCount < count) {
1830         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1831     }
1832 }
1833
1834
1835 /* Remove all highlighting escape sequences in s
1836    Also deletes any suffix starting with '('
1837    */
1838 char *
1839 StripHighlightAndTitle(s)
1840      char *s;
1841 {
1842     static char retbuf[MSG_SIZ];
1843     char *p = retbuf;
1844
1845     while (*s != NULLCHAR) {
1846         while (*s == '\033') {
1847             while (*s != NULLCHAR && !isalpha(*s)) s++;
1848             if (*s != NULLCHAR) s++;
1849         }
1850         while (*s != NULLCHAR && *s != '\033') {
1851             if (*s == '(' || *s == '[') {
1852                 *p = NULLCHAR;
1853                 return retbuf;
1854             }
1855             *p++ = *s++;
1856         }
1857     }
1858     *p = NULLCHAR;
1859     return retbuf;
1860 }
1861
1862 /* Remove all highlighting escape sequences in s */
1863 char *
1864 StripHighlight(s)
1865      char *s;
1866 {
1867     static char retbuf[MSG_SIZ];
1868     char *p = retbuf;
1869
1870     while (*s != NULLCHAR) {
1871         while (*s == '\033') {
1872             while (*s != NULLCHAR && !isalpha(*s)) s++;
1873             if (*s != NULLCHAR) s++;
1874         }
1875         while (*s != NULLCHAR && *s != '\033') {
1876             *p++ = *s++;
1877         }
1878     }
1879     *p = NULLCHAR;
1880     return retbuf;
1881 }
1882
1883 char *variantNames[] = VARIANT_NAMES;
1884 char *
1885 VariantName(v)
1886      VariantClass v;
1887 {
1888     return variantNames[v];
1889 }
1890
1891
1892 /* Identify a variant from the strings the chess servers use or the
1893    PGN Variant tag names we use. */
1894 VariantClass
1895 StringToVariant(e)
1896      char *e;
1897 {
1898     char *p;
1899     int wnum = -1;
1900     VariantClass v = VariantNormal;
1901     int i, found = FALSE;
1902     char buf[MSG_SIZ];
1903     int len;
1904
1905     if (!e) return v;
1906
1907     /* [HGM] skip over optional board-size prefixes */
1908     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1909         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1910         while( *e++ != '_');
1911     }
1912
1913     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1914         v = VariantNormal;
1915         found = TRUE;
1916     } else
1917     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1918       if (StrCaseStr(e, variantNames[i])) {
1919         v = (VariantClass) i;
1920         found = TRUE;
1921         break;
1922       }
1923     }
1924
1925     if (!found) {
1926       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1927           || StrCaseStr(e, "wild/fr")
1928           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1929         v = VariantFischeRandom;
1930       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1931                  (i = 1, p = StrCaseStr(e, "w"))) {
1932         p += i;
1933         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1934         if (isdigit(*p)) {
1935           wnum = atoi(p);
1936         } else {
1937           wnum = -1;
1938         }
1939         switch (wnum) {
1940         case 0: /* FICS only, actually */
1941         case 1:
1942           /* Castling legal even if K starts on d-file */
1943           v = VariantWildCastle;
1944           break;
1945         case 2:
1946         case 3:
1947         case 4:
1948           /* Castling illegal even if K & R happen to start in
1949              normal positions. */
1950           v = VariantNoCastle;
1951           break;
1952         case 5:
1953         case 7:
1954         case 8:
1955         case 10:
1956         case 11:
1957         case 12:
1958         case 13:
1959         case 14:
1960         case 15:
1961         case 18:
1962         case 19:
1963           /* Castling legal iff K & R start in normal positions */
1964           v = VariantNormal;
1965           break;
1966         case 6:
1967         case 20:
1968         case 21:
1969           /* Special wilds for position setup; unclear what to do here */
1970           v = VariantLoadable;
1971           break;
1972         case 9:
1973           /* Bizarre ICC game */
1974           v = VariantTwoKings;
1975           break;
1976         case 16:
1977           v = VariantKriegspiel;
1978           break;
1979         case 17:
1980           v = VariantLosers;
1981           break;
1982         case 22:
1983           v = VariantFischeRandom;
1984           break;
1985         case 23:
1986           v = VariantCrazyhouse;
1987           break;
1988         case 24:
1989           v = VariantBughouse;
1990           break;
1991         case 25:
1992           v = Variant3Check;
1993           break;
1994         case 26:
1995           /* Not quite the same as FICS suicide! */
1996           v = VariantGiveaway;
1997           break;
1998         case 27:
1999           v = VariantAtomic;
2000           break;
2001         case 28:
2002           v = VariantShatranj;
2003           break;
2004
2005         /* Temporary names for future ICC types.  The name *will* change in
2006            the next xboard/WinBoard release after ICC defines it. */
2007         case 29:
2008           v = Variant29;
2009           break;
2010         case 30:
2011           v = Variant30;
2012           break;
2013         case 31:
2014           v = Variant31;
2015           break;
2016         case 32:
2017           v = Variant32;
2018           break;
2019         case 33:
2020           v = Variant33;
2021           break;
2022         case 34:
2023           v = Variant34;
2024           break;
2025         case 35:
2026           v = Variant35;
2027           break;
2028         case 36:
2029           v = Variant36;
2030           break;
2031         case 37:
2032           v = VariantShogi;
2033           break;
2034         case 38:
2035           v = VariantXiangqi;
2036           break;
2037         case 39:
2038           v = VariantCourier;
2039           break;
2040         case 40:
2041           v = VariantGothic;
2042           break;
2043         case 41:
2044           v = VariantCapablanca;
2045           break;
2046         case 42:
2047           v = VariantKnightmate;
2048           break;
2049         case 43:
2050           v = VariantFairy;
2051           break;
2052         case 44:
2053           v = VariantCylinder;
2054           break;
2055         case 45:
2056           v = VariantFalcon;
2057           break;
2058         case 46:
2059           v = VariantCapaRandom;
2060           break;
2061         case 47:
2062           v = VariantBerolina;
2063           break;
2064         case 48:
2065           v = VariantJanus;
2066           break;
2067         case 49:
2068           v = VariantSuper;
2069           break;
2070         case 50:
2071           v = VariantGreat;
2072           break;
2073         case -1:
2074           /* Found "wild" or "w" in the string but no number;
2075              must assume it's normal chess. */
2076           v = VariantNormal;
2077           break;
2078         default:
2079           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2080           if( (len > MSG_SIZ) && appData.debugMode )
2081             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2082
2083           DisplayError(buf, 0);
2084           v = VariantUnknown;
2085           break;
2086         }
2087       }
2088     }
2089     if (appData.debugMode) {
2090       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2091               e, wnum, VariantName(v));
2092     }
2093     return v;
2094 }
2095
2096 static int leftover_start = 0, leftover_len = 0;
2097 char star_match[STAR_MATCH_N][MSG_SIZ];
2098
2099 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2100    advance *index beyond it, and set leftover_start to the new value of
2101    *index; else return FALSE.  If pattern contains the character '*', it
2102    matches any sequence of characters not containing '\r', '\n', or the
2103    character following the '*' (if any), and the matched sequence(s) are
2104    copied into star_match.
2105    */
2106 int
2107 looking_at(buf, index, pattern)
2108      char *buf;
2109      int *index;
2110      char *pattern;
2111 {
2112     char *bufp = &buf[*index], *patternp = pattern;
2113     int star_count = 0;
2114     char *matchp = star_match[0];
2115
2116     for (;;) {
2117         if (*patternp == NULLCHAR) {
2118             *index = leftover_start = bufp - buf;
2119             *matchp = NULLCHAR;
2120             return TRUE;
2121         }
2122         if (*bufp == NULLCHAR) return FALSE;
2123         if (*patternp == '*') {
2124             if (*bufp == *(patternp + 1)) {
2125                 *matchp = NULLCHAR;
2126                 matchp = star_match[++star_count];
2127                 patternp += 2;
2128                 bufp++;
2129                 continue;
2130             } else if (*bufp == '\n' || *bufp == '\r') {
2131                 patternp++;
2132                 if (*patternp == NULLCHAR)
2133                   continue;
2134                 else
2135                   return FALSE;
2136             } else {
2137                 *matchp++ = *bufp++;
2138                 continue;
2139             }
2140         }
2141         if (*patternp != *bufp) return FALSE;
2142         patternp++;
2143         bufp++;
2144     }
2145 }
2146
2147 void
2148 SendToPlayer(data, length)
2149      char *data;
2150      int length;
2151 {
2152     int error, outCount;
2153     outCount = OutputToProcess(NoProc, data, length, &error);
2154     if (outCount < length) {
2155         DisplayFatalError(_("Error writing to display"), error, 1);
2156     }
2157 }
2158
2159 void
2160 PackHolding(packed, holding)
2161      char packed[];
2162      char *holding;
2163 {
2164     char *p = holding;
2165     char *q = packed;
2166     int runlength = 0;
2167     int curr = 9999;
2168     do {
2169         if (*p == curr) {
2170             runlength++;
2171         } else {
2172             switch (runlength) {
2173               case 0:
2174                 break;
2175               case 1:
2176                 *q++ = curr;
2177                 break;
2178               case 2:
2179                 *q++ = curr;
2180                 *q++ = curr;
2181                 break;
2182               default:
2183                 sprintf(q, "%d", runlength);
2184                 while (*q) q++;
2185                 *q++ = curr;
2186                 break;
2187             }
2188             runlength = 1;
2189             curr = *p;
2190         }
2191     } while (*p++);
2192     *q = NULLCHAR;
2193 }
2194
2195 /* Telnet protocol requests from the front end */
2196 void
2197 TelnetRequest(ddww, option)
2198      unsigned char ddww, option;
2199 {
2200     unsigned char msg[3];
2201     int outCount, outError;
2202
2203     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2204
2205     if (appData.debugMode) {
2206         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2207         switch (ddww) {
2208           case TN_DO:
2209             ddwwStr = "DO";
2210             break;
2211           case TN_DONT:
2212             ddwwStr = "DONT";
2213             break;
2214           case TN_WILL:
2215             ddwwStr = "WILL";
2216             break;
2217           case TN_WONT:
2218             ddwwStr = "WONT";
2219             break;
2220           default:
2221             ddwwStr = buf1;
2222             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2223             break;
2224         }
2225         switch (option) {
2226           case TN_ECHO:
2227             optionStr = "ECHO";
2228             break;
2229           default:
2230             optionStr = buf2;
2231             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2232             break;
2233         }
2234         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2235     }
2236     msg[0] = TN_IAC;
2237     msg[1] = ddww;
2238     msg[2] = option;
2239     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2240     if (outCount < 3) {
2241         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2242     }
2243 }
2244
2245 void
2246 DoEcho()
2247 {
2248     if (!appData.icsActive) return;
2249     TelnetRequest(TN_DO, TN_ECHO);
2250 }
2251
2252 void
2253 DontEcho()
2254 {
2255     if (!appData.icsActive) return;
2256     TelnetRequest(TN_DONT, TN_ECHO);
2257 }
2258
2259 void
2260 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2261 {
2262     /* put the holdings sent to us by the server on the board holdings area */
2263     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2264     char p;
2265     ChessSquare piece;
2266
2267     if(gameInfo.holdingsWidth < 2)  return;
2268     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2269         return; // prevent overwriting by pre-board holdings
2270
2271     if( (int)lowestPiece >= BlackPawn ) {
2272         holdingsColumn = 0;
2273         countsColumn = 1;
2274         holdingsStartRow = BOARD_HEIGHT-1;
2275         direction = -1;
2276     } else {
2277         holdingsColumn = BOARD_WIDTH-1;
2278         countsColumn = BOARD_WIDTH-2;
2279         holdingsStartRow = 0;
2280         direction = 1;
2281     }
2282
2283     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2284         board[i][holdingsColumn] = EmptySquare;
2285         board[i][countsColumn]   = (ChessSquare) 0;
2286     }
2287     while( (p=*holdings++) != NULLCHAR ) {
2288         piece = CharToPiece( ToUpper(p) );
2289         if(piece == EmptySquare) continue;
2290         /*j = (int) piece - (int) WhitePawn;*/
2291         j = PieceToNumber(piece);
2292         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2293         if(j < 0) continue;               /* should not happen */
2294         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2295         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2296         board[holdingsStartRow+j*direction][countsColumn]++;
2297     }
2298 }
2299
2300
2301 void
2302 VariantSwitch(Board board, VariantClass newVariant)
2303 {
2304    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2305    static Board oldBoard;
2306
2307    startedFromPositionFile = FALSE;
2308    if(gameInfo.variant == newVariant) return;
2309
2310    /* [HGM] This routine is called each time an assignment is made to
2311     * gameInfo.variant during a game, to make sure the board sizes
2312     * are set to match the new variant. If that means adding or deleting
2313     * holdings, we shift the playing board accordingly
2314     * This kludge is needed because in ICS observe mode, we get boards
2315     * of an ongoing game without knowing the variant, and learn about the
2316     * latter only later. This can be because of the move list we requested,
2317     * in which case the game history is refilled from the beginning anyway,
2318     * but also when receiving holdings of a crazyhouse game. In the latter
2319     * case we want to add those holdings to the already received position.
2320     */
2321
2322
2323    if (appData.debugMode) {
2324      fprintf(debugFP, "Switch board from %s to %s\n",
2325              VariantName(gameInfo.variant), VariantName(newVariant));
2326      setbuf(debugFP, NULL);
2327    }
2328    shuffleOpenings = 0;       /* [HGM] shuffle */
2329    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2330    switch(newVariant)
2331      {
2332      case VariantShogi:
2333        newWidth = 9;  newHeight = 9;
2334        gameInfo.holdingsSize = 7;
2335      case VariantBughouse:
2336      case VariantCrazyhouse:
2337        newHoldingsWidth = 2; break;
2338      case VariantGreat:
2339        newWidth = 10;
2340      case VariantSuper:
2341        newHoldingsWidth = 2;
2342        gameInfo.holdingsSize = 8;
2343        break;
2344      case VariantGothic:
2345      case VariantCapablanca:
2346      case VariantCapaRandom:
2347        newWidth = 10;
2348      default:
2349        newHoldingsWidth = gameInfo.holdingsSize = 0;
2350      };
2351
2352    if(newWidth  != gameInfo.boardWidth  ||
2353       newHeight != gameInfo.boardHeight ||
2354       newHoldingsWidth != gameInfo.holdingsWidth ) {
2355
2356      /* shift position to new playing area, if needed */
2357      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2358        for(i=0; i<BOARD_HEIGHT; i++)
2359          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2360            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2361              board[i][j];
2362        for(i=0; i<newHeight; i++) {
2363          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2364          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2365        }
2366      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2367        for(i=0; i<BOARD_HEIGHT; i++)
2368          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2369            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2370              board[i][j];
2371      }
2372      gameInfo.boardWidth  = newWidth;
2373      gameInfo.boardHeight = newHeight;
2374      gameInfo.holdingsWidth = newHoldingsWidth;
2375      gameInfo.variant = newVariant;
2376      InitDrawingSizes(-2, 0);
2377    } else gameInfo.variant = newVariant;
2378    CopyBoard(oldBoard, board);   // remember correctly formatted board
2379      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2380    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2381 }
2382
2383 static int loggedOn = FALSE;
2384
2385 /*-- Game start info cache: --*/
2386 int gs_gamenum;
2387 char gs_kind[MSG_SIZ];
2388 static char player1Name[128] = "";
2389 static char player2Name[128] = "";
2390 static char cont_seq[] = "\n\\   ";
2391 static int player1Rating = -1;
2392 static int player2Rating = -1;
2393 /*----------------------------*/
2394
2395 ColorClass curColor = ColorNormal;
2396 int suppressKibitz = 0;
2397
2398 // [HGM] seekgraph
2399 Boolean soughtPending = FALSE;
2400 Boolean seekGraphUp;
2401 #define MAX_SEEK_ADS 200
2402 #define SQUARE 0x80
2403 char *seekAdList[MAX_SEEK_ADS];
2404 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2405 float tcList[MAX_SEEK_ADS];
2406 char colorList[MAX_SEEK_ADS];
2407 int nrOfSeekAds = 0;
2408 int minRating = 1010, maxRating = 2800;
2409 int hMargin = 10, vMargin = 20, h, w;
2410 extern int squareSize, lineGap;
2411
2412 void
2413 PlotSeekAd(int i)
2414 {
2415         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2416         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2417         if(r < minRating+100 && r >=0 ) r = minRating+100;
2418         if(r > maxRating) r = maxRating;
2419         if(tc < 1.) tc = 1.;
2420         if(tc > 95.) tc = 95.;
2421         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2422         y = ((double)r - minRating)/(maxRating - minRating)
2423             * (h-vMargin-squareSize/8-1) + vMargin;
2424         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2425         if(strstr(seekAdList[i], " u ")) color = 1;
2426         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2427            !strstr(seekAdList[i], "bullet") &&
2428            !strstr(seekAdList[i], "blitz") &&
2429            !strstr(seekAdList[i], "standard") ) color = 2;
2430         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2431         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2432 }
2433
2434 void
2435 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2436 {
2437         char buf[MSG_SIZ], *ext = "";
2438         VariantClass v = StringToVariant(type);
2439         if(strstr(type, "wild")) {
2440             ext = type + 4; // append wild number
2441             if(v == VariantFischeRandom) type = "chess960"; else
2442             if(v == VariantLoadable) type = "setup"; else
2443             type = VariantName(v);
2444         }
2445         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2446         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2447             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2448             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2449             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2450             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2451             seekNrList[nrOfSeekAds] = nr;
2452             zList[nrOfSeekAds] = 0;
2453             seekAdList[nrOfSeekAds++] = StrSave(buf);
2454             if(plot) PlotSeekAd(nrOfSeekAds-1);
2455         }
2456 }
2457
2458 void
2459 EraseSeekDot(int i)
2460 {
2461     int x = xList[i], y = yList[i], d=squareSize/4, k;
2462     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2463     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2464     // now replot every dot that overlapped
2465     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2466         int xx = xList[k], yy = yList[k];
2467         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2468             DrawSeekDot(xx, yy, colorList[k]);
2469     }
2470 }
2471
2472 void
2473 RemoveSeekAd(int nr)
2474 {
2475         int i;
2476         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2477             EraseSeekDot(i);
2478             if(seekAdList[i]) free(seekAdList[i]);
2479             seekAdList[i] = seekAdList[--nrOfSeekAds];
2480             seekNrList[i] = seekNrList[nrOfSeekAds];
2481             ratingList[i] = ratingList[nrOfSeekAds];
2482             colorList[i]  = colorList[nrOfSeekAds];
2483             tcList[i] = tcList[nrOfSeekAds];
2484             xList[i]  = xList[nrOfSeekAds];
2485             yList[i]  = yList[nrOfSeekAds];
2486             zList[i]  = zList[nrOfSeekAds];
2487             seekAdList[nrOfSeekAds] = NULL;
2488             break;
2489         }
2490 }
2491
2492 Boolean
2493 MatchSoughtLine(char *line)
2494 {
2495     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2496     int nr, base, inc, u=0; char dummy;
2497
2498     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2499        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2500        (u=1) &&
2501        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2502         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2503         // match: compact and save the line
2504         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2505         return TRUE;
2506     }
2507     return FALSE;
2508 }
2509
2510 int
2511 DrawSeekGraph()
2512 {
2513     int i;
2514     if(!seekGraphUp) return FALSE;
2515     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2516     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2517
2518     DrawSeekBackground(0, 0, w, h);
2519     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2520     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2521     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2522         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2523         yy = h-1-yy;
2524         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2525         if(i%500 == 0) {
2526             char buf[MSG_SIZ];
2527             snprintf(buf, MSG_SIZ, "%d", i);
2528             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2529         }
2530     }
2531     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2532     for(i=1; i<100; i+=(i<10?1:5)) {
2533         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2534         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2535         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2536             char buf[MSG_SIZ];
2537             snprintf(buf, MSG_SIZ, "%d", i);
2538             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2539         }
2540     }
2541     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2542     return TRUE;
2543 }
2544
2545 int SeekGraphClick(ClickType click, int x, int y, int moving)
2546 {
2547     static int lastDown = 0, displayed = 0, lastSecond;
2548     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2549         if(click == Release || moving) return FALSE;
2550         nrOfSeekAds = 0;
2551         soughtPending = TRUE;
2552         SendToICS(ics_prefix);
2553         SendToICS("sought\n"); // should this be "sought all"?
2554     } else { // issue challenge based on clicked ad
2555         int dist = 10000; int i, closest = 0, second = 0;
2556         for(i=0; i<nrOfSeekAds; i++) {
2557             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2558             if(d < dist) { dist = d; closest = i; }
2559             second += (d - zList[i] < 120); // count in-range ads
2560             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2561         }
2562         if(dist < 120) {
2563             char buf[MSG_SIZ];
2564             second = (second > 1);
2565             if(displayed != closest || second != lastSecond) {
2566                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2567                 lastSecond = second; displayed = closest;
2568             }
2569             if(click == Press) {
2570                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2571                 lastDown = closest;
2572                 return TRUE;
2573             } // on press 'hit', only show info
2574             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2575             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2576             SendToICS(ics_prefix);
2577             SendToICS(buf);
2578             return TRUE; // let incoming board of started game pop down the graph
2579         } else if(click == Release) { // release 'miss' is ignored
2580             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2581             if(moving == 2) { // right up-click
2582                 nrOfSeekAds = 0; // refresh graph
2583                 soughtPending = TRUE;
2584                 SendToICS(ics_prefix);
2585                 SendToICS("sought\n"); // should this be "sought all"?
2586             }
2587             return TRUE;
2588         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2589         // press miss or release hit 'pop down' seek graph
2590         seekGraphUp = FALSE;
2591         DrawPosition(TRUE, NULL);
2592     }
2593     return TRUE;
2594 }
2595
2596 void
2597 read_from_ics(isr, closure, data, count, error)
2598      InputSourceRef isr;
2599      VOIDSTAR closure;
2600      char *data;
2601      int count;
2602      int error;
2603 {
2604 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2605 #define STARTED_NONE 0
2606 #define STARTED_MOVES 1
2607 #define STARTED_BOARD 2
2608 #define STARTED_OBSERVE 3
2609 #define STARTED_HOLDINGS 4
2610 #define STARTED_CHATTER 5
2611 #define STARTED_COMMENT 6
2612 #define STARTED_MOVES_NOHIDE 7
2613
2614     static int started = STARTED_NONE;
2615     static char parse[20000];
2616     static int parse_pos = 0;
2617     static char buf[BUF_SIZE + 1];
2618     static int firstTime = TRUE, intfSet = FALSE;
2619     static ColorClass prevColor = ColorNormal;
2620     static int savingComment = FALSE;
2621     static int cmatch = 0; // continuation sequence match
2622     char *bp;
2623     char str[MSG_SIZ];
2624     int i, oldi;
2625     int buf_len;
2626     int next_out;
2627     int tkind;
2628     int backup;    /* [DM] For zippy color lines */
2629     char *p;
2630     char talker[MSG_SIZ]; // [HGM] chat
2631     int channel;
2632
2633     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2634
2635     if (appData.debugMode) {
2636       if (!error) {
2637         fprintf(debugFP, "<ICS: ");
2638         show_bytes(debugFP, data, count);
2639         fprintf(debugFP, "\n");
2640       }
2641     }
2642
2643     if (appData.debugMode) { int f = forwardMostMove;
2644         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2645                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2646                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2647     }
2648     if (count > 0) {
2649         /* If last read ended with a partial line that we couldn't parse,
2650            prepend it to the new read and try again. */
2651         if (leftover_len > 0) {
2652             for (i=0; i<leftover_len; i++)
2653               buf[i] = buf[leftover_start + i];
2654         }
2655
2656     /* copy new characters into the buffer */
2657     bp = buf + leftover_len;
2658     buf_len=leftover_len;
2659     for (i=0; i<count; i++)
2660     {
2661         // ignore these
2662         if (data[i] == '\r')
2663             continue;
2664
2665         // join lines split by ICS?
2666         if (!appData.noJoin)
2667         {
2668             /*
2669                 Joining just consists of finding matches against the
2670                 continuation sequence, and discarding that sequence
2671                 if found instead of copying it.  So, until a match
2672                 fails, there's nothing to do since it might be the
2673                 complete sequence, and thus, something we don't want
2674                 copied.
2675             */
2676             if (data[i] == cont_seq[cmatch])
2677             {
2678                 cmatch++;
2679                 if (cmatch == strlen(cont_seq))
2680                 {
2681                     cmatch = 0; // complete match.  just reset the counter
2682
2683                     /*
2684                         it's possible for the ICS to not include the space
2685                         at the end of the last word, making our [correct]
2686                         join operation fuse two separate words.  the server
2687                         does this when the space occurs at the width setting.
2688                     */
2689                     if (!buf_len || buf[buf_len-1] != ' ')
2690                     {
2691                         *bp++ = ' ';
2692                         buf_len++;
2693                     }
2694                 }
2695                 continue;
2696             }
2697             else if (cmatch)
2698             {
2699                 /*
2700                     match failed, so we have to copy what matched before
2701                     falling through and copying this character.  In reality,
2702                     this will only ever be just the newline character, but
2703                     it doesn't hurt to be precise.
2704                 */
2705                 strncpy(bp, cont_seq, cmatch);
2706                 bp += cmatch;
2707                 buf_len += cmatch;
2708                 cmatch = 0;
2709             }
2710         }
2711
2712         // copy this char
2713         *bp++ = data[i];
2714         buf_len++;
2715     }
2716
2717         buf[buf_len] = NULLCHAR;
2718 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2719         next_out = 0;
2720         leftover_start = 0;
2721
2722         i = 0;
2723         while (i < buf_len) {
2724             /* Deal with part of the TELNET option negotiation
2725                protocol.  We refuse to do anything beyond the
2726                defaults, except that we allow the WILL ECHO option,
2727                which ICS uses to turn off password echoing when we are
2728                directly connected to it.  We reject this option
2729                if localLineEditing mode is on (always on in xboard)
2730                and we are talking to port 23, which might be a real
2731                telnet server that will try to keep WILL ECHO on permanently.
2732              */
2733             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2734                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2735                 unsigned char option;
2736                 oldi = i;
2737                 switch ((unsigned char) buf[++i]) {
2738                   case TN_WILL:
2739                     if (appData.debugMode)
2740                       fprintf(debugFP, "\n<WILL ");
2741                     switch (option = (unsigned char) buf[++i]) {
2742                       case TN_ECHO:
2743                         if (appData.debugMode)
2744                           fprintf(debugFP, "ECHO ");
2745                         /* Reply only if this is a change, according
2746                            to the protocol rules. */
2747                         if (remoteEchoOption) break;
2748                         if (appData.localLineEditing &&
2749                             atoi(appData.icsPort) == TN_PORT) {
2750                             TelnetRequest(TN_DONT, TN_ECHO);
2751                         } else {
2752                             EchoOff();
2753                             TelnetRequest(TN_DO, TN_ECHO);
2754                             remoteEchoOption = TRUE;
2755                         }
2756                         break;
2757                       default:
2758                         if (appData.debugMode)
2759                           fprintf(debugFP, "%d ", option);
2760                         /* Whatever this is, we don't want it. */
2761                         TelnetRequest(TN_DONT, option);
2762                         break;
2763                     }
2764                     break;
2765                   case TN_WONT:
2766                     if (appData.debugMode)
2767                       fprintf(debugFP, "\n<WONT ");
2768                     switch (option = (unsigned char) buf[++i]) {
2769                       case TN_ECHO:
2770                         if (appData.debugMode)
2771                           fprintf(debugFP, "ECHO ");
2772                         /* Reply only if this is a change, according
2773                            to the protocol rules. */
2774                         if (!remoteEchoOption) break;
2775                         EchoOn();
2776                         TelnetRequest(TN_DONT, TN_ECHO);
2777                         remoteEchoOption = FALSE;
2778                         break;
2779                       default:
2780                         if (appData.debugMode)
2781                           fprintf(debugFP, "%d ", (unsigned char) option);
2782                         /* Whatever this is, it must already be turned
2783                            off, because we never agree to turn on
2784                            anything non-default, so according to the
2785                            protocol rules, we don't reply. */
2786                         break;
2787                     }
2788                     break;
2789                   case TN_DO:
2790                     if (appData.debugMode)
2791                       fprintf(debugFP, "\n<DO ");
2792                     switch (option = (unsigned char) buf[++i]) {
2793                       default:
2794                         /* Whatever this is, we refuse to do it. */
2795                         if (appData.debugMode)
2796                           fprintf(debugFP, "%d ", option);
2797                         TelnetRequest(TN_WONT, option);
2798                         break;
2799                     }
2800                     break;
2801                   case TN_DONT:
2802                     if (appData.debugMode)
2803                       fprintf(debugFP, "\n<DONT ");
2804                     switch (option = (unsigned char) buf[++i]) {
2805                       default:
2806                         if (appData.debugMode)
2807                           fprintf(debugFP, "%d ", option);
2808                         /* Whatever this is, we are already not doing
2809                            it, because we never agree to do anything
2810                            non-default, so according to the protocol
2811                            rules, we don't reply. */
2812                         break;
2813                     }
2814                     break;
2815                   case TN_IAC:
2816                     if (appData.debugMode)
2817                       fprintf(debugFP, "\n<IAC ");
2818                     /* Doubled IAC; pass it through */
2819                     i--;
2820                     break;
2821                   default:
2822                     if (appData.debugMode)
2823                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2824                     /* Drop all other telnet commands on the floor */
2825                     break;
2826                 }
2827                 if (oldi > next_out)
2828                   SendToPlayer(&buf[next_out], oldi - next_out);
2829                 if (++i > next_out)
2830                   next_out = i;
2831                 continue;
2832             }
2833
2834             /* OK, this at least will *usually* work */
2835             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2836                 loggedOn = TRUE;
2837             }
2838
2839             if (loggedOn && !intfSet) {
2840                 if (ics_type == ICS_ICC) {
2841                   snprintf(str, MSG_SIZ,
2842                           "/set-quietly interface %s\n/set-quietly style 12\n",
2843                           programVersion);
2844                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2845                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2846                 } else if (ics_type == ICS_CHESSNET) {
2847                   snprintf(str, MSG_SIZ, "/style 12\n");
2848                 } else {
2849                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2850                   strcat(str, programVersion);
2851                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2852                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2853                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2854 #ifdef WIN32
2855                   strcat(str, "$iset nohighlight 1\n");
2856 #endif
2857                   strcat(str, "$iset lock 1\n$style 12\n");
2858                 }
2859                 SendToICS(str);
2860                 NotifyFrontendLogin();
2861                 intfSet = TRUE;
2862             }
2863
2864             if (started == STARTED_COMMENT) {
2865                 /* Accumulate characters in comment */
2866                 parse[parse_pos++] = buf[i];
2867                 if (buf[i] == '\n') {
2868                     parse[parse_pos] = NULLCHAR;
2869                     if(chattingPartner>=0) {
2870                         char mess[MSG_SIZ];
2871                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2872                         OutputChatMessage(chattingPartner, mess);
2873                         chattingPartner = -1;
2874                         next_out = i+1; // [HGM] suppress printing in ICS window
2875                     } else
2876                     if(!suppressKibitz) // [HGM] kibitz
2877                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2878                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2879                         int nrDigit = 0, nrAlph = 0, j;
2880                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2881                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2882                         parse[parse_pos] = NULLCHAR;
2883                         // try to be smart: if it does not look like search info, it should go to
2884                         // ICS interaction window after all, not to engine-output window.
2885                         for(j=0; j<parse_pos; j++) { // count letters and digits
2886                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2887                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2888                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2889                         }
2890                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2891                             int depth=0; float score;
2892                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2893                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2894                                 pvInfoList[forwardMostMove-1].depth = depth;
2895                                 pvInfoList[forwardMostMove-1].score = 100*score;
2896                             }
2897                             OutputKibitz(suppressKibitz, parse);
2898                         } else {
2899                             char tmp[MSG_SIZ];
2900                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2901                             SendToPlayer(tmp, strlen(tmp));
2902                         }
2903                         next_out = i+1; // [HGM] suppress printing in ICS window
2904                     }
2905                     started = STARTED_NONE;
2906                 } else {
2907                     /* Don't match patterns against characters in comment */
2908                     i++;
2909                     continue;
2910                 }
2911             }
2912             if (started == STARTED_CHATTER) {
2913                 if (buf[i] != '\n') {
2914                     /* Don't match patterns against characters in chatter */
2915                     i++;
2916                     continue;
2917                 }
2918                 started = STARTED_NONE;
2919                 if(suppressKibitz) next_out = i+1;
2920             }
2921
2922             /* Kludge to deal with rcmd protocol */
2923             if (firstTime && looking_at(buf, &i, "\001*")) {
2924                 DisplayFatalError(&buf[1], 0, 1);
2925                 continue;
2926             } else {
2927                 firstTime = FALSE;
2928             }
2929
2930             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2931                 ics_type = ICS_ICC;
2932                 ics_prefix = "/";
2933                 if (appData.debugMode)
2934                   fprintf(debugFP, "ics_type %d\n", ics_type);
2935                 continue;
2936             }
2937             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2938                 ics_type = ICS_FICS;
2939                 ics_prefix = "$";
2940                 if (appData.debugMode)
2941                   fprintf(debugFP, "ics_type %d\n", ics_type);
2942                 continue;
2943             }
2944             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2945                 ics_type = ICS_CHESSNET;
2946                 ics_prefix = "/";
2947                 if (appData.debugMode)
2948                   fprintf(debugFP, "ics_type %d\n", ics_type);
2949                 continue;
2950             }
2951
2952             if (!loggedOn &&
2953                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2954                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2955                  looking_at(buf, &i, "will be \"*\""))) {
2956               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2957               continue;
2958             }
2959
2960             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2961               char buf[MSG_SIZ];
2962               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2963               DisplayIcsInteractionTitle(buf);
2964               have_set_title = TRUE;
2965             }
2966
2967             /* skip finger notes */
2968             if (started == STARTED_NONE &&
2969                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2970                  (buf[i] == '1' && buf[i+1] == '0')) &&
2971                 buf[i+2] == ':' && buf[i+3] == ' ') {
2972               started = STARTED_CHATTER;
2973               i += 3;
2974               continue;
2975             }
2976
2977             oldi = i;
2978             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2979             if(appData.seekGraph) {
2980                 if(soughtPending && MatchSoughtLine(buf+i)) {
2981                     i = strstr(buf+i, "rated") - buf;
2982                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2983                     next_out = leftover_start = i;
2984                     started = STARTED_CHATTER;
2985                     suppressKibitz = TRUE;
2986                     continue;
2987                 }
2988                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2989                         && looking_at(buf, &i, "* ads displayed")) {
2990                     soughtPending = FALSE;
2991                     seekGraphUp = TRUE;
2992                     DrawSeekGraph();
2993                     continue;
2994                 }
2995                 if(appData.autoRefresh) {
2996                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2997                         int s = (ics_type == ICS_ICC); // ICC format differs
2998                         if(seekGraphUp)
2999                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3000                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3001                         looking_at(buf, &i, "*% "); // eat prompt
3002                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3003                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3004                         next_out = i; // suppress
3005                         continue;
3006                     }
3007                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3008                         char *p = star_match[0];
3009                         while(*p) {
3010                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3011                             while(*p && *p++ != ' '); // next
3012                         }
3013                         looking_at(buf, &i, "*% "); // eat prompt
3014                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3015                         next_out = i;
3016                         continue;
3017                     }
3018                 }
3019             }
3020
3021             /* skip formula vars */
3022             if (started == STARTED_NONE &&
3023                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3024               started = STARTED_CHATTER;
3025               i += 3;
3026               continue;
3027             }
3028
3029             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3030             if (appData.autoKibitz && started == STARTED_NONE &&
3031                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3032                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3033                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3034                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3035                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3036                         suppressKibitz = TRUE;
3037                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3038                         next_out = i;
3039                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3040                                 && (gameMode == IcsPlayingWhite)) ||
3041                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3042                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3043                             started = STARTED_CHATTER; // own kibitz we simply discard
3044                         else {
3045                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3046                             parse_pos = 0; parse[0] = NULLCHAR;
3047                             savingComment = TRUE;
3048                             suppressKibitz = gameMode != IcsObserving ? 2 :
3049                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3050                         }
3051                         continue;
3052                 } else
3053                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3054                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3055                          && atoi(star_match[0])) {
3056                     // suppress the acknowledgements of our own autoKibitz
3057                     char *p;
3058                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3059                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3060                     SendToPlayer(star_match[0], strlen(star_match[0]));
3061                     if(looking_at(buf, &i, "*% ")) // eat prompt
3062                         suppressKibitz = FALSE;
3063                     next_out = i;
3064                     continue;
3065                 }
3066             } // [HGM] kibitz: end of patch
3067
3068             // [HGM] chat: intercept tells by users for which we have an open chat window
3069             channel = -1;
3070             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3071                                            looking_at(buf, &i, "* whispers:") ||
3072                                            looking_at(buf, &i, "* kibitzes:") ||
3073                                            looking_at(buf, &i, "* shouts:") ||
3074                                            looking_at(buf, &i, "* c-shouts:") ||
3075                                            looking_at(buf, &i, "--> * ") ||
3076                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3077                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3078                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3079                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3080                 int p;
3081                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3082                 chattingPartner = -1;
3083
3084                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3085                 for(p=0; p<MAX_CHAT; p++) {
3086                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3087                     talker[0] = '['; strcat(talker, "] ");
3088                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3089                     chattingPartner = p; break;
3090                     }
3091                 } else
3092                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3093                 for(p=0; p<MAX_CHAT; p++) {
3094                     if(!strcmp("kibitzes", chatPartner[p])) {
3095                         talker[0] = '['; strcat(talker, "] ");
3096                         chattingPartner = p; break;
3097                     }
3098                 } else
3099                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3100                 for(p=0; p<MAX_CHAT; p++) {
3101                     if(!strcmp("whispers", chatPartner[p])) {
3102                         talker[0] = '['; strcat(talker, "] ");
3103                         chattingPartner = p; break;
3104                     }
3105                 } else
3106                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3107                   if(buf[i-8] == '-' && buf[i-3] == 't')
3108                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3109                     if(!strcmp("c-shouts", chatPartner[p])) {
3110                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3111                         chattingPartner = p; break;
3112                     }
3113                   }
3114                   if(chattingPartner < 0)
3115                   for(p=0; p<MAX_CHAT; p++) {
3116                     if(!strcmp("shouts", chatPartner[p])) {
3117                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3118                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3119                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3120                         chattingPartner = p; break;
3121                     }
3122                   }
3123                 }
3124                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3125                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3126                     talker[0] = 0; Colorize(ColorTell, FALSE);
3127                     chattingPartner = p; break;
3128                 }
3129                 if(chattingPartner<0) i = oldi; else {
3130                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3131                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3132                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3133                     started = STARTED_COMMENT;
3134                     parse_pos = 0; parse[0] = NULLCHAR;
3135                     savingComment = 3 + chattingPartner; // counts as TRUE
3136                     suppressKibitz = TRUE;
3137                     continue;
3138                 }
3139             } // [HGM] chat: end of patch
3140
3141           backup = i;
3142             if (appData.zippyTalk || appData.zippyPlay) {
3143                 /* [DM] Backup address for color zippy lines */
3144 #if ZIPPY
3145                if (loggedOn == TRUE)
3146                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3147                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3148 #endif
3149             } // [DM] 'else { ' deleted
3150                 if (
3151                     /* Regular tells and says */
3152                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3153                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3154                     looking_at(buf, &i, "* says: ") ||
3155                     /* Don't color "message" or "messages" output */
3156                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3157                     looking_at(buf, &i, "*. * at *:*: ") ||
3158                     looking_at(buf, &i, "--* (*:*): ") ||
3159                     /* Message notifications (same color as tells) */
3160                     looking_at(buf, &i, "* has left a message ") ||
3161                     looking_at(buf, &i, "* just sent you a message:\n") ||
3162                     /* Whispers and kibitzes */
3163                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3164                     looking_at(buf, &i, "* kibitzes: ") ||
3165                     /* Channel tells */
3166                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3167
3168                   if (tkind == 1 && strchr(star_match[0], ':')) {
3169                       /* Avoid "tells you:" spoofs in channels */
3170                      tkind = 3;
3171                   }
3172                   if (star_match[0][0] == NULLCHAR ||
3173                       strchr(star_match[0], ' ') ||
3174                       (tkind == 3 && strchr(star_match[1], ' '))) {
3175                     /* Reject bogus matches */
3176                     i = oldi;
3177                   } else {
3178                     if (appData.colorize) {
3179                       if (oldi > next_out) {
3180                         SendToPlayer(&buf[next_out], oldi - next_out);
3181                         next_out = oldi;
3182                       }
3183                       switch (tkind) {
3184                       case 1:
3185                         Colorize(ColorTell, FALSE);
3186                         curColor = ColorTell;
3187                         break;
3188                       case 2:
3189                         Colorize(ColorKibitz, FALSE);
3190                         curColor = ColorKibitz;
3191                         break;
3192                       case 3:
3193                         p = strrchr(star_match[1], '(');
3194                         if (p == NULL) {
3195                           p = star_match[1];
3196                         } else {
3197                           p++;
3198                         }
3199                         if (atoi(p) == 1) {
3200                           Colorize(ColorChannel1, FALSE);
3201                           curColor = ColorChannel1;
3202                         } else {
3203                           Colorize(ColorChannel, FALSE);
3204                           curColor = ColorChannel;
3205                         }
3206                         break;
3207                       case 5:
3208                         curColor = ColorNormal;
3209                         break;
3210                       }
3211                     }
3212                     if (started == STARTED_NONE && appData.autoComment &&
3213                         (gameMode == IcsObserving ||
3214                          gameMode == IcsPlayingWhite ||
3215                          gameMode == IcsPlayingBlack)) {
3216                       parse_pos = i - oldi;
3217                       memcpy(parse, &buf[oldi], parse_pos);
3218                       parse[parse_pos] = NULLCHAR;
3219                       started = STARTED_COMMENT;
3220                       savingComment = TRUE;
3221                     } else {
3222                       started = STARTED_CHATTER;
3223                       savingComment = FALSE;
3224                     }
3225                     loggedOn = TRUE;
3226                     continue;
3227                   }
3228                 }
3229
3230                 if (looking_at(buf, &i, "* s-shouts: ") ||
3231                     looking_at(buf, &i, "* c-shouts: ")) {
3232                     if (appData.colorize) {
3233                         if (oldi > next_out) {
3234                             SendToPlayer(&buf[next_out], oldi - next_out);
3235                             next_out = oldi;
3236                         }
3237                         Colorize(ColorSShout, FALSE);
3238                         curColor = ColorSShout;
3239                     }
3240                     loggedOn = TRUE;
3241                     started = STARTED_CHATTER;
3242                     continue;
3243                 }
3244
3245                 if (looking_at(buf, &i, "--->")) {
3246                     loggedOn = TRUE;
3247                     continue;
3248                 }
3249
3250                 if (looking_at(buf, &i, "* shouts: ") ||
3251                     looking_at(buf, &i, "--> ")) {
3252                     if (appData.colorize) {
3253                         if (oldi > next_out) {
3254                             SendToPlayer(&buf[next_out], oldi - next_out);
3255                             next_out = oldi;
3256                         }
3257                         Colorize(ColorShout, FALSE);
3258                         curColor = ColorShout;
3259                     }
3260                     loggedOn = TRUE;
3261                     started = STARTED_CHATTER;
3262                     continue;
3263                 }
3264
3265                 if (looking_at( buf, &i, "Challenge:")) {
3266                     if (appData.colorize) {
3267                         if (oldi > next_out) {
3268                             SendToPlayer(&buf[next_out], oldi - next_out);
3269                             next_out = oldi;
3270                         }
3271                         Colorize(ColorChallenge, FALSE);
3272                         curColor = ColorChallenge;
3273                     }
3274                     loggedOn = TRUE;
3275                     continue;
3276                 }
3277
3278                 if (looking_at(buf, &i, "* offers you") ||
3279                     looking_at(buf, &i, "* offers to be") ||
3280                     looking_at(buf, &i, "* would like to") ||
3281                     looking_at(buf, &i, "* requests to") ||
3282                     looking_at(buf, &i, "Your opponent offers") ||
3283                     looking_at(buf, &i, "Your opponent requests")) {
3284
3285                     if (appData.colorize) {
3286                         if (oldi > next_out) {
3287                             SendToPlayer(&buf[next_out], oldi - next_out);
3288                             next_out = oldi;
3289                         }
3290                         Colorize(ColorRequest, FALSE);
3291                         curColor = ColorRequest;
3292                     }
3293                     continue;
3294                 }
3295
3296                 if (looking_at(buf, &i, "* (*) seeking")) {
3297                     if (appData.colorize) {
3298                         if (oldi > next_out) {
3299                             SendToPlayer(&buf[next_out], oldi - next_out);
3300                             next_out = oldi;
3301                         }
3302                         Colorize(ColorSeek, FALSE);
3303                         curColor = ColorSeek;
3304                     }
3305                     continue;
3306             }
3307
3308           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3309
3310             if (looking_at(buf, &i, "\\   ")) {
3311                 if (prevColor != ColorNormal) {
3312                     if (oldi > next_out) {
3313                         SendToPlayer(&buf[next_out], oldi - next_out);
3314                         next_out = oldi;
3315                     }
3316                     Colorize(prevColor, TRUE);
3317                     curColor = prevColor;
3318                 }
3319                 if (savingComment) {
3320                     parse_pos = i - oldi;
3321                     memcpy(parse, &buf[oldi], parse_pos);
3322                     parse[parse_pos] = NULLCHAR;
3323                     started = STARTED_COMMENT;
3324                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3325                         chattingPartner = savingComment - 3; // kludge to remember the box
3326                 } else {
3327                     started = STARTED_CHATTER;
3328                 }
3329                 continue;
3330             }
3331
3332             if (looking_at(buf, &i, "Black Strength :") ||
3333                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3334                 looking_at(buf, &i, "<10>") ||
3335                 looking_at(buf, &i, "#@#")) {
3336                 /* Wrong board style */
3337                 loggedOn = TRUE;
3338                 SendToICS(ics_prefix);
3339                 SendToICS("set style 12\n");
3340                 SendToICS(ics_prefix);
3341                 SendToICS("refresh\n");
3342                 continue;
3343             }
3344
3345             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3346                 ICSInitScript();
3347                 have_sent_ICS_logon = 1;
3348                 continue;
3349             }
3350
3351             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3352                 (looking_at(buf, &i, "\n<12> ") ||
3353                  looking_at(buf, &i, "<12> "))) {
3354                 loggedOn = TRUE;
3355                 if (oldi > next_out) {
3356                     SendToPlayer(&buf[next_out], oldi - next_out);
3357                 }
3358                 next_out = i;
3359                 started = STARTED_BOARD;
3360                 parse_pos = 0;
3361                 continue;
3362             }
3363
3364             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3365                 looking_at(buf, &i, "<b1> ")) {
3366                 if (oldi > next_out) {
3367                     SendToPlayer(&buf[next_out], oldi - next_out);
3368                 }
3369                 next_out = i;
3370                 started = STARTED_HOLDINGS;
3371                 parse_pos = 0;
3372                 continue;
3373             }
3374
3375             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3376                 loggedOn = TRUE;
3377                 /* Header for a move list -- first line */
3378
3379                 switch (ics_getting_history) {
3380                   case H_FALSE:
3381                     switch (gameMode) {
3382                       case IcsIdle:
3383                       case BeginningOfGame:
3384                         /* User typed "moves" or "oldmoves" while we
3385                            were idle.  Pretend we asked for these
3386                            moves and soak them up so user can step
3387                            through them and/or save them.
3388                            */
3389                         Reset(FALSE, TRUE);
3390                         gameMode = IcsObserving;
3391                         ModeHighlight();
3392                         ics_gamenum = -1;
3393                         ics_getting_history = H_GOT_UNREQ_HEADER;
3394                         break;
3395                       case EditGame: /*?*/
3396                       case EditPosition: /*?*/
3397                         /* Should above feature work in these modes too? */
3398                         /* For now it doesn't */
3399                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3400                         break;
3401                       default:
3402                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3403                         break;
3404                     }
3405                     break;
3406                   case H_REQUESTED:
3407                     /* Is this the right one? */
3408                     if (gameInfo.white && gameInfo.black &&
3409                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3410                         strcmp(gameInfo.black, star_match[2]) == 0) {
3411                         /* All is well */
3412                         ics_getting_history = H_GOT_REQ_HEADER;
3413                     }
3414                     break;
3415                   case H_GOT_REQ_HEADER:
3416                   case H_GOT_UNREQ_HEADER:
3417                   case H_GOT_UNWANTED_HEADER:
3418                   case H_GETTING_MOVES:
3419                     /* Should not happen */
3420                     DisplayError(_("Error gathering move list: two headers"), 0);
3421                     ics_getting_history = H_FALSE;
3422                     break;
3423                 }
3424
3425                 /* Save player ratings into gameInfo if needed */
3426                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3427                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3428                     (gameInfo.whiteRating == -1 ||
3429                      gameInfo.blackRating == -1)) {
3430
3431                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3432                     gameInfo.blackRating = string_to_rating(star_match[3]);
3433                     if (appData.debugMode)
3434                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3435                               gameInfo.whiteRating, gameInfo.blackRating);
3436                 }
3437                 continue;
3438             }
3439
3440             if (looking_at(buf, &i,
3441               "* * match, initial time: * minute*, increment: * second")) {
3442                 /* Header for a move list -- second line */
3443                 /* Initial board will follow if this is a wild game */
3444                 if (gameInfo.event != NULL) free(gameInfo.event);
3445                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3446                 gameInfo.event = StrSave(str);
3447                 /* [HGM] we switched variant. Translate boards if needed. */
3448                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3449                 continue;
3450             }
3451
3452             if (looking_at(buf, &i, "Move  ")) {
3453                 /* Beginning of a move list */
3454                 switch (ics_getting_history) {
3455                   case H_FALSE:
3456                     /* Normally should not happen */
3457                     /* Maybe user hit reset while we were parsing */
3458                     break;
3459                   case H_REQUESTED:
3460                     /* Happens if we are ignoring a move list that is not
3461                      * the one we just requested.  Common if the user
3462                      * tries to observe two games without turning off
3463                      * getMoveList */
3464                     break;
3465                   case H_GETTING_MOVES:
3466                     /* Should not happen */
3467                     DisplayError(_("Error gathering move list: nested"), 0);
3468                     ics_getting_history = H_FALSE;
3469                     break;
3470                   case H_GOT_REQ_HEADER:
3471                     ics_getting_history = H_GETTING_MOVES;
3472                     started = STARTED_MOVES;
3473                     parse_pos = 0;
3474                     if (oldi > next_out) {
3475                         SendToPlayer(&buf[next_out], oldi - next_out);
3476                     }
3477                     break;
3478                   case H_GOT_UNREQ_HEADER:
3479                     ics_getting_history = H_GETTING_MOVES;
3480                     started = STARTED_MOVES_NOHIDE;
3481                     parse_pos = 0;
3482                     break;
3483                   case H_GOT_UNWANTED_HEADER:
3484                     ics_getting_history = H_FALSE;
3485                     break;
3486                 }
3487                 continue;
3488             }
3489
3490             if (looking_at(buf, &i, "% ") ||
3491                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3492                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3493                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3494                     soughtPending = FALSE;
3495                     seekGraphUp = TRUE;
3496                     DrawSeekGraph();
3497                 }
3498                 if(suppressKibitz) next_out = i;
3499                 savingComment = FALSE;
3500                 suppressKibitz = 0;
3501                 switch (started) {
3502                   case STARTED_MOVES:
3503                   case STARTED_MOVES_NOHIDE:
3504                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3505                     parse[parse_pos + i - oldi] = NULLCHAR;
3506                     ParseGameHistory(parse);
3507 #if ZIPPY
3508                     if (appData.zippyPlay && first.initDone) {
3509                         FeedMovesToProgram(&first, forwardMostMove);
3510                         if (gameMode == IcsPlayingWhite) {
3511                             if (WhiteOnMove(forwardMostMove)) {
3512                                 if (first.sendTime) {
3513                                   if (first.useColors) {
3514                                     SendToProgram("black\n", &first);
3515                                   }
3516                                   SendTimeRemaining(&first, TRUE);
3517                                 }
3518                                 if (first.useColors) {
3519                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3520                                 }
3521                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3522                                 first.maybeThinking = TRUE;
3523                             } else {
3524                                 if (first.usePlayother) {
3525                                   if (first.sendTime) {
3526                                     SendTimeRemaining(&first, TRUE);
3527                                   }
3528                                   SendToProgram("playother\n", &first);
3529                                   firstMove = FALSE;
3530                                 } else {
3531                                   firstMove = TRUE;
3532                                 }
3533                             }
3534                         } else if (gameMode == IcsPlayingBlack) {
3535                             if (!WhiteOnMove(forwardMostMove)) {
3536                                 if (first.sendTime) {
3537                                   if (first.useColors) {
3538                                     SendToProgram("white\n", &first);
3539                                   }
3540                                   SendTimeRemaining(&first, FALSE);
3541                                 }
3542                                 if (first.useColors) {
3543                                   SendToProgram("black\n", &first);
3544                                 }
3545                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3546                                 first.maybeThinking = TRUE;
3547                             } else {
3548                                 if (first.usePlayother) {
3549                                   if (first.sendTime) {
3550                                     SendTimeRemaining(&first, FALSE);
3551                                   }
3552                                   SendToProgram("playother\n", &first);
3553                                   firstMove = FALSE;
3554                                 } else {
3555                                   firstMove = TRUE;
3556                                 }
3557                             }
3558                         }
3559                     }
3560 #endif
3561                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3562                         /* Moves came from oldmoves or moves command
3563                            while we weren't doing anything else.
3564                            */
3565                         currentMove = forwardMostMove;
3566                         ClearHighlights();/*!!could figure this out*/
3567                         flipView = appData.flipView;
3568                         DrawPosition(TRUE, boards[currentMove]);
3569                         DisplayBothClocks();
3570                         snprintf(str, MSG_SIZ, "%s vs. %s",
3571                                 gameInfo.white, gameInfo.black);
3572                         DisplayTitle(str);
3573                         gameMode = IcsIdle;
3574                     } else {
3575                         /* Moves were history of an active game */
3576                         if (gameInfo.resultDetails != NULL) {
3577                             free(gameInfo.resultDetails);
3578                             gameInfo.resultDetails = NULL;
3579                         }
3580                     }
3581                     HistorySet(parseList, backwardMostMove,
3582                                forwardMostMove, currentMove-1);
3583                     DisplayMove(currentMove - 1);
3584                     if (started == STARTED_MOVES) next_out = i;
3585                     started = STARTED_NONE;
3586                     ics_getting_history = H_FALSE;
3587                     break;
3588
3589                   case STARTED_OBSERVE:
3590                     started = STARTED_NONE;
3591                     SendToICS(ics_prefix);
3592                     SendToICS("refresh\n");
3593                     break;
3594
3595                   default:
3596                     break;
3597                 }
3598                 if(bookHit) { // [HGM] book: simulate book reply
3599                     static char bookMove[MSG_SIZ]; // a bit generous?
3600
3601                     programStats.nodes = programStats.depth = programStats.time =
3602                     programStats.score = programStats.got_only_move = 0;
3603                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3604
3605                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3606                     strcat(bookMove, bookHit);
3607                     HandleMachineMove(bookMove, &first);
3608                 }
3609                 continue;
3610             }
3611
3612             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3613                  started == STARTED_HOLDINGS ||
3614                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3615                 /* Accumulate characters in move list or board */
3616                 parse[parse_pos++] = buf[i];
3617             }
3618
3619             /* Start of game messages.  Mostly we detect start of game
3620                when the first board image arrives.  On some versions
3621                of the ICS, though, we need to do a "refresh" after starting
3622                to observe in order to get the current board right away. */
3623             if (looking_at(buf, &i, "Adding game * to observation list")) {
3624                 started = STARTED_OBSERVE;
3625                 continue;
3626             }
3627
3628             /* Handle auto-observe */
3629             if (appData.autoObserve &&
3630                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3631                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3632                 char *player;
3633                 /* Choose the player that was highlighted, if any. */
3634                 if (star_match[0][0] == '\033' ||
3635                     star_match[1][0] != '\033') {
3636                     player = star_match[0];
3637                 } else {
3638                     player = star_match[2];
3639                 }
3640                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3641                         ics_prefix, StripHighlightAndTitle(player));
3642                 SendToICS(str);
3643
3644                 /* Save ratings from notify string */
3645                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3646                 player1Rating = string_to_rating(star_match[1]);
3647                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3648                 player2Rating = string_to_rating(star_match[3]);
3649
3650                 if (appData.debugMode)
3651                   fprintf(debugFP,
3652                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3653                           player1Name, player1Rating,
3654                           player2Name, player2Rating);
3655
3656                 continue;
3657             }
3658
3659             /* Deal with automatic examine mode after a game,
3660                and with IcsObserving -> IcsExamining transition */
3661             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3662                 looking_at(buf, &i, "has made you an examiner of game *")) {
3663
3664                 int gamenum = atoi(star_match[0]);
3665                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3666                     gamenum == ics_gamenum) {
3667                     /* We were already playing or observing this game;
3668                        no need to refetch history */
3669                     gameMode = IcsExamining;
3670                     if (pausing) {
3671                         pauseExamForwardMostMove = forwardMostMove;
3672                     } else if (currentMove < forwardMostMove) {
3673                         ForwardInner(forwardMostMove);
3674                     }
3675                 } else {
3676                     /* I don't think this case really can happen */
3677                     SendToICS(ics_prefix);
3678                     SendToICS("refresh\n");
3679                 }
3680                 continue;
3681             }
3682
3683             /* Error messages */
3684 //          if (ics_user_moved) {
3685             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3686                 if (looking_at(buf, &i, "Illegal move") ||
3687                     looking_at(buf, &i, "Not a legal move") ||
3688                     looking_at(buf, &i, "Your king is in check") ||
3689                     looking_at(buf, &i, "It isn't your turn") ||
3690                     looking_at(buf, &i, "It is not your move")) {
3691                     /* Illegal move */
3692                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3693                         currentMove = forwardMostMove-1;
3694                         DisplayMove(currentMove - 1); /* before DMError */
3695                         DrawPosition(FALSE, boards[currentMove]);
3696                         SwitchClocks(forwardMostMove-1); // [HGM] race
3697                         DisplayBothClocks();
3698                     }
3699                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3700                     ics_user_moved = 0;
3701                     continue;
3702                 }
3703             }
3704
3705             if (looking_at(buf, &i, "still have time") ||
3706                 looking_at(buf, &i, "not out of time") ||
3707                 looking_at(buf, &i, "either player is out of time") ||
3708                 looking_at(buf, &i, "has timeseal; checking")) {
3709                 /* We must have called his flag a little too soon */
3710                 whiteFlag = blackFlag = FALSE;
3711                 continue;
3712             }
3713
3714             if (looking_at(buf, &i, "added * seconds to") ||
3715                 looking_at(buf, &i, "seconds were added to")) {
3716                 /* Update the clocks */
3717                 SendToICS(ics_prefix);
3718                 SendToICS("refresh\n");
3719                 continue;
3720             }
3721
3722             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3723                 ics_clock_paused = TRUE;
3724                 StopClocks();
3725                 continue;
3726             }
3727
3728             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3729                 ics_clock_paused = FALSE;
3730                 StartClocks();
3731                 continue;
3732             }
3733
3734             /* Grab player ratings from the Creating: message.
3735                Note we have to check for the special case when
3736                the ICS inserts things like [white] or [black]. */
3737             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3738                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3739                 /* star_matches:
3740                    0    player 1 name (not necessarily white)
3741                    1    player 1 rating
3742                    2    empty, white, or black (IGNORED)
3743                    3    player 2 name (not necessarily black)
3744                    4    player 2 rating
3745
3746                    The names/ratings are sorted out when the game
3747                    actually starts (below).
3748                 */
3749                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3750                 player1Rating = string_to_rating(star_match[1]);
3751                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3752                 player2Rating = string_to_rating(star_match[4]);
3753
3754                 if (appData.debugMode)
3755                   fprintf(debugFP,
3756                           "Ratings from 'Creating:' %s %d, %s %d\n",
3757                           player1Name, player1Rating,
3758                           player2Name, player2Rating);
3759
3760                 continue;
3761             }
3762
3763             /* Improved generic start/end-of-game messages */
3764             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3765                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3766                 /* If tkind == 0: */
3767                 /* star_match[0] is the game number */
3768                 /*           [1] is the white player's name */
3769                 /*           [2] is the black player's name */
3770                 /* For end-of-game: */
3771                 /*           [3] is the reason for the game end */
3772                 /*           [4] is a PGN end game-token, preceded by " " */
3773                 /* For start-of-game: */
3774                 /*           [3] begins with "Creating" or "Continuing" */
3775                 /*           [4] is " *" or empty (don't care). */
3776                 int gamenum = atoi(star_match[0]);
3777                 char *whitename, *blackname, *why, *endtoken;
3778                 ChessMove endtype = EndOfFile;
3779
3780                 if (tkind == 0) {
3781                   whitename = star_match[1];
3782                   blackname = star_match[2];
3783                   why = star_match[3];
3784                   endtoken = star_match[4];
3785                 } else {
3786                   whitename = star_match[1];
3787                   blackname = star_match[3];
3788                   why = star_match[5];
3789                   endtoken = star_match[6];
3790                 }
3791
3792                 /* Game start messages */
3793                 if (strncmp(why, "Creating ", 9) == 0 ||
3794                     strncmp(why, "Continuing ", 11) == 0) {
3795                     gs_gamenum = gamenum;
3796                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3797                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3798 #if ZIPPY
3799                     if (appData.zippyPlay) {
3800                         ZippyGameStart(whitename, blackname);
3801                     }
3802 #endif /*ZIPPY*/
3803                     partnerBoardValid = FALSE; // [HGM] bughouse
3804                     continue;
3805                 }
3806
3807                 /* Game end messages */
3808                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3809                     ics_gamenum != gamenum) {
3810                     continue;
3811                 }
3812                 while (endtoken[0] == ' ') endtoken++;
3813                 switch (endtoken[0]) {
3814                   case '*':
3815                   default:
3816                     endtype = GameUnfinished;
3817                     break;
3818                   case '0':
3819                     endtype = BlackWins;
3820                     break;
3821                   case '1':
3822                     if (endtoken[1] == '/')
3823                       endtype = GameIsDrawn;
3824                     else
3825                       endtype = WhiteWins;
3826                     break;
3827                 }
3828                 GameEnds(endtype, why, GE_ICS);
3829 #if ZIPPY
3830                 if (appData.zippyPlay && first.initDone) {
3831                     ZippyGameEnd(endtype, why);
3832                     if (first.pr == NULL) {
3833                       /* Start the next process early so that we'll
3834                          be ready for the next challenge */
3835                       StartChessProgram(&first);
3836                     }
3837                     /* Send "new" early, in case this command takes
3838                        a long time to finish, so that we'll be ready
3839                        for the next challenge. */
3840                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3841                     Reset(TRUE, TRUE);
3842                 }
3843 #endif /*ZIPPY*/
3844                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3845                 continue;
3846             }
3847
3848             if (looking_at(buf, &i, "Removing game * from observation") ||
3849                 looking_at(buf, &i, "no longer observing game *") ||
3850                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3851                 if (gameMode == IcsObserving &&
3852                     atoi(star_match[0]) == ics_gamenum)
3853                   {
3854                       /* icsEngineAnalyze */
3855                       if (appData.icsEngineAnalyze) {
3856                             ExitAnalyzeMode();
3857                             ModeHighlight();
3858                       }
3859                       StopClocks();
3860                       gameMode = IcsIdle;
3861                       ics_gamenum = -1;
3862                       ics_user_moved = FALSE;
3863                   }
3864                 continue;
3865             }
3866
3867             if (looking_at(buf, &i, "no longer examining game *")) {
3868                 if (gameMode == IcsExamining &&
3869                     atoi(star_match[0]) == ics_gamenum)
3870                   {
3871                       gameMode = IcsIdle;
3872                       ics_gamenum = -1;
3873                       ics_user_moved = FALSE;
3874                   }
3875                 continue;
3876             }
3877
3878             /* Advance leftover_start past any newlines we find,
3879                so only partial lines can get reparsed */
3880             if (looking_at(buf, &i, "\n")) {
3881                 prevColor = curColor;
3882                 if (curColor != ColorNormal) {
3883                     if (oldi > next_out) {
3884                         SendToPlayer(&buf[next_out], oldi - next_out);
3885                         next_out = oldi;
3886                     }
3887                     Colorize(ColorNormal, FALSE);
3888                     curColor = ColorNormal;
3889                 }
3890                 if (started == STARTED_BOARD) {
3891                     started = STARTED_NONE;
3892                     parse[parse_pos] = NULLCHAR;
3893                     ParseBoard12(parse);
3894                     ics_user_moved = 0;
3895
3896                     /* Send premove here */
3897                     if (appData.premove) {
3898                       char str[MSG_SIZ];
3899                       if (currentMove == 0 &&
3900                           gameMode == IcsPlayingWhite &&
3901                           appData.premoveWhite) {
3902                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3903                         if (appData.debugMode)
3904                           fprintf(debugFP, "Sending premove:\n");
3905                         SendToICS(str);
3906                       } else if (currentMove == 1 &&
3907                                  gameMode == IcsPlayingBlack &&
3908                                  appData.premoveBlack) {
3909                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3910                         if (appData.debugMode)
3911                           fprintf(debugFP, "Sending premove:\n");
3912                         SendToICS(str);
3913                       } else if (gotPremove) {
3914                         gotPremove = 0;
3915                         ClearPremoveHighlights();
3916                         if (appData.debugMode)
3917                           fprintf(debugFP, "Sending premove:\n");
3918                           UserMoveEvent(premoveFromX, premoveFromY,
3919                                         premoveToX, premoveToY,
3920                                         premovePromoChar);
3921                       }
3922                     }
3923
3924                     /* Usually suppress following prompt */
3925                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3926                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3927                         if (looking_at(buf, &i, "*% ")) {
3928                             savingComment = FALSE;
3929                             suppressKibitz = 0;
3930                         }
3931                     }
3932                     next_out = i;
3933                 } else if (started == STARTED_HOLDINGS) {
3934                     int gamenum;
3935                     char new_piece[MSG_SIZ];
3936                     started = STARTED_NONE;
3937                     parse[parse_pos] = NULLCHAR;
3938                     if (appData.debugMode)
3939                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3940                                                         parse, currentMove);
3941                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3942                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3943                         if (gameInfo.variant == VariantNormal) {
3944                           /* [HGM] We seem to switch variant during a game!
3945                            * Presumably no holdings were displayed, so we have
3946                            * to move the position two files to the right to
3947                            * create room for them!
3948                            */
3949                           VariantClass newVariant;
3950                           switch(gameInfo.boardWidth) { // base guess on board width
3951                                 case 9:  newVariant = VariantShogi; break;
3952                                 case 10: newVariant = VariantGreat; break;
3953                                 default: newVariant = VariantCrazyhouse; break;
3954                           }
3955                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3956                           /* Get a move list just to see the header, which
3957                              will tell us whether this is really bug or zh */
3958                           if (ics_getting_history == H_FALSE) {
3959                             ics_getting_history = H_REQUESTED;
3960                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3961                             SendToICS(str);
3962                           }
3963                         }
3964                         new_piece[0] = NULLCHAR;
3965                         sscanf(parse, "game %d white [%s black [%s <- %s",
3966                                &gamenum, white_holding, black_holding,
3967                                new_piece);
3968                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3969                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3970                         /* [HGM] copy holdings to board holdings area */
3971                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3972                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3973                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3974 #if ZIPPY
3975                         if (appData.zippyPlay && first.initDone) {
3976                             ZippyHoldings(white_holding, black_holding,
3977                                           new_piece);
3978                         }
3979 #endif /*ZIPPY*/
3980                         if (tinyLayout || smallLayout) {
3981                             char wh[16], bh[16];
3982                             PackHolding(wh, white_holding);
3983                             PackHolding(bh, black_holding);
3984                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3985                                     gameInfo.white, gameInfo.black);
3986                         } else {
3987                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3988                                     gameInfo.white, white_holding,
3989                                     gameInfo.black, black_holding);
3990                         }
3991                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3992                         DrawPosition(FALSE, boards[currentMove]);
3993                         DisplayTitle(str);
3994                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3995                         sscanf(parse, "game %d white [%s black [%s <- %s",
3996                                &gamenum, white_holding, black_holding,
3997                                new_piece);
3998                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3999                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4000                         /* [HGM] copy holdings to partner-board holdings area */
4001                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4002                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4003                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4004                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4005                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4006                       }
4007                     }
4008                     /* Suppress following prompt */
4009                     if (looking_at(buf, &i, "*% ")) {
4010                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4011                         savingComment = FALSE;
4012                         suppressKibitz = 0;
4013                     }
4014                     next_out = i;
4015                 }
4016                 continue;
4017             }
4018
4019             i++;                /* skip unparsed character and loop back */
4020         }
4021
4022         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4023 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4024 //          SendToPlayer(&buf[next_out], i - next_out);
4025             started != STARTED_HOLDINGS && leftover_start > next_out) {
4026             SendToPlayer(&buf[next_out], leftover_start - next_out);
4027             next_out = i;
4028         }
4029
4030         leftover_len = buf_len - leftover_start;
4031         /* if buffer ends with something we couldn't parse,
4032            reparse it after appending the next read */
4033
4034     } else if (count == 0) {
4035         RemoveInputSource(isr);
4036         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4037     } else {
4038         DisplayFatalError(_("Error reading from ICS"), error, 1);
4039     }
4040 }
4041
4042
4043 /* Board style 12 looks like this:
4044
4045    <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
4046
4047  * The "<12> " is stripped before it gets to this routine.  The two
4048  * trailing 0's (flip state and clock ticking) are later addition, and
4049  * some chess servers may not have them, or may have only the first.
4050  * Additional trailing fields may be added in the future.
4051  */
4052
4053 #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"
4054
4055 #define RELATION_OBSERVING_PLAYED    0
4056 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4057 #define RELATION_PLAYING_MYMOVE      1
4058 #define RELATION_PLAYING_NOTMYMOVE  -1
4059 #define RELATION_EXAMINING           2
4060 #define RELATION_ISOLATED_BOARD     -3
4061 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4062
4063 void
4064 ParseBoard12(string)
4065      char *string;
4066 {
4067     GameMode newGameMode;
4068     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4069     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4070     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4071     char to_play, board_chars[200];
4072     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4073     char black[32], white[32];
4074     Board board;
4075     int prevMove = currentMove;
4076     int ticking = 2;
4077     ChessMove moveType;
4078     int fromX, fromY, toX, toY;
4079     char promoChar;
4080     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4081     char *bookHit = NULL; // [HGM] book
4082     Boolean weird = FALSE, reqFlag = FALSE;
4083
4084     fromX = fromY = toX = toY = -1;
4085
4086     newGame = FALSE;
4087
4088     if (appData.debugMode)
4089       fprintf(debugFP, _("Parsing board: %s\n"), string);
4090
4091     move_str[0] = NULLCHAR;
4092     elapsed_time[0] = NULLCHAR;
4093     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4094         int  i = 0, j;
4095         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4096             if(string[i] == ' ') { ranks++; files = 0; }
4097             else files++;
4098             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4099             i++;
4100         }
4101         for(j = 0; j <i; j++) board_chars[j] = string[j];
4102         board_chars[i] = '\0';
4103         string += i + 1;
4104     }
4105     n = sscanf(string, PATTERN, &to_play, &double_push,
4106                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4107                &gamenum, white, black, &relation, &basetime, &increment,
4108                &white_stren, &black_stren, &white_time, &black_time,
4109                &moveNum, str, elapsed_time, move_str, &ics_flip,
4110                &ticking);
4111
4112     if (n < 21) {
4113         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4114         DisplayError(str, 0);
4115         return;
4116     }
4117
4118     /* Convert the move number to internal form */
4119     moveNum = (moveNum - 1) * 2;
4120     if (to_play == 'B') moveNum++;
4121     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4122       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4123                         0, 1);
4124       return;
4125     }
4126
4127     switch (relation) {
4128       case RELATION_OBSERVING_PLAYED:
4129       case RELATION_OBSERVING_STATIC:
4130         if (gamenum == -1) {
4131             /* Old ICC buglet */
4132             relation = RELATION_OBSERVING_STATIC;
4133         }
4134         newGameMode = IcsObserving;
4135         break;
4136       case RELATION_PLAYING_MYMOVE:
4137       case RELATION_PLAYING_NOTMYMOVE:
4138         newGameMode =
4139           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4140             IcsPlayingWhite : IcsPlayingBlack;
4141         break;
4142       case RELATION_EXAMINING:
4143         newGameMode = IcsExamining;
4144         break;
4145       case RELATION_ISOLATED_BOARD:
4146       default:
4147         /* Just display this board.  If user was doing something else,
4148            we will forget about it until the next board comes. */
4149         newGameMode = IcsIdle;
4150         break;
4151       case RELATION_STARTING_POSITION:
4152         newGameMode = gameMode;
4153         break;
4154     }
4155
4156     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4157          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4158       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4159       char *toSqr;
4160       for (k = 0; k < ranks; k++) {
4161         for (j = 0; j < files; j++)
4162           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4163         if(gameInfo.holdingsWidth > 1) {
4164              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4165              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4166         }
4167       }
4168       CopyBoard(partnerBoard, board);
4169       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4170         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4171         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4172       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4173       if(toSqr = strchr(str, '-')) {
4174         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4175         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4176       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4177       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4178       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4179       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4180       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4181       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4182                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4183       DisplayMessage(partnerStatus, "");
4184         partnerBoardValid = TRUE;
4185       return;
4186     }
4187
4188     /* Modify behavior for initial board display on move listing
4189        of wild games.
4190        */
4191     switch (ics_getting_history) {
4192       case H_FALSE:
4193       case H_REQUESTED:
4194         break;
4195       case H_GOT_REQ_HEADER:
4196       case H_GOT_UNREQ_HEADER:
4197         /* This is the initial position of the current game */
4198         gamenum = ics_gamenum;
4199         moveNum = 0;            /* old ICS bug workaround */
4200         if (to_play == 'B') {
4201           startedFromSetupPosition = TRUE;
4202           blackPlaysFirst = TRUE;
4203           moveNum = 1;
4204           if (forwardMostMove == 0) forwardMostMove = 1;
4205           if (backwardMostMove == 0) backwardMostMove = 1;
4206           if (currentMove == 0) currentMove = 1;
4207         }
4208         newGameMode = gameMode;
4209         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4210         break;
4211       case H_GOT_UNWANTED_HEADER:
4212         /* This is an initial board that we don't want */
4213         return;
4214       case H_GETTING_MOVES:
4215         /* Should not happen */
4216         DisplayError(_("Error gathering move list: extra board"), 0);
4217         ics_getting_history = H_FALSE;
4218         return;
4219     }
4220
4221    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4222                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4223      /* [HGM] We seem to have switched variant unexpectedly
4224       * Try to guess new variant from board size
4225       */
4226           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4227           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4228           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4229           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4230           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4231           if(!weird) newVariant = VariantNormal;
4232           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4233           /* Get a move list just to see the header, which
4234              will tell us whether this is really bug or zh */
4235           if (ics_getting_history == H_FALSE) {
4236             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4237             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4238             SendToICS(str);
4239           }
4240     }
4241
4242     /* Take action if this is the first board of a new game, or of a
4243        different game than is currently being displayed.  */
4244     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4245         relation == RELATION_ISOLATED_BOARD) {
4246
4247         /* Forget the old game and get the history (if any) of the new one */
4248         if (gameMode != BeginningOfGame) {
4249           Reset(TRUE, TRUE);
4250         }
4251         newGame = TRUE;
4252         if (appData.autoRaiseBoard) BoardToTop();
4253         prevMove = -3;
4254         if (gamenum == -1) {
4255             newGameMode = IcsIdle;
4256         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4257                    appData.getMoveList && !reqFlag) {
4258             /* Need to get game history */
4259             ics_getting_history = H_REQUESTED;
4260             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4261             SendToICS(str);
4262         }
4263
4264         /* Initially flip the board to have black on the bottom if playing
4265            black or if the ICS flip flag is set, but let the user change
4266            it with the Flip View button. */
4267         flipView = appData.autoFlipView ?
4268           (newGameMode == IcsPlayingBlack) || ics_flip :
4269           appData.flipView;
4270
4271         /* Done with values from previous mode; copy in new ones */
4272         gameMode = newGameMode;
4273         ModeHighlight();
4274         ics_gamenum = gamenum;
4275         if (gamenum == gs_gamenum) {
4276             int klen = strlen(gs_kind);
4277             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4278             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4279             gameInfo.event = StrSave(str);
4280         } else {
4281             gameInfo.event = StrSave("ICS game");
4282         }
4283         gameInfo.site = StrSave(appData.icsHost);
4284         gameInfo.date = PGNDate();
4285         gameInfo.round = StrSave("-");
4286         gameInfo.white = StrSave(white);
4287         gameInfo.black = StrSave(black);
4288         timeControl = basetime * 60 * 1000;
4289         timeControl_2 = 0;
4290         timeIncrement = increment * 1000;
4291         movesPerSession = 0;
4292         gameInfo.timeControl = TimeControlTagValue();
4293         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4294   if (appData.debugMode) {
4295     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4296     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4297     setbuf(debugFP, NULL);
4298   }
4299
4300         gameInfo.outOfBook = NULL;
4301
4302         /* Do we have the ratings? */
4303         if (strcmp(player1Name, white) == 0 &&
4304             strcmp(player2Name, black) == 0) {
4305             if (appData.debugMode)
4306               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4307                       player1Rating, player2Rating);
4308             gameInfo.whiteRating = player1Rating;
4309             gameInfo.blackRating = player2Rating;
4310         } else if (strcmp(player2Name, white) == 0 &&
4311                    strcmp(player1Name, black) == 0) {
4312             if (appData.debugMode)
4313               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4314                       player2Rating, player1Rating);
4315             gameInfo.whiteRating = player2Rating;
4316             gameInfo.blackRating = player1Rating;
4317         }
4318         player1Name[0] = player2Name[0] = NULLCHAR;
4319
4320         /* Silence shouts if requested */
4321         if (appData.quietPlay &&
4322             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4323             SendToICS(ics_prefix);
4324             SendToICS("set shout 0\n");
4325         }
4326     }
4327
4328     /* Deal with midgame name changes */
4329     if (!newGame) {
4330         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4331             if (gameInfo.white) free(gameInfo.white);
4332             gameInfo.white = StrSave(white);
4333         }
4334         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4335             if (gameInfo.black) free(gameInfo.black);
4336             gameInfo.black = StrSave(black);
4337         }
4338     }
4339
4340     /* Throw away game result if anything actually changes in examine mode */
4341     if (gameMode == IcsExamining && !newGame) {
4342         gameInfo.result = GameUnfinished;
4343         if (gameInfo.resultDetails != NULL) {
4344             free(gameInfo.resultDetails);
4345             gameInfo.resultDetails = NULL;
4346         }
4347     }
4348
4349     /* In pausing && IcsExamining mode, we ignore boards coming
4350        in if they are in a different variation than we are. */
4351     if (pauseExamInvalid) return;
4352     if (pausing && gameMode == IcsExamining) {
4353         if (moveNum <= pauseExamForwardMostMove) {
4354             pauseExamInvalid = TRUE;
4355             forwardMostMove = pauseExamForwardMostMove;
4356             return;
4357         }
4358     }
4359
4360   if (appData.debugMode) {
4361     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4362   }
4363     /* Parse the board */
4364     for (k = 0; k < ranks; k++) {
4365       for (j = 0; j < files; j++)
4366         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4367       if(gameInfo.holdingsWidth > 1) {
4368            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4369            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4370       }
4371     }
4372     CopyBoard(boards[moveNum], board);
4373     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4374     if (moveNum == 0) {
4375         startedFromSetupPosition =
4376           !CompareBoards(board, initialPosition);
4377         if(startedFromSetupPosition)
4378             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4379     }
4380
4381     /* [HGM] Set castling rights. Take the outermost Rooks,
4382        to make it also work for FRC opening positions. Note that board12
4383        is really defective for later FRC positions, as it has no way to
4384        indicate which Rook can castle if they are on the same side of King.
4385        For the initial position we grant rights to the outermost Rooks,
4386        and remember thos rights, and we then copy them on positions
4387        later in an FRC game. This means WB might not recognize castlings with
4388        Rooks that have moved back to their original position as illegal,
4389        but in ICS mode that is not its job anyway.
4390     */
4391     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4392     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4393
4394         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4395             if(board[0][i] == WhiteRook) j = i;
4396         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4397         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4398             if(board[0][i] == WhiteRook) j = i;
4399         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4400         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4401             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4402         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4403         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4404             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4405         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4406
4407         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4408         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4409             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4410         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4411             if(board[BOARD_HEIGHT-1][k] == bKing)
4412                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4413         if(gameInfo.variant == VariantTwoKings) {
4414             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4415             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4416             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4417         }
4418     } else { int r;
4419         r = boards[moveNum][CASTLING][0] = initialRights[0];
4420         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4421         r = boards[moveNum][CASTLING][1] = initialRights[1];
4422         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4423         r = boards[moveNum][CASTLING][3] = initialRights[3];
4424         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4425         r = boards[moveNum][CASTLING][4] = initialRights[4];
4426         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4427         /* wildcastle kludge: always assume King has rights */
4428         r = boards[moveNum][CASTLING][2] = initialRights[2];
4429         r = boards[moveNum][CASTLING][5] = initialRights[5];
4430     }
4431     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4432     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4433
4434
4435     if (ics_getting_history == H_GOT_REQ_HEADER ||
4436         ics_getting_history == H_GOT_UNREQ_HEADER) {
4437         /* This was an initial position from a move list, not
4438            the current position */
4439         return;
4440     }
4441
4442     /* Update currentMove and known move number limits */
4443     newMove = newGame || moveNum > forwardMostMove;
4444
4445     if (newGame) {
4446         forwardMostMove = backwardMostMove = currentMove = moveNum;
4447         if (gameMode == IcsExamining && moveNum == 0) {
4448           /* Workaround for ICS limitation: we are not told the wild
4449              type when starting to examine a game.  But if we ask for
4450              the move list, the move list header will tell us */
4451             ics_getting_history = H_REQUESTED;
4452             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4453             SendToICS(str);
4454         }
4455     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4456                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4457 #if ZIPPY
4458         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4459         /* [HGM] applied this also to an engine that is silently watching        */
4460         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4461             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4462             gameInfo.variant == currentlyInitializedVariant) {
4463           takeback = forwardMostMove - moveNum;
4464           for (i = 0; i < takeback; i++) {
4465             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4466             SendToProgram("undo\n", &first);
4467           }
4468         }
4469 #endif
4470
4471         forwardMostMove = moveNum;
4472         if (!pausing || currentMove > forwardMostMove)
4473           currentMove = forwardMostMove;
4474     } else {
4475         /* New part of history that is not contiguous with old part */
4476         if (pausing && gameMode == IcsExamining) {
4477             pauseExamInvalid = TRUE;
4478             forwardMostMove = pauseExamForwardMostMove;
4479             return;
4480         }
4481         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4482 #if ZIPPY
4483             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4484                 // [HGM] when we will receive the move list we now request, it will be
4485                 // fed to the engine from the first move on. So if the engine is not
4486                 // in the initial position now, bring it there.
4487                 InitChessProgram(&first, 0);
4488             }
4489 #endif
4490             ics_getting_history = H_REQUESTED;
4491             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4492             SendToICS(str);
4493         }
4494         forwardMostMove = backwardMostMove = currentMove = moveNum;
4495     }
4496
4497     /* Update the clocks */
4498     if (strchr(elapsed_time, '.')) {
4499       /* Time is in ms */
4500       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4501       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4502     } else {
4503       /* Time is in seconds */
4504       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4505       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4506     }
4507
4508
4509 #if ZIPPY
4510     if (appData.zippyPlay && newGame &&
4511         gameMode != IcsObserving && gameMode != IcsIdle &&
4512         gameMode != IcsExamining)
4513       ZippyFirstBoard(moveNum, basetime, increment);
4514 #endif
4515
4516     /* Put the move on the move list, first converting
4517        to canonical algebraic form. */
4518     if (moveNum > 0) {
4519   if (appData.debugMode) {
4520     if (appData.debugMode) { int f = forwardMostMove;
4521         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4522                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4523                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4524     }
4525     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4526     fprintf(debugFP, "moveNum = %d\n", moveNum);
4527     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4528     setbuf(debugFP, NULL);
4529   }
4530         if (moveNum <= backwardMostMove) {
4531             /* We don't know what the board looked like before
4532                this move.  Punt. */
4533           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4534             strcat(parseList[moveNum - 1], " ");
4535             strcat(parseList[moveNum - 1], elapsed_time);
4536             moveList[moveNum - 1][0] = NULLCHAR;
4537         } else if (strcmp(move_str, "none") == 0) {
4538             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4539             /* Again, we don't know what the board looked like;
4540                this is really the start of the game. */
4541             parseList[moveNum - 1][0] = NULLCHAR;
4542             moveList[moveNum - 1][0] = NULLCHAR;
4543             backwardMostMove = moveNum;
4544             startedFromSetupPosition = TRUE;
4545             fromX = fromY = toX = toY = -1;
4546         } else {
4547           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4548           //                 So we parse the long-algebraic move string in stead of the SAN move
4549           int valid; char buf[MSG_SIZ], *prom;
4550
4551           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4552                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4553           // str looks something like "Q/a1-a2"; kill the slash
4554           if(str[1] == '/')
4555             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4556           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4557           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4558                 strcat(buf, prom); // long move lacks promo specification!
4559           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4560                 if(appData.debugMode)
4561                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4562                 safeStrCpy(move_str, buf, MSG_SIZ);
4563           }
4564           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4565                                 &fromX, &fromY, &toX, &toY, &promoChar)
4566                || ParseOneMove(buf, moveNum - 1, &moveType,
4567                                 &fromX, &fromY, &toX, &toY, &promoChar);
4568           // end of long SAN patch
4569           if (valid) {
4570             (void) CoordsToAlgebraic(boards[moveNum - 1],
4571                                      PosFlags(moveNum - 1),
4572                                      fromY, fromX, toY, toX, promoChar,
4573                                      parseList[moveNum-1]);
4574             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4575               case MT_NONE:
4576               case MT_STALEMATE:
4577               default:
4578                 break;
4579               case MT_CHECK:
4580                 if(gameInfo.variant != VariantShogi)
4581                     strcat(parseList[moveNum - 1], "+");
4582                 break;
4583               case MT_CHECKMATE:
4584               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4585                 strcat(parseList[moveNum - 1], "#");
4586                 break;
4587             }
4588             strcat(parseList[moveNum - 1], " ");
4589             strcat(parseList[moveNum - 1], elapsed_time);
4590             /* currentMoveString is set as a side-effect of ParseOneMove */
4591             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4592             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4593             strcat(moveList[moveNum - 1], "\n");
4594
4595             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4596                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4597               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4598                 ChessSquare old, new = boards[moveNum][k][j];
4599                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4600                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4601                   if(old == new) continue;
4602                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4603                   else if(new == WhiteWazir || new == BlackWazir) {
4604                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4605                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4606                       else boards[moveNum][k][j] = old; // preserve type of Gold
4607                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4608                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4609               }
4610           } else {
4611             /* Move from ICS was illegal!?  Punt. */
4612             if (appData.debugMode) {
4613               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4614               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4615             }
4616             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4617             strcat(parseList[moveNum - 1], " ");
4618             strcat(parseList[moveNum - 1], elapsed_time);
4619             moveList[moveNum - 1][0] = NULLCHAR;
4620             fromX = fromY = toX = toY = -1;
4621           }
4622         }
4623   if (appData.debugMode) {
4624     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4625     setbuf(debugFP, NULL);
4626   }
4627
4628 #if ZIPPY
4629         /* Send move to chess program (BEFORE animating it). */
4630         if (appData.zippyPlay && !newGame && newMove &&
4631            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4632
4633             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4634                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4635                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4636                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4637                             move_str);
4638                     DisplayError(str, 0);
4639                 } else {
4640                     if (first.sendTime) {
4641                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4642                     }
4643                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4644                     if (firstMove && !bookHit) {
4645                         firstMove = FALSE;
4646                         if (first.useColors) {
4647                           SendToProgram(gameMode == IcsPlayingWhite ?
4648                                         "white\ngo\n" :
4649                                         "black\ngo\n", &first);
4650                         } else {
4651                           SendToProgram("go\n", &first);
4652                         }
4653                         first.maybeThinking = TRUE;
4654                     }
4655                 }
4656             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4657               if (moveList[moveNum - 1][0] == NULLCHAR) {
4658                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4659                 DisplayError(str, 0);
4660               } else {
4661                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4662                 SendMoveToProgram(moveNum - 1, &first);
4663               }
4664             }
4665         }
4666 #endif
4667     }
4668
4669     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4670         /* If move comes from a remote source, animate it.  If it
4671            isn't remote, it will have already been animated. */
4672         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4673             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4674         }
4675         if (!pausing && appData.highlightLastMove) {
4676             SetHighlights(fromX, fromY, toX, toY);
4677         }
4678     }
4679
4680     /* Start the clocks */
4681     whiteFlag = blackFlag = FALSE;
4682     appData.clockMode = !(basetime == 0 && increment == 0);
4683     if (ticking == 0) {
4684       ics_clock_paused = TRUE;
4685       StopClocks();
4686     } else if (ticking == 1) {
4687       ics_clock_paused = FALSE;
4688     }
4689     if (gameMode == IcsIdle ||
4690         relation == RELATION_OBSERVING_STATIC ||
4691         relation == RELATION_EXAMINING ||
4692         ics_clock_paused)
4693       DisplayBothClocks();
4694     else
4695       StartClocks();
4696
4697     /* Display opponents and material strengths */
4698     if (gameInfo.variant != VariantBughouse &&
4699         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4700         if (tinyLayout || smallLayout) {
4701             if(gameInfo.variant == VariantNormal)
4702               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4703                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4704                     basetime, increment);
4705             else
4706               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4707                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4708                     basetime, increment, (int) gameInfo.variant);
4709         } else {
4710             if(gameInfo.variant == VariantNormal)
4711               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4712                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4713                     basetime, increment);
4714             else
4715               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4716                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4717                     basetime, increment, VariantName(gameInfo.variant));
4718         }
4719         DisplayTitle(str);
4720   if (appData.debugMode) {
4721     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4722   }
4723     }
4724
4725
4726     /* Display the board */
4727     if (!pausing && !appData.noGUI) {
4728
4729       if (appData.premove)
4730           if (!gotPremove ||
4731              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4732              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4733               ClearPremoveHighlights();
4734
4735       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4736         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4737       DrawPosition(j, boards[currentMove]);
4738
4739       DisplayMove(moveNum - 1);
4740       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4741             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4742               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4743         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4744       }
4745     }
4746
4747     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4748 #if ZIPPY
4749     if(bookHit) { // [HGM] book: simulate book reply
4750         static char bookMove[MSG_SIZ]; // a bit generous?
4751
4752         programStats.nodes = programStats.depth = programStats.time =
4753         programStats.score = programStats.got_only_move = 0;
4754         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4755
4756         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4757         strcat(bookMove, bookHit);
4758         HandleMachineMove(bookMove, &first);
4759     }
4760 #endif
4761 }
4762
4763 void
4764 GetMoveListEvent()
4765 {
4766     char buf[MSG_SIZ];
4767     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4768         ics_getting_history = H_REQUESTED;
4769         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4770         SendToICS(buf);
4771     }
4772 }
4773
4774 void
4775 AnalysisPeriodicEvent(force)
4776      int force;
4777 {
4778     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4779          && !force) || !appData.periodicUpdates)
4780       return;
4781
4782     /* Send . command to Crafty to collect stats */
4783     SendToProgram(".\n", &first);
4784
4785     /* Don't send another until we get a response (this makes
4786        us stop sending to old Crafty's which don't understand
4787        the "." command (sending illegal cmds resets node count & time,
4788        which looks bad)) */
4789     programStats.ok_to_send = 0;
4790 }
4791
4792 void ics_update_width(new_width)
4793         int new_width;
4794 {
4795         ics_printf("set width %d\n", new_width);
4796 }
4797
4798 void
4799 SendMoveToProgram(moveNum, cps)
4800      int moveNum;
4801      ChessProgramState *cps;
4802 {
4803     char buf[MSG_SIZ];
4804
4805     if (cps->useUsermove) {
4806       SendToProgram("usermove ", cps);
4807     }
4808     if (cps->useSAN) {
4809       char *space;
4810       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4811         int len = space - parseList[moveNum];
4812         memcpy(buf, parseList[moveNum], len);
4813         buf[len++] = '\n';
4814         buf[len] = NULLCHAR;
4815       } else {
4816         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4817       }
4818       SendToProgram(buf, cps);
4819     } else {
4820       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4821         AlphaRank(moveList[moveNum], 4);
4822         SendToProgram(moveList[moveNum], cps);
4823         AlphaRank(moveList[moveNum], 4); // and back
4824       } else
4825       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4826        * the engine. It would be nice to have a better way to identify castle
4827        * moves here. */
4828       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4829                                                                          && cps->useOOCastle) {
4830         int fromX = moveList[moveNum][0] - AAA;
4831         int fromY = moveList[moveNum][1] - ONE;
4832         int toX = moveList[moveNum][2] - AAA;
4833         int toY = moveList[moveNum][3] - ONE;
4834         if((boards[moveNum][fromY][fromX] == WhiteKing
4835             && boards[moveNum][toY][toX] == WhiteRook)
4836            || (boards[moveNum][fromY][fromX] == BlackKing
4837                && boards[moveNum][toY][toX] == BlackRook)) {
4838           if(toX > fromX) SendToProgram("O-O\n", cps);
4839           else SendToProgram("O-O-O\n", cps);
4840         }
4841         else SendToProgram(moveList[moveNum], cps);
4842       }
4843       else SendToProgram(moveList[moveNum], cps);
4844       /* End of additions by Tord */
4845     }
4846
4847     /* [HGM] setting up the opening has brought engine in force mode! */
4848     /*       Send 'go' if we are in a mode where machine should play. */
4849     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4850         (gameMode == TwoMachinesPlay   ||
4851 #if ZIPPY
4852          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4853 #endif
4854          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4855         SendToProgram("go\n", cps);
4856   if (appData.debugMode) {
4857     fprintf(debugFP, "(extra)\n");
4858   }
4859     }
4860     setboardSpoiledMachineBlack = 0;
4861 }
4862
4863 void
4864 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4865      ChessMove moveType;
4866      int fromX, fromY, toX, toY;
4867      char promoChar;
4868 {
4869     char user_move[MSG_SIZ];
4870
4871     switch (moveType) {
4872       default:
4873         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4874                 (int)moveType, fromX, fromY, toX, toY);
4875         DisplayError(user_move + strlen("say "), 0);
4876         break;
4877       case WhiteKingSideCastle:
4878       case BlackKingSideCastle:
4879       case WhiteQueenSideCastleWild:
4880       case BlackQueenSideCastleWild:
4881       /* PUSH Fabien */
4882       case WhiteHSideCastleFR:
4883       case BlackHSideCastleFR:
4884       /* POP Fabien */
4885         snprintf(user_move, MSG_SIZ, "o-o\n");
4886         break;
4887       case WhiteQueenSideCastle:
4888       case BlackQueenSideCastle:
4889       case WhiteKingSideCastleWild:
4890       case BlackKingSideCastleWild:
4891       /* PUSH Fabien */
4892       case WhiteASideCastleFR:
4893       case BlackASideCastleFR:
4894       /* POP Fabien */
4895         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4896         break;
4897       case WhiteNonPromotion:
4898       case BlackNonPromotion:
4899         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4900         break;
4901       case WhitePromotion:
4902       case BlackPromotion:
4903         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4904           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4905                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4906                 PieceToChar(WhiteFerz));
4907         else if(gameInfo.variant == VariantGreat)
4908           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4909                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4910                 PieceToChar(WhiteMan));
4911         else
4912           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4913                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4914                 promoChar);
4915         break;
4916       case WhiteDrop:
4917       case BlackDrop:
4918       drop:
4919         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4920                  ToUpper(PieceToChar((ChessSquare) fromX)),
4921                  AAA + toX, ONE + toY);
4922         break;
4923       case IllegalMove:  /* could be a variant we don't quite understand */
4924         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4925       case NormalMove:
4926       case WhiteCapturesEnPassant:
4927       case BlackCapturesEnPassant:
4928         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4929                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4930         break;
4931     }
4932     SendToICS(user_move);
4933     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4934         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4935 }
4936
4937 void
4938 UploadGameEvent()
4939 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4940     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4941     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4942     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4943         DisplayError("You cannot do this while you are playing or observing", 0);
4944         return;
4945     }
4946     if(gameMode != IcsExamining) { // is this ever not the case?
4947         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4948
4949         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4950           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4951         } else { // on FICS we must first go to general examine mode
4952           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4953         }
4954         if(gameInfo.variant != VariantNormal) {
4955             // try figure out wild number, as xboard names are not always valid on ICS
4956             for(i=1; i<=36; i++) {
4957               snprintf(buf, MSG_SIZ, "wild/%d", i);
4958                 if(StringToVariant(buf) == gameInfo.variant) break;
4959             }
4960             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4961             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4962             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4963         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4964         SendToICS(ics_prefix);
4965         SendToICS(buf);
4966         if(startedFromSetupPosition || backwardMostMove != 0) {
4967           fen = PositionToFEN(backwardMostMove, NULL);
4968           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4969             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4970             SendToICS(buf);
4971           } else { // FICS: everything has to set by separate bsetup commands
4972             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4973             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4974             SendToICS(buf);
4975             if(!WhiteOnMove(backwardMostMove)) {
4976                 SendToICS("bsetup tomove black\n");
4977             }
4978             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4979             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4980             SendToICS(buf);
4981             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4982             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4983             SendToICS(buf);
4984             i = boards[backwardMostMove][EP_STATUS];
4985             if(i >= 0) { // set e.p.
4986               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4987                 SendToICS(buf);
4988             }
4989             bsetup++;
4990           }
4991         }
4992       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4993             SendToICS("bsetup done\n"); // switch to normal examining.
4994     }
4995     for(i = backwardMostMove; i<last; i++) {
4996         char buf[20];
4997         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4998         SendToICS(buf);
4999     }
5000     SendToICS(ics_prefix);
5001     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5002 }
5003
5004 void
5005 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5006      int rf, ff, rt, ft;
5007      char promoChar;
5008      char move[7];
5009 {
5010     if (rf == DROP_RANK) {
5011       sprintf(move, "%c@%c%c\n",
5012                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5013     } else {
5014         if (promoChar == 'x' || promoChar == NULLCHAR) {
5015           sprintf(move, "%c%c%c%c\n",
5016                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5017         } else {
5018             sprintf(move, "%c%c%c%c%c\n",
5019                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5020         }
5021     }
5022 }
5023
5024 void
5025 ProcessICSInitScript(f)
5026      FILE *f;
5027 {
5028     char buf[MSG_SIZ];
5029
5030     while (fgets(buf, MSG_SIZ, f)) {
5031         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5032     }
5033
5034     fclose(f);
5035 }
5036
5037
5038 static int lastX, lastY, selectFlag, dragging;
5039
5040 void
5041 Sweep(int step)
5042 {
5043     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5044     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5045     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5046     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5047     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5048     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5049     do {
5050         promoSweep -= step;
5051         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5052         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5053         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5054         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5055         if(!step) step = 1;
5056     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5057             appData.testLegality && (promoSweep == king ||
5058             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5059     ChangeDragPiece(promoSweep);
5060 }
5061
5062 int PromoScroll(int x, int y)
5063 {
5064   int step = 0;
5065
5066   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5067   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5068   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5069   if(!step) return FALSE;
5070   lastX = x; lastY = y;
5071   if((promoSweep < BlackPawn) == flipView) step = -step;
5072   if(step > 0) selectFlag = 1;
5073   if(!selectFlag) Sweep(step);
5074   return FALSE;
5075 }
5076
5077 void
5078 NextPiece(int step)
5079 {
5080     ChessSquare piece = boards[currentMove][toY][toX];
5081     do {
5082         pieceSweep -= step;
5083         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5084         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5085         if(!step) step = -1;
5086     } while(PieceToChar(pieceSweep) == '.');
5087     boards[currentMove][toY][toX] = pieceSweep;
5088     DrawPosition(FALSE, boards[currentMove]);
5089     boards[currentMove][toY][toX] = piece;
5090 }
5091 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5092 void
5093 AlphaRank(char *move, int n)
5094 {
5095 //    char *p = move, c; int x, y;
5096
5097     if (appData.debugMode) {
5098         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5099     }
5100
5101     if(move[1]=='*' &&
5102        move[2]>='0' && move[2]<='9' &&
5103        move[3]>='a' && move[3]<='x'    ) {
5104         move[1] = '@';
5105         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5106         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5107     } else
5108     if(move[0]>='0' && move[0]<='9' &&
5109        move[1]>='a' && move[1]<='x' &&
5110        move[2]>='0' && move[2]<='9' &&
5111        move[3]>='a' && move[3]<='x'    ) {
5112         /* input move, Shogi -> normal */
5113         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5114         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5115         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5116         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5117     } else
5118     if(move[1]=='@' &&
5119        move[3]>='0' && move[3]<='9' &&
5120        move[2]>='a' && move[2]<='x'    ) {
5121         move[1] = '*';
5122         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5123         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5124     } else
5125     if(
5126        move[0]>='a' && move[0]<='x' &&
5127        move[3]>='0' && move[3]<='9' &&
5128        move[2]>='a' && move[2]<='x'    ) {
5129          /* output move, normal -> Shogi */
5130         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5131         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5132         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5133         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5134         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5135     }
5136     if (appData.debugMode) {
5137         fprintf(debugFP, "   out = '%s'\n", move);
5138     }
5139 }
5140
5141 char yy_textstr[8000];
5142
5143 /* Parser for moves from gnuchess, ICS, or user typein box */
5144 Boolean
5145 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5146      char *move;
5147      int moveNum;
5148      ChessMove *moveType;
5149      int *fromX, *fromY, *toX, *toY;
5150      char *promoChar;
5151 {
5152     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5153
5154     switch (*moveType) {
5155       case WhitePromotion:
5156       case BlackPromotion:
5157       case WhiteNonPromotion:
5158       case BlackNonPromotion:
5159       case NormalMove:
5160       case WhiteCapturesEnPassant:
5161       case BlackCapturesEnPassant:
5162       case WhiteKingSideCastle:
5163       case WhiteQueenSideCastle:
5164       case BlackKingSideCastle:
5165       case BlackQueenSideCastle:
5166       case WhiteKingSideCastleWild:
5167       case WhiteQueenSideCastleWild:
5168       case BlackKingSideCastleWild:
5169       case BlackQueenSideCastleWild:
5170       /* Code added by Tord: */
5171       case WhiteHSideCastleFR:
5172       case WhiteASideCastleFR:
5173       case BlackHSideCastleFR:
5174       case BlackASideCastleFR:
5175       /* End of code added by Tord */
5176       case IllegalMove:         /* bug or odd chess variant */
5177         *fromX = currentMoveString[0] - AAA;
5178         *fromY = currentMoveString[1] - ONE;
5179         *toX = currentMoveString[2] - AAA;
5180         *toY = currentMoveString[3] - ONE;
5181         *promoChar = currentMoveString[4];
5182         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5183             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5184     if (appData.debugMode) {
5185         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5186     }
5187             *fromX = *fromY = *toX = *toY = 0;
5188             return FALSE;
5189         }
5190         if (appData.testLegality) {
5191           return (*moveType != IllegalMove);
5192         } else {
5193           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5194                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5195         }
5196
5197       case WhiteDrop:
5198       case BlackDrop:
5199         *fromX = *moveType == WhiteDrop ?
5200           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5201           (int) CharToPiece(ToLower(currentMoveString[0]));
5202         *fromY = DROP_RANK;
5203         *toX = currentMoveString[2] - AAA;
5204         *toY = currentMoveString[3] - ONE;
5205         *promoChar = NULLCHAR;
5206         return TRUE;
5207
5208       case AmbiguousMove:
5209       case ImpossibleMove:
5210       case EndOfFile:
5211       case ElapsedTime:
5212       case Comment:
5213       case PGNTag:
5214       case NAG:
5215       case WhiteWins:
5216       case BlackWins:
5217       case GameIsDrawn:
5218       default:
5219     if (appData.debugMode) {
5220         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5221     }
5222         /* bug? */
5223         *fromX = *fromY = *toX = *toY = 0;
5224         *promoChar = NULLCHAR;
5225         return FALSE;
5226     }
5227 }
5228
5229 Boolean pushed = FALSE;
5230
5231 void
5232 ParsePV(char *pv, Boolean storeComments)
5233 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5234   int fromX, fromY, toX, toY; char promoChar;
5235   ChessMove moveType;
5236   Boolean valid;
5237   int nr = 0;
5238
5239   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5240     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5241     pushed = TRUE;
5242   }
5243   endPV = forwardMostMove;
5244   do {
5245     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5246     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5247     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5248 if(appData.debugMode){
5249 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);
5250 }
5251     if(!valid && nr == 0 &&
5252        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5253         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5254         // Hande case where played move is different from leading PV move
5255         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5256         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5257         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5258         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5259           endPV += 2; // if position different, keep this
5260           moveList[endPV-1][0] = fromX + AAA;
5261           moveList[endPV-1][1] = fromY + ONE;
5262           moveList[endPV-1][2] = toX + AAA;
5263           moveList[endPV-1][3] = toY + ONE;
5264           parseList[endPV-1][0] = NULLCHAR;
5265           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5266         }
5267       }
5268     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5269     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5270     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5271     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5272         valid++; // allow comments in PV
5273         continue;
5274     }
5275     nr++;
5276     if(endPV+1 > framePtr) break; // no space, truncate
5277     if(!valid) break;
5278     endPV++;
5279     CopyBoard(boards[endPV], boards[endPV-1]);
5280     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5281     moveList[endPV-1][0] = fromX + AAA;
5282     moveList[endPV-1][1] = fromY + ONE;
5283     moveList[endPV-1][2] = toX + AAA;
5284     moveList[endPV-1][3] = toY + ONE;
5285     moveList[endPV-1][4] = promoChar;
5286     moveList[endPV-1][5] = NULLCHAR;
5287     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5288     if(storeComments)
5289         CoordsToAlgebraic(boards[endPV - 1],
5290                              PosFlags(endPV - 1),
5291                              fromY, fromX, toY, toX, promoChar,
5292                              parseList[endPV - 1]);
5293     else
5294         parseList[endPV-1][0] = NULLCHAR;
5295   } while(valid);
5296   currentMove = endPV;
5297   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5298   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5299                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5300   DrawPosition(TRUE, boards[currentMove]);
5301 }
5302
5303 Boolean
5304 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5305 {
5306         int startPV;
5307         char *p;
5308
5309         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5310         lastX = x; lastY = y;
5311         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5312         startPV = index;
5313         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5314         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5315         index = startPV;
5316         do{ while(buf[index] && buf[index] != '\n') index++;
5317         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5318         buf[index] = 0;
5319         ParsePV(buf+startPV, FALSE);
5320         *start = startPV; *end = index-1;
5321         return TRUE;
5322 }
5323
5324 Boolean
5325 LoadPV(int x, int y)
5326 { // called on right mouse click to load PV
5327   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5328   lastX = x; lastY = y;
5329   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5330   return TRUE;
5331 }
5332
5333 void
5334 UnLoadPV()
5335 {
5336   if(endPV < 0) return;
5337   endPV = -1;
5338   currentMove = forwardMostMove;
5339   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5340   ClearPremoveHighlights();
5341   DrawPosition(TRUE, boards[currentMove]);
5342 }
5343
5344 void
5345 MovePV(int x, int y, int h)
5346 { // step through PV based on mouse coordinates (called on mouse move)
5347   int margin = h>>3, step = 0;
5348
5349   // we must somehow check if right button is still down (might be released off board!)
5350   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5351   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5352   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5353   if(!step) return;
5354   lastX = x; lastY = y;
5355
5356   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5357   if(endPV < 0) return;
5358   if(y < margin) step = 1; else
5359   if(y > h - margin) step = -1;
5360   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5361   currentMove += step;
5362   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5363   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5364                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5365   DrawPosition(FALSE, boards[currentMove]);
5366 }
5367
5368
5369 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5370 // All positions will have equal probability, but the current method will not provide a unique
5371 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5372 #define DARK 1
5373 #define LITE 2
5374 #define ANY 3
5375
5376 int squaresLeft[4];
5377 int piecesLeft[(int)BlackPawn];
5378 int seed, nrOfShuffles;
5379
5380 void GetPositionNumber()
5381 {       // sets global variable seed
5382         int i;
5383
5384         seed = appData.defaultFrcPosition;
5385         if(seed < 0) { // randomize based on time for negative FRC position numbers
5386                 for(i=0; i<50; i++) seed += random();
5387                 seed = random() ^ random() >> 8 ^ random() << 8;
5388                 if(seed<0) seed = -seed;
5389         }
5390 }
5391
5392 int put(Board board, int pieceType, int rank, int n, int shade)
5393 // put the piece on the (n-1)-th empty squares of the given shade
5394 {
5395         int i;
5396
5397         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5398                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5399                         board[rank][i] = (ChessSquare) pieceType;
5400                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5401                         squaresLeft[ANY]--;
5402                         piecesLeft[pieceType]--;
5403                         return i;
5404                 }
5405         }
5406         return -1;
5407 }
5408
5409
5410 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5411 // calculate where the next piece goes, (any empty square), and put it there
5412 {
5413         int i;
5414
5415         i = seed % squaresLeft[shade];
5416         nrOfShuffles *= squaresLeft[shade];
5417         seed /= squaresLeft[shade];
5418         put(board, pieceType, rank, i, shade);
5419 }
5420
5421 void AddTwoPieces(Board board, int pieceType, int rank)
5422 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5423 {
5424         int i, n=squaresLeft[ANY], j=n-1, k;
5425
5426         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5427         i = seed % k;  // pick one
5428         nrOfShuffles *= k;
5429         seed /= k;
5430         while(i >= j) i -= j--;
5431         j = n - 1 - j; i += j;
5432         put(board, pieceType, rank, j, ANY);
5433         put(board, pieceType, rank, i, ANY);
5434 }
5435
5436 void SetUpShuffle(Board board, int number)
5437 {
5438         int i, p, first=1;
5439
5440         GetPositionNumber(); nrOfShuffles = 1;
5441
5442         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5443         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5444         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5445
5446         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5447
5448         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5449             p = (int) board[0][i];
5450             if(p < (int) BlackPawn) piecesLeft[p] ++;
5451             board[0][i] = EmptySquare;
5452         }
5453
5454         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5455             // shuffles restricted to allow normal castling put KRR first
5456             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5457                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5458             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5459                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5460             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5461                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5462             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5463                 put(board, WhiteRook, 0, 0, ANY);
5464             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5465         }
5466
5467         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5468             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5469             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5470                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5471                 while(piecesLeft[p] >= 2) {
5472                     AddOnePiece(board, p, 0, LITE);
5473                     AddOnePiece(board, p, 0, DARK);
5474                 }
5475                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5476             }
5477
5478         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5479             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5480             // but we leave King and Rooks for last, to possibly obey FRC restriction
5481             if(p == (int)WhiteRook) continue;
5482             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5483             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5484         }
5485
5486         // now everything is placed, except perhaps King (Unicorn) and Rooks
5487
5488         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5489             // Last King gets castling rights
5490             while(piecesLeft[(int)WhiteUnicorn]) {
5491                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5492                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5493             }
5494
5495             while(piecesLeft[(int)WhiteKing]) {
5496                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5497                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5498             }
5499
5500
5501         } else {
5502             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5503             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5504         }
5505
5506         // Only Rooks can be left; simply place them all
5507         while(piecesLeft[(int)WhiteRook]) {
5508                 i = put(board, WhiteRook, 0, 0, ANY);
5509                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5510                         if(first) {
5511                                 first=0;
5512                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5513                         }
5514                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5515                 }
5516         }
5517         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5518             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5519         }
5520
5521         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5522 }
5523
5524 int SetCharTable( char *table, const char * map )
5525 /* [HGM] moved here from winboard.c because of its general usefulness */
5526 /*       Basically a safe strcpy that uses the last character as King */
5527 {
5528     int result = FALSE; int NrPieces;
5529
5530     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5531                     && NrPieces >= 12 && !(NrPieces&1)) {
5532         int i; /* [HGM] Accept even length from 12 to 34 */
5533
5534         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5535         for( i=0; i<NrPieces/2-1; i++ ) {
5536             table[i] = map[i];
5537             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5538         }
5539         table[(int) WhiteKing]  = map[NrPieces/2-1];
5540         table[(int) BlackKing]  = map[NrPieces-1];
5541
5542         result = TRUE;
5543     }
5544
5545     return result;
5546 }
5547
5548 void Prelude(Board board)
5549 {       // [HGM] superchess: random selection of exo-pieces
5550         int i, j, k; ChessSquare p;
5551         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5552
5553         GetPositionNumber(); // use FRC position number
5554
5555         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5556             SetCharTable(pieceToChar, appData.pieceToCharTable);
5557             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5558                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5559         }
5560
5561         j = seed%4;                 seed /= 4;
5562         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5563         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5564         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5565         j = seed%3 + (seed%3 >= j); seed /= 3;
5566         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5567         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5568         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5569         j = seed%3;                 seed /= 3;
5570         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5571         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5572         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5573         j = seed%2 + (seed%2 >= j); seed /= 2;
5574         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5575         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5576         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5577         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5578         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5579         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5580         put(board, exoPieces[0],    0, 0, ANY);
5581         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5582 }
5583
5584 void
5585 InitPosition(redraw)
5586      int redraw;
5587 {
5588     ChessSquare (* pieces)[BOARD_FILES];
5589     int i, j, pawnRow, overrule,
5590     oldx = gameInfo.boardWidth,
5591     oldy = gameInfo.boardHeight,
5592     oldh = gameInfo.holdingsWidth;
5593     static int oldv;
5594
5595     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5596
5597     /* [AS] Initialize pv info list [HGM] and game status */
5598     {
5599         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5600             pvInfoList[i].depth = 0;
5601             boards[i][EP_STATUS] = EP_NONE;
5602             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5603         }
5604
5605         initialRulePlies = 0; /* 50-move counter start */
5606
5607         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5608         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5609     }
5610
5611
5612     /* [HGM] logic here is completely changed. In stead of full positions */
5613     /* the initialized data only consist of the two backranks. The switch */
5614     /* selects which one we will use, which is than copied to the Board   */
5615     /* initialPosition, which for the rest is initialized by Pawns and    */
5616     /* empty squares. This initial position is then copied to boards[0],  */
5617     /* possibly after shuffling, so that it remains available.            */
5618
5619     gameInfo.holdingsWidth = 0; /* default board sizes */
5620     gameInfo.boardWidth    = 8;
5621     gameInfo.boardHeight   = 8;
5622     gameInfo.holdingsSize  = 0;
5623     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5624     for(i=0; i<BOARD_FILES-2; i++)
5625       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5626     initialPosition[EP_STATUS] = EP_NONE;
5627     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5628     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5629          SetCharTable(pieceNickName, appData.pieceNickNames);
5630     else SetCharTable(pieceNickName, "............");
5631     pieces = FIDEArray;
5632
5633     switch (gameInfo.variant) {
5634     case VariantFischeRandom:
5635       shuffleOpenings = TRUE;
5636     default:
5637       break;
5638     case VariantShatranj:
5639       pieces = ShatranjArray;
5640       nrCastlingRights = 0;
5641       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5642       break;
5643     case VariantMakruk:
5644       pieces = makrukArray;
5645       nrCastlingRights = 0;
5646       startedFromSetupPosition = TRUE;
5647       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5648       break;
5649     case VariantTwoKings:
5650       pieces = twoKingsArray;
5651       break;
5652     case VariantCapaRandom:
5653       shuffleOpenings = TRUE;
5654     case VariantCapablanca:
5655       pieces = CapablancaArray;
5656       gameInfo.boardWidth = 10;
5657       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5658       break;
5659     case VariantGothic:
5660       pieces = GothicArray;
5661       gameInfo.boardWidth = 10;
5662       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5663       break;
5664     case VariantSChess:
5665       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5666       gameInfo.holdingsSize = 7;
5667       break;
5668     case VariantJanus:
5669       pieces = JanusArray;
5670       gameInfo.boardWidth = 10;
5671       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5672       nrCastlingRights = 6;
5673         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5674         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5675         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5676         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5677         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5678         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5679       break;
5680     case VariantFalcon:
5681       pieces = FalconArray;
5682       gameInfo.boardWidth = 10;
5683       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5684       break;
5685     case VariantXiangqi:
5686       pieces = XiangqiArray;
5687       gameInfo.boardWidth  = 9;
5688       gameInfo.boardHeight = 10;
5689       nrCastlingRights = 0;
5690       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5691       break;
5692     case VariantShogi:
5693       pieces = ShogiArray;
5694       gameInfo.boardWidth  = 9;
5695       gameInfo.boardHeight = 9;
5696       gameInfo.holdingsSize = 7;
5697       nrCastlingRights = 0;
5698       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5699       break;
5700     case VariantCourier:
5701       pieces = CourierArray;
5702       gameInfo.boardWidth  = 12;
5703       nrCastlingRights = 0;
5704       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5705       break;
5706     case VariantKnightmate:
5707       pieces = KnightmateArray;
5708       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5709       break;
5710     case VariantSpartan:
5711       pieces = SpartanArray;
5712       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5713       break;
5714     case VariantFairy:
5715       pieces = fairyArray;
5716       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5717       break;
5718     case VariantGreat:
5719       pieces = GreatArray;
5720       gameInfo.boardWidth = 10;
5721       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5722       gameInfo.holdingsSize = 8;
5723       break;
5724     case VariantSuper:
5725       pieces = FIDEArray;
5726       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5727       gameInfo.holdingsSize = 8;
5728       startedFromSetupPosition = TRUE;
5729       break;
5730     case VariantCrazyhouse:
5731     case VariantBughouse:
5732       pieces = FIDEArray;
5733       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5734       gameInfo.holdingsSize = 5;
5735       break;
5736     case VariantWildCastle:
5737       pieces = FIDEArray;
5738       /* !!?shuffle with kings guaranteed to be on d or e file */
5739       shuffleOpenings = 1;
5740       break;
5741     case VariantNoCastle:
5742       pieces = FIDEArray;
5743       nrCastlingRights = 0;
5744       /* !!?unconstrained back-rank shuffle */
5745       shuffleOpenings = 1;
5746       break;
5747     }
5748
5749     overrule = 0;
5750     if(appData.NrFiles >= 0) {
5751         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5752         gameInfo.boardWidth = appData.NrFiles;
5753     }
5754     if(appData.NrRanks >= 0) {
5755         gameInfo.boardHeight = appData.NrRanks;
5756     }
5757     if(appData.holdingsSize >= 0) {
5758         i = appData.holdingsSize;
5759         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5760         gameInfo.holdingsSize = i;
5761     }
5762     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5763     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5764         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5765
5766     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5767     if(pawnRow < 1) pawnRow = 1;
5768     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5769
5770     /* User pieceToChar list overrules defaults */
5771     if(appData.pieceToCharTable != NULL)
5772         SetCharTable(pieceToChar, appData.pieceToCharTable);
5773
5774     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5775
5776         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5777             s = (ChessSquare) 0; /* account holding counts in guard band */
5778         for( i=0; i<BOARD_HEIGHT; i++ )
5779             initialPosition[i][j] = s;
5780
5781         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5782         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5783         initialPosition[pawnRow][j] = WhitePawn;
5784         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5785         if(gameInfo.variant == VariantXiangqi) {
5786             if(j&1) {
5787                 initialPosition[pawnRow][j] =
5788                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5789                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5790                    initialPosition[2][j] = WhiteCannon;
5791                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5792                 }
5793             }
5794         }
5795         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5796     }
5797     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5798
5799             j=BOARD_LEFT+1;
5800             initialPosition[1][j] = WhiteBishop;
5801             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5802             j=BOARD_RGHT-2;
5803             initialPosition[1][j] = WhiteRook;
5804             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5805     }
5806
5807     if( nrCastlingRights == -1) {
5808         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5809         /*       This sets default castling rights from none to normal corners   */
5810         /* Variants with other castling rights must set them themselves above    */
5811         nrCastlingRights = 6;
5812
5813         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5814         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5815         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5816         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5817         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5818         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5819      }
5820
5821      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5822      if(gameInfo.variant == VariantGreat) { // promotion commoners
5823         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5824         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5825         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5826         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5827      }
5828      if( gameInfo.variant == VariantSChess ) {
5829       initialPosition[1][0] = BlackMarshall;
5830       initialPosition[2][0] = BlackAngel;
5831       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5832       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5833       initialPosition[1][1] = initialPosition[2][1] = 
5834       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5835      }
5836   if (appData.debugMode) {
5837     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5838   }
5839     if(shuffleOpenings) {
5840         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5841         startedFromSetupPosition = TRUE;
5842     }
5843     if(startedFromPositionFile) {
5844       /* [HGM] loadPos: use PositionFile for every new game */
5845       CopyBoard(initialPosition, filePosition);
5846       for(i=0; i<nrCastlingRights; i++)
5847           initialRights[i] = filePosition[CASTLING][i];
5848       startedFromSetupPosition = TRUE;
5849     }
5850
5851     CopyBoard(boards[0], initialPosition);
5852
5853     if(oldx != gameInfo.boardWidth ||
5854        oldy != gameInfo.boardHeight ||
5855        oldv != gameInfo.variant ||
5856        oldh != gameInfo.holdingsWidth
5857                                          )
5858             InitDrawingSizes(-2 ,0);
5859
5860     oldv = gameInfo.variant;
5861     if (redraw)
5862       DrawPosition(TRUE, boards[currentMove]);
5863 }
5864
5865 void
5866 SendBoard(cps, moveNum)
5867      ChessProgramState *cps;
5868      int moveNum;
5869 {
5870     char message[MSG_SIZ];
5871
5872     if (cps->useSetboard) {
5873       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5874       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5875       SendToProgram(message, cps);
5876       free(fen);
5877
5878     } else {
5879       ChessSquare *bp;
5880       int i, j;
5881       /* Kludge to set black to move, avoiding the troublesome and now
5882        * deprecated "black" command.
5883        */
5884       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5885         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5886
5887       SendToProgram("edit\n", cps);
5888       SendToProgram("#\n", cps);
5889       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5890         bp = &boards[moveNum][i][BOARD_LEFT];
5891         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5892           if ((int) *bp < (int) BlackPawn) {
5893             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5894                     AAA + j, ONE + i);
5895             if(message[0] == '+' || message[0] == '~') {
5896               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5897                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5898                         AAA + j, ONE + i);
5899             }
5900             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5901                 message[1] = BOARD_RGHT   - 1 - j + '1';
5902                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5903             }
5904             SendToProgram(message, cps);
5905           }
5906         }
5907       }
5908
5909       SendToProgram("c\n", cps);
5910       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5911         bp = &boards[moveNum][i][BOARD_LEFT];
5912         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5913           if (((int) *bp != (int) EmptySquare)
5914               && ((int) *bp >= (int) BlackPawn)) {
5915             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5916                     AAA + j, ONE + i);
5917             if(message[0] == '+' || message[0] == '~') {
5918               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5919                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5920                         AAA + j, ONE + i);
5921             }
5922             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5923                 message[1] = BOARD_RGHT   - 1 - j + '1';
5924                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5925             }
5926             SendToProgram(message, cps);
5927           }
5928         }
5929       }
5930
5931       SendToProgram(".\n", cps);
5932     }
5933     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5934 }
5935
5936 ChessSquare
5937 DefaultPromoChoice(int white)
5938 {
5939     ChessSquare result;
5940     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5941         result = WhiteFerz; // no choice
5942     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5943         result= WhiteKing; // in Suicide Q is the last thing we want
5944     else if(gameInfo.variant == VariantSpartan)
5945         result = white ? WhiteQueen : WhiteAngel;
5946     else result = WhiteQueen;
5947     if(!white) result = WHITE_TO_BLACK result;
5948     return result;
5949 }
5950
5951 static int autoQueen; // [HGM] oneclick
5952
5953 int
5954 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5955 {
5956     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5957     /* [HGM] add Shogi promotions */
5958     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5959     ChessSquare piece;
5960     ChessMove moveType;
5961     Boolean premove;
5962
5963     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5964     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5965
5966     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5967       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5968         return FALSE;
5969
5970     piece = boards[currentMove][fromY][fromX];
5971     if(gameInfo.variant == VariantShogi) {
5972         promotionZoneSize = BOARD_HEIGHT/3;
5973         highestPromotingPiece = (int)WhiteFerz;
5974     } else if(gameInfo.variant == VariantMakruk) {
5975         promotionZoneSize = 3;
5976     }
5977
5978     // Treat Lance as Pawn when it is not representing Amazon
5979     if(gameInfo.variant != VariantSuper) {
5980         if(piece == WhiteLance) piece = WhitePawn; else
5981         if(piece == BlackLance) piece = BlackPawn;
5982     }
5983
5984     // next weed out all moves that do not touch the promotion zone at all
5985     if((int)piece >= BlackPawn) {
5986         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5987              return FALSE;
5988         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5989     } else {
5990         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5991            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5992     }
5993
5994     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5995
5996     // weed out mandatory Shogi promotions
5997     if(gameInfo.variant == VariantShogi) {
5998         if(piece >= BlackPawn) {
5999             if(toY == 0 && piece == BlackPawn ||
6000                toY == 0 && piece == BlackQueen ||
6001                toY <= 1 && piece == BlackKnight) {
6002                 *promoChoice = '+';
6003                 return FALSE;
6004             }
6005         } else {
6006             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6007                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6008                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6009                 *promoChoice = '+';
6010                 return FALSE;
6011             }
6012         }
6013     }
6014
6015     // weed out obviously illegal Pawn moves
6016     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6017         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6018         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6019         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6020         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6021         // note we are not allowed to test for valid (non-)capture, due to premove
6022     }
6023
6024     // we either have a choice what to promote to, or (in Shogi) whether to promote
6025     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6026         *promoChoice = PieceToChar(BlackFerz);  // no choice
6027         return FALSE;
6028     }
6029     // no sense asking what we must promote to if it is going to explode...
6030     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6031         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6032         return FALSE;
6033     }
6034     // give caller the default choice even if we will not make it
6035     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6036     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6037     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6038                            && gameInfo.variant != VariantShogi
6039                            && gameInfo.variant != VariantSuper) return FALSE;
6040     if(autoQueen) return FALSE; // predetermined
6041
6042     // suppress promotion popup on illegal moves that are not premoves
6043     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6044               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6045     if(appData.testLegality && !premove) {
6046         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6047                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6048         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6049             return FALSE;
6050     }
6051
6052     return TRUE;
6053 }
6054
6055 int
6056 InPalace(row, column)
6057      int row, column;
6058 {   /* [HGM] for Xiangqi */
6059     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6060          column < (BOARD_WIDTH + 4)/2 &&
6061          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6062     return FALSE;
6063 }
6064
6065 int
6066 PieceForSquare (x, y)
6067      int x;
6068      int y;
6069 {
6070   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6071      return -1;
6072   else
6073      return boards[currentMove][y][x];
6074 }
6075
6076 int
6077 OKToStartUserMove(x, y)
6078      int x, y;
6079 {
6080     ChessSquare from_piece;
6081     int white_piece;
6082
6083     if (matchMode) return FALSE;
6084     if (gameMode == EditPosition) return TRUE;
6085
6086     if (x >= 0 && y >= 0)
6087       from_piece = boards[currentMove][y][x];
6088     else
6089       from_piece = EmptySquare;
6090
6091     if (from_piece == EmptySquare) return FALSE;
6092
6093     white_piece = (int)from_piece >= (int)WhitePawn &&
6094       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6095
6096     switch (gameMode) {
6097       case PlayFromGameFile:
6098       case AnalyzeFile:
6099       case TwoMachinesPlay:
6100       case EndOfGame:
6101         return FALSE;
6102
6103       case IcsObserving:
6104       case IcsIdle:
6105         return FALSE;
6106
6107       case MachinePlaysWhite:
6108       case IcsPlayingBlack:
6109         if (appData.zippyPlay) return FALSE;
6110         if (white_piece) {
6111             DisplayMoveError(_("You are playing Black"));
6112             return FALSE;
6113         }
6114         break;
6115
6116       case MachinePlaysBlack:
6117       case IcsPlayingWhite:
6118         if (appData.zippyPlay) return FALSE;
6119         if (!white_piece) {
6120             DisplayMoveError(_("You are playing White"));
6121             return FALSE;
6122         }
6123         break;
6124
6125       case EditGame:
6126         if (!white_piece && WhiteOnMove(currentMove)) {
6127             DisplayMoveError(_("It is White's turn"));
6128             return FALSE;
6129         }
6130         if (white_piece && !WhiteOnMove(currentMove)) {
6131             DisplayMoveError(_("It is Black's turn"));
6132             return FALSE;
6133         }
6134         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6135             /* Editing correspondence game history */
6136             /* Could disallow this or prompt for confirmation */
6137             cmailOldMove = -1;
6138         }
6139         break;
6140
6141       case BeginningOfGame:
6142         if (appData.icsActive) return FALSE;
6143         if (!appData.noChessProgram) {
6144             if (!white_piece) {
6145                 DisplayMoveError(_("You are playing White"));
6146                 return FALSE;
6147             }
6148         }
6149         break;
6150
6151       case Training:
6152         if (!white_piece && WhiteOnMove(currentMove)) {
6153             DisplayMoveError(_("It is White's turn"));
6154             return FALSE;
6155         }
6156         if (white_piece && !WhiteOnMove(currentMove)) {
6157             DisplayMoveError(_("It is Black's turn"));
6158             return FALSE;
6159         }
6160         break;
6161
6162       default:
6163       case IcsExamining:
6164         break;
6165     }
6166     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6167         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6168         && gameMode != AnalyzeFile && gameMode != Training) {
6169         DisplayMoveError(_("Displayed position is not current"));
6170         return FALSE;
6171     }
6172     return TRUE;
6173 }
6174
6175 Boolean
6176 OnlyMove(int *x, int *y, Boolean captures) {
6177     DisambiguateClosure cl;
6178     if (appData.zippyPlay) return FALSE;
6179     switch(gameMode) {
6180       case MachinePlaysBlack:
6181       case IcsPlayingWhite:
6182       case BeginningOfGame:
6183         if(!WhiteOnMove(currentMove)) return FALSE;
6184         break;
6185       case MachinePlaysWhite:
6186       case IcsPlayingBlack:
6187         if(WhiteOnMove(currentMove)) return FALSE;
6188         break;
6189       case EditGame:
6190         break;
6191       default:
6192         return FALSE;
6193     }
6194     cl.pieceIn = EmptySquare;
6195     cl.rfIn = *y;
6196     cl.ffIn = *x;
6197     cl.rtIn = -1;
6198     cl.ftIn = -1;
6199     cl.promoCharIn = NULLCHAR;
6200     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6201     if( cl.kind == NormalMove ||
6202         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6203         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6204         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6205       fromX = cl.ff;
6206       fromY = cl.rf;
6207       *x = cl.ft;
6208       *y = cl.rt;
6209       return TRUE;
6210     }
6211     if(cl.kind != ImpossibleMove) return FALSE;
6212     cl.pieceIn = EmptySquare;
6213     cl.rfIn = -1;
6214     cl.ffIn = -1;
6215     cl.rtIn = *y;
6216     cl.ftIn = *x;
6217     cl.promoCharIn = NULLCHAR;
6218     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6219     if( cl.kind == NormalMove ||
6220         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6221         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6222         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6223       fromX = cl.ff;
6224       fromY = cl.rf;
6225       *x = cl.ft;
6226       *y = cl.rt;
6227       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6228       return TRUE;
6229     }
6230     return FALSE;
6231 }
6232
6233 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6234 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6235 int lastLoadGameUseList = FALSE;
6236 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6237 ChessMove lastLoadGameStart = EndOfFile;
6238
6239 void
6240 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6241      int fromX, fromY, toX, toY;
6242      int promoChar;
6243 {
6244     ChessMove moveType;
6245     ChessSquare pdown, pup;
6246
6247     /* Check if the user is playing in turn.  This is complicated because we
6248        let the user "pick up" a piece before it is his turn.  So the piece he
6249        tried to pick up may have been captured by the time he puts it down!
6250        Therefore we use the color the user is supposed to be playing in this
6251        test, not the color of the piece that is currently on the starting
6252        square---except in EditGame mode, where the user is playing both
6253        sides; fortunately there the capture race can't happen.  (It can
6254        now happen in IcsExamining mode, but that's just too bad.  The user
6255        will get a somewhat confusing message in that case.)
6256        */
6257
6258     switch (gameMode) {
6259       case PlayFromGameFile:
6260       case AnalyzeFile:
6261       case TwoMachinesPlay:
6262       case EndOfGame:
6263       case IcsObserving:
6264       case IcsIdle:
6265         /* We switched into a game mode where moves are not accepted,
6266            perhaps while the mouse button was down. */
6267         return;
6268
6269       case MachinePlaysWhite:
6270         /* User is moving for Black */
6271         if (WhiteOnMove(currentMove)) {
6272             DisplayMoveError(_("It is White's turn"));
6273             return;
6274         }
6275         break;
6276
6277       case MachinePlaysBlack:
6278         /* User is moving for White */
6279         if (!WhiteOnMove(currentMove)) {
6280             DisplayMoveError(_("It is Black's turn"));
6281             return;
6282         }
6283         break;
6284
6285       case EditGame:
6286       case IcsExamining:
6287       case BeginningOfGame:
6288       case AnalyzeMode:
6289       case Training:
6290         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6291         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6292             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6293             /* User is moving for Black */
6294             if (WhiteOnMove(currentMove)) {
6295                 DisplayMoveError(_("It is White's turn"));
6296                 return;
6297             }
6298         } else {
6299             /* User is moving for White */
6300             if (!WhiteOnMove(currentMove)) {
6301                 DisplayMoveError(_("It is Black's turn"));
6302                 return;
6303             }
6304         }
6305         break;
6306
6307       case IcsPlayingBlack:
6308         /* User is moving for Black */
6309         if (WhiteOnMove(currentMove)) {
6310             if (!appData.premove) {
6311                 DisplayMoveError(_("It is White's turn"));
6312             } else if (toX >= 0 && toY >= 0) {
6313                 premoveToX = toX;
6314                 premoveToY = toY;
6315                 premoveFromX = fromX;
6316                 premoveFromY = fromY;
6317                 premovePromoChar = promoChar;
6318                 gotPremove = 1;
6319                 if (appData.debugMode)
6320                     fprintf(debugFP, "Got premove: fromX %d,"
6321                             "fromY %d, toX %d, toY %d\n",
6322                             fromX, fromY, toX, toY);
6323             }
6324             return;
6325         }
6326         break;
6327
6328       case IcsPlayingWhite:
6329         /* User is moving for White */
6330         if (!WhiteOnMove(currentMove)) {
6331             if (!appData.premove) {
6332                 DisplayMoveError(_("It is Black's turn"));
6333             } else if (toX >= 0 && toY >= 0) {
6334                 premoveToX = toX;
6335                 premoveToY = toY;
6336                 premoveFromX = fromX;
6337                 premoveFromY = fromY;
6338                 premovePromoChar = promoChar;
6339                 gotPremove = 1;
6340                 if (appData.debugMode)
6341                     fprintf(debugFP, "Got premove: fromX %d,"
6342                             "fromY %d, toX %d, toY %d\n",
6343                             fromX, fromY, toX, toY);
6344             }
6345             return;
6346         }
6347         break;
6348
6349       default:
6350         break;
6351
6352       case EditPosition:
6353         /* EditPosition, empty square, or different color piece;
6354            click-click move is possible */
6355         if (toX == -2 || toY == -2) {
6356             boards[0][fromY][fromX] = EmptySquare;
6357             DrawPosition(FALSE, boards[currentMove]);
6358             return;
6359         } else if (toX >= 0 && toY >= 0) {
6360             boards[0][toY][toX] = boards[0][fromY][fromX];
6361             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6362                 if(boards[0][fromY][0] != EmptySquare) {
6363                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6364                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6365                 }
6366             } else
6367             if(fromX == BOARD_RGHT+1) {
6368                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6369                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6370                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6371                 }
6372             } else
6373             boards[0][fromY][fromX] = EmptySquare;
6374             DrawPosition(FALSE, boards[currentMove]);
6375             return;
6376         }
6377         return;
6378     }
6379
6380     if(toX < 0 || toY < 0) return;
6381     pdown = boards[currentMove][fromY][fromX];
6382     pup = boards[currentMove][toY][toX];
6383
6384     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6385     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6386          if( pup != EmptySquare ) return;
6387          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6388            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6389                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6390            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6391            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6392            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6393            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6394          fromY = DROP_RANK;
6395     }
6396
6397     /* [HGM] always test for legality, to get promotion info */
6398     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6399                                          fromY, fromX, toY, toX, promoChar);
6400     /* [HGM] but possibly ignore an IllegalMove result */
6401     if (appData.testLegality) {
6402         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6403             DisplayMoveError(_("Illegal move"));
6404             return;
6405         }
6406     }
6407
6408     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6409 }
6410
6411 /* Common tail of UserMoveEvent and DropMenuEvent */
6412 int
6413 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6414      ChessMove moveType;
6415      int fromX, fromY, toX, toY;
6416      /*char*/int promoChar;
6417 {
6418     char *bookHit = 0;
6419
6420     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6421         // [HGM] superchess: suppress promotions to non-available piece
6422         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6423         if(WhiteOnMove(currentMove)) {
6424             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6425         } else {
6426             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6427         }
6428     }
6429
6430     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6431        move type in caller when we know the move is a legal promotion */
6432     if(moveType == NormalMove && promoChar)
6433         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6434
6435     /* [HGM] <popupFix> The following if has been moved here from
6436        UserMoveEvent(). Because it seemed to belong here (why not allow
6437        piece drops in training games?), and because it can only be
6438        performed after it is known to what we promote. */
6439     if (gameMode == Training) {
6440       /* compare the move played on the board to the next move in the
6441        * game. If they match, display the move and the opponent's response.
6442        * If they don't match, display an error message.
6443        */
6444       int saveAnimate;
6445       Board testBoard;
6446       CopyBoard(testBoard, boards[currentMove]);
6447       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6448
6449       if (CompareBoards(testBoard, boards[currentMove+1])) {
6450         ForwardInner(currentMove+1);
6451
6452         /* Autoplay the opponent's response.
6453          * if appData.animate was TRUE when Training mode was entered,
6454          * the response will be animated.
6455          */
6456         saveAnimate = appData.animate;
6457         appData.animate = animateTraining;
6458         ForwardInner(currentMove+1);
6459         appData.animate = saveAnimate;
6460
6461         /* check for the end of the game */
6462         if (currentMove >= forwardMostMove) {
6463           gameMode = PlayFromGameFile;
6464           ModeHighlight();
6465           SetTrainingModeOff();
6466           DisplayInformation(_("End of game"));
6467         }
6468       } else {
6469         DisplayError(_("Incorrect move"), 0);
6470       }
6471       return 1;
6472     }
6473
6474   /* Ok, now we know that the move is good, so we can kill
6475      the previous line in Analysis Mode */
6476   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6477                                 && currentMove < forwardMostMove) {
6478     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6479     else forwardMostMove = currentMove;
6480   }
6481
6482   /* If we need the chess program but it's dead, restart it */
6483   ResurrectChessProgram();
6484
6485   /* A user move restarts a paused game*/
6486   if (pausing)
6487     PauseEvent();
6488
6489   thinkOutput[0] = NULLCHAR;
6490
6491   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6492
6493   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6494     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6495     return 1;
6496   }
6497
6498   if (gameMode == BeginningOfGame) {
6499     if (appData.noChessProgram) {
6500       gameMode = EditGame;
6501       SetGameInfo();
6502     } else {
6503       char buf[MSG_SIZ];
6504       gameMode = MachinePlaysBlack;
6505       StartClocks();
6506       SetGameInfo();
6507       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6508       DisplayTitle(buf);
6509       if (first.sendName) {
6510         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6511         SendToProgram(buf, &first);
6512       }
6513       StartClocks();
6514     }
6515     ModeHighlight();
6516   }
6517
6518   /* Relay move to ICS or chess engine */
6519   if (appData.icsActive) {
6520     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6521         gameMode == IcsExamining) {
6522       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6523         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6524         SendToICS("draw ");
6525         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6526       }
6527       // also send plain move, in case ICS does not understand atomic claims
6528       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6529       ics_user_moved = 1;
6530     }
6531   } else {
6532     if (first.sendTime && (gameMode == BeginningOfGame ||
6533                            gameMode == MachinePlaysWhite ||
6534                            gameMode == MachinePlaysBlack)) {
6535       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6536     }
6537     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6538          // [HGM] book: if program might be playing, let it use book
6539         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6540         first.maybeThinking = TRUE;
6541     } else SendMoveToProgram(forwardMostMove-1, &first);
6542     if (currentMove == cmailOldMove + 1) {
6543       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6544     }
6545   }
6546
6547   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6548
6549   switch (gameMode) {
6550   case EditGame:
6551     if(appData.testLegality)
6552     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6553     case MT_NONE:
6554     case MT_CHECK:
6555       break;
6556     case MT_CHECKMATE:
6557     case MT_STAINMATE:
6558       if (WhiteOnMove(currentMove)) {
6559         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6560       } else {
6561         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6562       }
6563       break;
6564     case MT_STALEMATE:
6565       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6566       break;
6567     }
6568     break;
6569
6570   case MachinePlaysBlack:
6571   case MachinePlaysWhite:
6572     /* disable certain menu options while machine is thinking */
6573     SetMachineThinkingEnables();
6574     break;
6575
6576   default:
6577     break;
6578   }
6579
6580   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6581   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6582
6583   if(bookHit) { // [HGM] book: simulate book reply
6584         static char bookMove[MSG_SIZ]; // a bit generous?
6585
6586         programStats.nodes = programStats.depth = programStats.time =
6587         programStats.score = programStats.got_only_move = 0;
6588         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6589
6590         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6591         strcat(bookMove, bookHit);
6592         HandleMachineMove(bookMove, &first);
6593   }
6594   return 1;
6595 }
6596
6597 void
6598 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6599      Board board;
6600      int flags;
6601      ChessMove kind;
6602      int rf, ff, rt, ft;
6603      VOIDSTAR closure;
6604 {
6605     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6606     Markers *m = (Markers *) closure;
6607     if(rf == fromY && ff == fromX)
6608         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6609                          || kind == WhiteCapturesEnPassant
6610                          || kind == BlackCapturesEnPassant);
6611     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6612 }
6613
6614 void
6615 MarkTargetSquares(int clear)
6616 {
6617   int x, y;
6618   if(!appData.markers || !appData.highlightDragging ||
6619      !appData.testLegality || gameMode == EditPosition) return;
6620   if(clear) {
6621     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6622   } else {
6623     int capt = 0;
6624     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6625     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6626       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6627       if(capt)
6628       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6629     }
6630   }
6631   DrawPosition(TRUE, NULL);
6632 }
6633
6634 int
6635 Explode(Board board, int fromX, int fromY, int toX, int toY)
6636 {
6637     if(gameInfo.variant == VariantAtomic &&
6638        (board[toY][toX] != EmptySquare ||                     // capture?
6639         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6640                          board[fromY][fromX] == BlackPawn   )
6641       )) {
6642         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6643         return TRUE;
6644     }
6645     return FALSE;
6646 }
6647
6648 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6649
6650 int CanPromote(ChessSquare piece, int y)
6651 {
6652         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6653         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6654         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6655            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6656            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6657                                                   gameInfo.variant == VariantMakruk) return FALSE;
6658         return (piece == BlackPawn && y == 1 ||
6659                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6660                 piece == BlackLance && y == 1 ||
6661                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6662 }
6663
6664 void LeftClick(ClickType clickType, int xPix, int yPix)
6665 {
6666     int x, y;
6667     Boolean saveAnimate;
6668     static int second = 0, promotionChoice = 0, clearFlag = 0;
6669     char promoChoice = NULLCHAR;
6670     ChessSquare piece;
6671
6672     if(appData.seekGraph && appData.icsActive && loggedOn &&
6673         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6674         SeekGraphClick(clickType, xPix, yPix, 0);
6675         return;
6676     }
6677
6678     if (clickType == Press) ErrorPopDown();
6679     MarkTargetSquares(1);
6680
6681     x = EventToSquare(xPix, BOARD_WIDTH);
6682     y = EventToSquare(yPix, BOARD_HEIGHT);
6683     if (!flipView && y >= 0) {
6684         y = BOARD_HEIGHT - 1 - y;
6685     }
6686     if (flipView && x >= 0) {
6687         x = BOARD_WIDTH - 1 - x;
6688     }
6689
6690     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6691         defaultPromoChoice = promoSweep;
6692         promoSweep = EmptySquare;   // terminate sweep
6693         promoDefaultAltered = TRUE;
6694         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6695     }
6696
6697     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6698         if(clickType == Release) return; // ignore upclick of click-click destination
6699         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6700         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6701         if(gameInfo.holdingsWidth &&
6702                 (WhiteOnMove(currentMove)
6703                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6704                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6705             // click in right holdings, for determining promotion piece
6706             ChessSquare p = boards[currentMove][y][x];
6707             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6708             if(p != EmptySquare) {
6709                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6710                 fromX = fromY = -1;
6711                 return;
6712             }
6713         }
6714         DrawPosition(FALSE, boards[currentMove]);
6715         return;
6716     }
6717
6718     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6719     if(clickType == Press
6720             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6721               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6722               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6723         return;
6724
6725     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6726         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6727
6728     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6729         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6730                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6731         defaultPromoChoice = DefaultPromoChoice(side);
6732     }
6733
6734     autoQueen = appData.alwaysPromoteToQueen;
6735
6736     if (fromX == -1) {
6737       int originalY = y;
6738       gatingPiece = EmptySquare;
6739       if (clickType != Press) {
6740         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6741             DragPieceEnd(xPix, yPix); dragging = 0;
6742             DrawPosition(FALSE, NULL);
6743         }
6744         return;
6745       }
6746       fromX = x; fromY = y;
6747       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6748          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6749          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6750             /* First square */
6751             if (OKToStartUserMove(fromX, fromY)) {
6752                 second = 0;
6753                 MarkTargetSquares(0);
6754                 DragPieceBegin(xPix, yPix); dragging = 1;
6755                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6756                     promoSweep = defaultPromoChoice;
6757                     selectFlag = 0; lastX = xPix; lastY = yPix;
6758                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6759                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6760                 }
6761                 if (appData.highlightDragging) {
6762                     SetHighlights(fromX, fromY, -1, -1);
6763                 }
6764             } else fromX = fromY = -1;
6765             return;
6766         }
6767     }
6768
6769     /* fromX != -1 */
6770     if (clickType == Press && gameMode != EditPosition) {
6771         ChessSquare fromP;
6772         ChessSquare toP;
6773         int frc;
6774
6775         // ignore off-board to clicks
6776         if(y < 0 || x < 0) return;
6777
6778         /* Check if clicking again on the same color piece */
6779         fromP = boards[currentMove][fromY][fromX];
6780         toP = boards[currentMove][y][x];
6781         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6782         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6783              WhitePawn <= toP && toP <= WhiteKing &&
6784              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6785              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6786             (BlackPawn <= fromP && fromP <= BlackKing &&
6787              BlackPawn <= toP && toP <= BlackKing &&
6788              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6789              !(fromP == BlackKing && toP == BlackRook && frc))) {
6790             /* Clicked again on same color piece -- changed his mind */
6791             second = (x == fromX && y == fromY);
6792             promoDefaultAltered = FALSE;
6793            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6794             if (appData.highlightDragging) {
6795                 SetHighlights(x, y, -1, -1);
6796             } else {
6797                 ClearHighlights();
6798             }
6799             if (OKToStartUserMove(x, y)) {
6800                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6801                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6802                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6803                  gatingPiece = boards[currentMove][fromY][fromX];
6804                 else gatingPiece = EmptySquare;
6805                 fromX = x;
6806                 fromY = y; dragging = 1;
6807                 MarkTargetSquares(0);
6808                 DragPieceBegin(xPix, yPix);
6809                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6810                     promoSweep = defaultPromoChoice;
6811                     selectFlag = 0; lastX = xPix; lastY = yPix;
6812                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6813                 }
6814             }
6815            }
6816            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6817            second = FALSE; 
6818         }
6819         // ignore clicks on holdings
6820         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6821     }
6822
6823     if (clickType == Release && x == fromX && y == fromY) {
6824         DragPieceEnd(xPix, yPix); dragging = 0;
6825         if(clearFlag) {
6826             // a deferred attempt to click-click move an empty square on top of a piece
6827             boards[currentMove][y][x] = EmptySquare;
6828             ClearHighlights();
6829             DrawPosition(FALSE, boards[currentMove]);
6830             fromX = fromY = -1; clearFlag = 0;
6831             return;
6832         }
6833         if (appData.animateDragging) {
6834             /* Undo animation damage if any */
6835             DrawPosition(FALSE, NULL);
6836         }
6837         if (second) {
6838             /* Second up/down in same square; just abort move */
6839             second = 0;
6840             fromX = fromY = -1;
6841             gatingPiece = EmptySquare;
6842             ClearHighlights();
6843             gotPremove = 0;
6844             ClearPremoveHighlights();
6845         } else {
6846             /* First upclick in same square; start click-click mode */
6847             SetHighlights(x, y, -1, -1);
6848         }
6849         return;
6850     }
6851
6852     clearFlag = 0;
6853
6854     /* we now have a different from- and (possibly off-board) to-square */
6855     /* Completed move */
6856     toX = x;
6857     toY = y;
6858     saveAnimate = appData.animate;
6859     if (clickType == Press) {
6860         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6861             // must be Edit Position mode with empty-square selected
6862             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6863             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6864             return;
6865         }
6866         /* Finish clickclick move */
6867         if (appData.animate || appData.highlightLastMove) {
6868             SetHighlights(fromX, fromY, toX, toY);
6869         } else {
6870             ClearHighlights();
6871         }
6872     } else {
6873         /* Finish drag move */
6874         if (appData.highlightLastMove) {
6875             SetHighlights(fromX, fromY, toX, toY);
6876         } else {
6877             ClearHighlights();
6878         }
6879         DragPieceEnd(xPix, yPix); dragging = 0;
6880         /* Don't animate move and drag both */
6881         appData.animate = FALSE;
6882     }
6883
6884     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6885     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6886         ChessSquare piece = boards[currentMove][fromY][fromX];
6887         if(gameMode == EditPosition && piece != EmptySquare &&
6888            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6889             int n;
6890
6891             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6892                 n = PieceToNumber(piece - (int)BlackPawn);
6893                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6894                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6895                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6896             } else
6897             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6898                 n = PieceToNumber(piece);
6899                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6900                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6901                 boards[currentMove][n][BOARD_WIDTH-2]++;
6902             }
6903             boards[currentMove][fromY][fromX] = EmptySquare;
6904         }
6905         ClearHighlights();
6906         fromX = fromY = -1;
6907         DrawPosition(TRUE, boards[currentMove]);
6908         return;
6909     }
6910
6911     // off-board moves should not be highlighted
6912     if(x < 0 || y < 0) ClearHighlights();
6913
6914     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6915
6916     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6917         SetHighlights(fromX, fromY, toX, toY);
6918         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6919             // [HGM] super: promotion to captured piece selected from holdings
6920             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6921             promotionChoice = TRUE;
6922             // kludge follows to temporarily execute move on display, without promoting yet
6923             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6924             boards[currentMove][toY][toX] = p;
6925             DrawPosition(FALSE, boards[currentMove]);
6926             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6927             boards[currentMove][toY][toX] = q;
6928             DisplayMessage("Click in holdings to choose piece", "");
6929             return;
6930         }
6931         PromotionPopUp();
6932     } else {
6933         int oldMove = currentMove;
6934         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6935         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6936         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6937         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6938            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6939             DrawPosition(TRUE, boards[currentMove]);
6940         fromX = fromY = -1;
6941     }
6942     appData.animate = saveAnimate;
6943     if (appData.animate || appData.animateDragging) {
6944         /* Undo animation damage if needed */
6945         DrawPosition(FALSE, NULL);
6946     }
6947 }
6948
6949 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6950 {   // front-end-free part taken out of PieceMenuPopup
6951     int whichMenu; int xSqr, ySqr;
6952
6953     if(seekGraphUp) { // [HGM] seekgraph
6954         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6955         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6956         return -2;
6957     }
6958
6959     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6960          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6961         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6962         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6963         if(action == Press)   {
6964             originalFlip = flipView;
6965             flipView = !flipView; // temporarily flip board to see game from partners perspective
6966             DrawPosition(TRUE, partnerBoard);
6967             DisplayMessage(partnerStatus, "");
6968             partnerUp = TRUE;
6969         } else if(action == Release) {
6970             flipView = originalFlip;
6971             DrawPosition(TRUE, boards[currentMove]);
6972             partnerUp = FALSE;
6973         }
6974         return -2;
6975     }
6976
6977     xSqr = EventToSquare(x, BOARD_WIDTH);
6978     ySqr = EventToSquare(y, BOARD_HEIGHT);
6979     if (action == Release) {
6980         if(pieceSweep != EmptySquare) {
6981             EditPositionMenuEvent(pieceSweep, toX, toY);
6982             pieceSweep = EmptySquare;
6983         } else UnLoadPV(); // [HGM] pv
6984     }
6985     if (action != Press) return -2; // return code to be ignored
6986     switch (gameMode) {
6987       case IcsExamining:
6988         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6989       case EditPosition:
6990         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6991         if (xSqr < 0 || ySqr < 0) return -1;
6992         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6993         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6994         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6995         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6996         NextPiece(0);
6997         return -2;\r
6998       case IcsObserving:
6999         if(!appData.icsEngineAnalyze) return -1;
7000       case IcsPlayingWhite:
7001       case IcsPlayingBlack:
7002         if(!appData.zippyPlay) goto noZip;
7003       case AnalyzeMode:
7004       case AnalyzeFile:
7005       case MachinePlaysWhite:
7006       case MachinePlaysBlack:
7007       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7008         if (!appData.dropMenu) {
7009           LoadPV(x, y);
7010           return 2; // flag front-end to grab mouse events
7011         }
7012         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7013            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7014       case EditGame:
7015       noZip:
7016         if (xSqr < 0 || ySqr < 0) return -1;
7017         if (!appData.dropMenu || appData.testLegality &&
7018             gameInfo.variant != VariantBughouse &&
7019             gameInfo.variant != VariantCrazyhouse) return -1;
7020         whichMenu = 1; // drop menu
7021         break;
7022       default:
7023         return -1;
7024     }
7025
7026     if (((*fromX = xSqr) < 0) ||
7027         ((*fromY = ySqr) < 0)) {
7028         *fromX = *fromY = -1;
7029         return -1;
7030     }
7031     if (flipView)
7032       *fromX = BOARD_WIDTH - 1 - *fromX;
7033     else
7034       *fromY = BOARD_HEIGHT - 1 - *fromY;
7035
7036     return whichMenu;
7037 }
7038
7039 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7040 {
7041 //    char * hint = lastHint;
7042     FrontEndProgramStats stats;
7043
7044     stats.which = cps == &first ? 0 : 1;
7045     stats.depth = cpstats->depth;
7046     stats.nodes = cpstats->nodes;
7047     stats.score = cpstats->score;
7048     stats.time = cpstats->time;
7049     stats.pv = cpstats->movelist;
7050     stats.hint = lastHint;
7051     stats.an_move_index = 0;
7052     stats.an_move_count = 0;
7053
7054     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7055         stats.hint = cpstats->move_name;
7056         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7057         stats.an_move_count = cpstats->nr_moves;
7058     }
7059
7060     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
7061
7062     SetProgramStats( &stats );
7063 }
7064
7065 #define MAXPLAYERS 500
7066
7067 char *
7068 TourneyStandings(int display)
7069 {
7070     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7071     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7072     char result, *p, *names[MAXPLAYERS];
7073
7074     names[0] = p = strdup(appData.participants);
7075     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7076
7077     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7078
7079     while(result = appData.results[nr]) {
7080         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7081         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7082         wScore = bScore = 0;
7083         switch(result) {
7084           case '+': wScore = 2; break;
7085           case '-': bScore = 2; break;
7086           case '=': wScore = bScore = 1; break;
7087           case ' ':
7088           case '*': return strdup("busy"); // tourney not finished
7089         }
7090         score[w] += wScore;
7091         score[b] += bScore;
7092         games[w]++;
7093         games[b]++;
7094         nr++;
7095     }
7096     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7097     for(w=0; w<nPlayers; w++) {
7098         bScore = -1;
7099         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7100         ranking[w] = b; points[w] = bScore; score[b] = -2;
7101     }
7102     p = malloc(nPlayers*34+1);
7103     for(w=0; w<nPlayers && w<display; w++)
7104         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7105     free(names[0]);
7106     return p;
7107 }
7108
7109 void
7110 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7111 {       // count all piece types
7112         int p, f, r;
7113         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7114         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7115         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7116                 p = board[r][f];
7117                 pCnt[p]++;
7118                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7119                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7120                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7121                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7122                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7123                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7124         }
7125 }
7126
7127 int
7128 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7129 {
7130         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7131         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7132
7133         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7134         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7135         if(myPawns == 2 && nMine == 3) // KPP
7136             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7137         if(myPawns == 1 && nMine == 2) // KP
7138             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7139         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7140             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7141         if(myPawns) return FALSE;
7142         if(pCnt[WhiteRook+side])
7143             return pCnt[BlackRook-side] ||
7144                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7145                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7146                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7147         if(pCnt[WhiteCannon+side]) {
7148             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7149             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7150         }
7151         if(pCnt[WhiteKnight+side])
7152             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7153         return FALSE;
7154 }
7155
7156 int
7157 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7158 {
7159         VariantClass v = gameInfo.variant;
7160
7161         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7162         if(v == VariantShatranj) return TRUE; // always winnable through baring
7163         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7164         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7165
7166         if(v == VariantXiangqi) {
7167                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7168
7169                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7170                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7171                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7172                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7173                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7174                 if(stale) // we have at least one last-rank P plus perhaps C
7175                     return majors // KPKX
7176                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7177                 else // KCA*E*
7178                     return pCnt[WhiteFerz+side] // KCAK
7179                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7180                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7181                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7182
7183         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7184                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7185
7186                 if(nMine == 1) return FALSE; // bare King
7187                 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
7188                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7189                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7190                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7191                 if(pCnt[WhiteKnight+side])
7192                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7193                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7194                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7195                 if(nBishops)
7196                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7197                 if(pCnt[WhiteAlfil+side])
7198                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7199                 if(pCnt[WhiteWazir+side])
7200                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7201         }
7202
7203         return TRUE;
7204 }
7205
7206 int
7207 Adjudicate(ChessProgramState *cps)
7208 {       // [HGM] some adjudications useful with buggy engines
7209         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7210         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7211         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7212         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7213         int k, count = 0; static int bare = 1;
7214         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7215         Boolean canAdjudicate = !appData.icsActive;
7216
7217         // most tests only when we understand the game, i.e. legality-checking on
7218             if( appData.testLegality )
7219             {   /* [HGM] Some more adjudications for obstinate engines */
7220                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7221                 static int moveCount = 6;
7222                 ChessMove result;
7223                 char *reason = NULL;
7224
7225                 /* Count what is on board. */
7226                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7227
7228                 /* Some material-based adjudications that have to be made before stalemate test */
7229                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7230                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7231                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7232                      if(canAdjudicate && appData.checkMates) {
7233                          if(engineOpponent)
7234                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7235                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7236                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7237                          return 1;
7238                      }
7239                 }
7240
7241                 /* Bare King in Shatranj (loses) or Losers (wins) */
7242                 if( nrW == 1 || nrB == 1) {
7243                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7244                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7245                      if(canAdjudicate && appData.checkMates) {
7246                          if(engineOpponent)
7247                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7248                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7249                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7250                          return 1;
7251                      }
7252                   } else
7253                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7254                   {    /* bare King */
7255                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7256                         if(canAdjudicate && appData.checkMates) {
7257                             /* but only adjudicate if adjudication enabled */
7258                             if(engineOpponent)
7259                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7260                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7261                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7262                             return 1;
7263                         }
7264                   }
7265                 } else bare = 1;
7266
7267
7268             // don't wait for engine to announce game end if we can judge ourselves
7269             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7270               case MT_CHECK:
7271                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7272                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7273                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7274                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7275                             checkCnt++;
7276                         if(checkCnt >= 2) {
7277                             reason = "Xboard adjudication: 3rd check";
7278                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7279                             break;
7280                         }
7281                     }
7282                 }
7283               case MT_NONE:
7284               default:
7285                 break;
7286               case MT_STALEMATE:
7287               case MT_STAINMATE:
7288                 reason = "Xboard adjudication: Stalemate";
7289                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7290                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7291                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7292                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7293                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7294                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7295                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7296                                                                         EP_CHECKMATE : EP_WINS);
7297                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7298                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7299                 }
7300                 break;
7301               case MT_CHECKMATE:
7302                 reason = "Xboard adjudication: Checkmate";
7303                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7304                 break;
7305             }
7306
7307                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7308                     case EP_STALEMATE:
7309                         result = GameIsDrawn; break;
7310                     case EP_CHECKMATE:
7311                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7312                     case EP_WINS:
7313                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7314                     default:
7315                         result = EndOfFile;
7316                 }
7317                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7318                     if(engineOpponent)
7319                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7320                     GameEnds( result, reason, GE_XBOARD );
7321                     return 1;
7322                 }
7323
7324                 /* Next absolutely insufficient mating material. */
7325                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7326                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7327                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7328
7329                      /* always flag draws, for judging claims */
7330                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7331
7332                      if(canAdjudicate && appData.materialDraws) {
7333                          /* but only adjudicate them if adjudication enabled */
7334                          if(engineOpponent) {
7335                            SendToProgram("force\n", engineOpponent); // suppress reply
7336                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7337                          }
7338                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7339                          return 1;
7340                      }
7341                 }
7342
7343                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7344                 if(gameInfo.variant == VariantXiangqi ?
7345                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7346                  : nrW + nrB == 4 &&
7347                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7348                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7349                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7350                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7351                    ) ) {
7352                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7353                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7354                           if(engineOpponent) {
7355                             SendToProgram("force\n", engineOpponent); // suppress reply
7356                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7357                           }
7358                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7359                           return 1;
7360                      }
7361                 } else moveCount = 6;
7362             }
7363         if (appData.debugMode) { int i;
7364             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7365                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7366                     appData.drawRepeats);
7367             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7368               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7369
7370         }
7371
7372         // Repetition draws and 50-move rule can be applied independently of legality testing
7373
7374                 /* Check for rep-draws */
7375                 count = 0;
7376                 for(k = forwardMostMove-2;
7377                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7378                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7379                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7380                     k-=2)
7381                 {   int rights=0;
7382                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7383                         /* compare castling rights */
7384                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7385                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7386                                 rights++; /* King lost rights, while rook still had them */
7387                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7388                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7389                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7390                                    rights++; /* but at least one rook lost them */
7391                         }
7392                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7393                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7394                                 rights++;
7395                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7396                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7397                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7398                                    rights++;
7399                         }
7400                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7401                             && appData.drawRepeats > 1) {
7402                              /* adjudicate after user-specified nr of repeats */
7403                              int result = GameIsDrawn;
7404                              char *details = "XBoard adjudication: repetition draw";
7405                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7406                                 // [HGM] xiangqi: check for forbidden perpetuals
7407                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7408                                 for(m=forwardMostMove; m>k; m-=2) {
7409                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7410                                         ourPerpetual = 0; // the current mover did not always check
7411                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7412                                         hisPerpetual = 0; // the opponent did not always check
7413                                 }
7414                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7415                                                                         ourPerpetual, hisPerpetual);
7416                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7417                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7418                                     details = "Xboard adjudication: perpetual checking";
7419                                 } else
7420                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7421                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7422                                 } else
7423                                 // Now check for perpetual chases
7424                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7425                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7426                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7427                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7428                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7429                                         details = "Xboard adjudication: perpetual chasing";
7430                                     } else
7431                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7432                                         break; // Abort repetition-checking loop.
7433                                 }
7434                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7435                              }
7436                              if(engineOpponent) {
7437                                SendToProgram("force\n", engineOpponent); // suppress reply
7438                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7439                              }
7440                              GameEnds( result, details, GE_XBOARD );
7441                              return 1;
7442                         }
7443                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7444                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7445                     }
7446                 }
7447
7448                 /* Now we test for 50-move draws. Determine ply count */
7449                 count = forwardMostMove;
7450                 /* look for last irreversble move */
7451                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7452                     count--;
7453                 /* if we hit starting position, add initial plies */
7454                 if( count == backwardMostMove )
7455                     count -= initialRulePlies;
7456                 count = forwardMostMove - count;
7457                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7458                         // adjust reversible move counter for checks in Xiangqi
7459                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7460                         if(i < backwardMostMove) i = backwardMostMove;
7461                         while(i <= forwardMostMove) {
7462                                 lastCheck = inCheck; // check evasion does not count
7463                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7464                                 if(inCheck || lastCheck) count--; // check does not count
7465                                 i++;
7466                         }
7467                 }
7468                 if( count >= 100)
7469                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7470                          /* this is used to judge if draw claims are legal */
7471                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7472                          if(engineOpponent) {
7473                            SendToProgram("force\n", engineOpponent); // suppress reply
7474                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7475                          }
7476                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7477                          return 1;
7478                 }
7479
7480                 /* if draw offer is pending, treat it as a draw claim
7481                  * when draw condition present, to allow engines a way to
7482                  * claim draws before making their move to avoid a race
7483                  * condition occurring after their move
7484                  */
7485                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7486                          char *p = NULL;
7487                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7488                              p = "Draw claim: 50-move rule";
7489                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7490                              p = "Draw claim: 3-fold repetition";
7491                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7492                              p = "Draw claim: insufficient mating material";
7493                          if( p != NULL && canAdjudicate) {
7494                              if(engineOpponent) {
7495                                SendToProgram("force\n", engineOpponent); // suppress reply
7496                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7497                              }
7498                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7499                              return 1;
7500                          }
7501                 }
7502
7503                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7504                     if(engineOpponent) {
7505                       SendToProgram("force\n", engineOpponent); // suppress reply
7506                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7507                     }
7508                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7509                     return 1;
7510                 }
7511         return 0;
7512 }
7513
7514 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7515 {   // [HGM] book: this routine intercepts moves to simulate book replies
7516     char *bookHit = NULL;
7517
7518     //first determine if the incoming move brings opponent into his book
7519     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7520         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7521     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7522     if(bookHit != NULL && !cps->bookSuspend) {
7523         // make sure opponent is not going to reply after receiving move to book position
7524         SendToProgram("force\n", cps);
7525         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7526     }
7527     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7528     // now arrange restart after book miss
7529     if(bookHit) {
7530         // after a book hit we never send 'go', and the code after the call to this routine
7531         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7532         char buf[MSG_SIZ];
7533         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7534         SendToProgram(buf, cps);
7535         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7536     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7537         SendToProgram("go\n", cps);
7538         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7539     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7540         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7541             SendToProgram("go\n", cps);
7542         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7543     }
7544     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7545 }
7546
7547 char *savedMessage;
7548 ChessProgramState *savedState;
7549 void DeferredBookMove(void)
7550 {
7551         if(savedState->lastPing != savedState->lastPong)
7552                     ScheduleDelayedEvent(DeferredBookMove, 10);
7553         else
7554         HandleMachineMove(savedMessage, savedState);
7555 }
7556
7557 void
7558 HandleMachineMove(message, cps)
7559      char *message;
7560      ChessProgramState *cps;
7561 {
7562     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7563     char realname[MSG_SIZ];
7564     int fromX, fromY, toX, toY;
7565     ChessMove moveType;
7566     char promoChar;
7567     char *p;
7568     int machineWhite;
7569     char *bookHit;
7570
7571     cps->userError = 0;
7572
7573 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7574     /*
7575      * Kludge to ignore BEL characters
7576      */
7577     while (*message == '\007') message++;
7578
7579     /*
7580      * [HGM] engine debug message: ignore lines starting with '#' character
7581      */
7582     if(cps->debug && *message == '#') return;
7583
7584     /*
7585      * Look for book output
7586      */
7587     if (cps == &first && bookRequested) {
7588         if (message[0] == '\t' || message[0] == ' ') {
7589             /* Part of the book output is here; append it */
7590             strcat(bookOutput, message);
7591             strcat(bookOutput, "  \n");
7592             return;
7593         } else if (bookOutput[0] != NULLCHAR) {
7594             /* All of book output has arrived; display it */
7595             char *p = bookOutput;
7596             while (*p != NULLCHAR) {
7597                 if (*p == '\t') *p = ' ';
7598                 p++;
7599             }
7600             DisplayInformation(bookOutput);
7601             bookRequested = FALSE;
7602             /* Fall through to parse the current output */
7603         }
7604     }
7605
7606     /*
7607      * Look for machine move.
7608      */
7609     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7610         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7611     {
7612         /* This method is only useful on engines that support ping */
7613         if (cps->lastPing != cps->lastPong) {
7614           if (gameMode == BeginningOfGame) {
7615             /* Extra move from before last new; ignore */
7616             if (appData.debugMode) {
7617                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7618             }
7619           } else {
7620             if (appData.debugMode) {
7621                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7622                         cps->which, gameMode);
7623             }
7624
7625             SendToProgram("undo\n", cps);
7626           }
7627           return;
7628         }
7629
7630         switch (gameMode) {
7631           case BeginningOfGame:
7632             /* Extra move from before last reset; ignore */
7633             if (appData.debugMode) {
7634                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7635             }
7636             return;
7637
7638           case EndOfGame:
7639           case IcsIdle:
7640           default:
7641             /* Extra move after we tried to stop.  The mode test is
7642                not a reliable way of detecting this problem, but it's
7643                the best we can do on engines that don't support ping.
7644             */
7645             if (appData.debugMode) {
7646                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7647                         cps->which, gameMode);
7648             }
7649             SendToProgram("undo\n", cps);
7650             return;
7651
7652           case MachinePlaysWhite:
7653           case IcsPlayingWhite:
7654             machineWhite = TRUE;
7655             break;
7656
7657           case MachinePlaysBlack:
7658           case IcsPlayingBlack:
7659             machineWhite = FALSE;
7660             break;
7661
7662           case TwoMachinesPlay:
7663             machineWhite = (cps->twoMachinesColor[0] == 'w');
7664             break;
7665         }
7666         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7667             if (appData.debugMode) {
7668                 fprintf(debugFP,
7669                         "Ignoring move out of turn by %s, gameMode %d"
7670                         ", forwardMost %d\n",
7671                         cps->which, gameMode, forwardMostMove);
7672             }
7673             return;
7674         }
7675
7676     if (appData.debugMode) { int f = forwardMostMove;
7677         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7678                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7679                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7680     }
7681         if(cps->alphaRank) AlphaRank(machineMove, 4);
7682         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7683                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7684             /* Machine move could not be parsed; ignore it. */
7685           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7686                     machineMove, _(cps->which));
7687             DisplayError(buf1, 0);
7688             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7689                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7690             if (gameMode == TwoMachinesPlay) {
7691               GameEnds(machineWhite ? BlackWins : WhiteWins,
7692                        buf1, GE_XBOARD);
7693             }
7694             return;
7695         }
7696
7697         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7698         /* So we have to redo legality test with true e.p. status here,  */
7699         /* to make sure an illegal e.p. capture does not slip through,   */
7700         /* to cause a forfeit on a justified illegal-move complaint      */
7701         /* of the opponent.                                              */
7702         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7703            ChessMove moveType;
7704            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7705                              fromY, fromX, toY, toX, promoChar);
7706             if (appData.debugMode) {
7707                 int i;
7708                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7709                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7710                 fprintf(debugFP, "castling rights\n");
7711             }
7712             if(moveType == IllegalMove) {
7713               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7714                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7715                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7716                            buf1, GE_XBOARD);
7717                 return;
7718            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7719            /* [HGM] Kludge to handle engines that send FRC-style castling
7720               when they shouldn't (like TSCP-Gothic) */
7721            switch(moveType) {
7722              case WhiteASideCastleFR:
7723              case BlackASideCastleFR:
7724                toX+=2;
7725                currentMoveString[2]++;
7726                break;
7727              case WhiteHSideCastleFR:
7728              case BlackHSideCastleFR:
7729                toX--;
7730                currentMoveString[2]--;
7731                break;
7732              default: ; // nothing to do, but suppresses warning of pedantic compilers
7733            }
7734         }
7735         hintRequested = FALSE;
7736         lastHint[0] = NULLCHAR;
7737         bookRequested = FALSE;
7738         /* Program may be pondering now */
7739         cps->maybeThinking = TRUE;
7740         if (cps->sendTime == 2) cps->sendTime = 1;
7741         if (cps->offeredDraw) cps->offeredDraw--;
7742
7743         /* [AS] Save move info*/
7744         pvInfoList[ forwardMostMove ].score = programStats.score;
7745         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7746         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7747
7748         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7749
7750         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7751         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7752             int count = 0;
7753
7754             while( count < adjudicateLossPlies ) {
7755                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7756
7757                 if( count & 1 ) {
7758                     score = -score; /* Flip score for winning side */
7759                 }
7760
7761                 if( score > adjudicateLossThreshold ) {
7762                     break;
7763                 }
7764
7765                 count++;
7766             }
7767
7768             if( count >= adjudicateLossPlies ) {
7769                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7770
7771                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7772                     "Xboard adjudication",
7773                     GE_XBOARD );
7774
7775                 return;
7776             }
7777         }
7778
7779         if(Adjudicate(cps)) {
7780             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7781             return; // [HGM] adjudicate: for all automatic game ends
7782         }
7783
7784 #if ZIPPY
7785         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7786             first.initDone) {
7787           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7788                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7789                 SendToICS("draw ");
7790                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7791           }
7792           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7793           ics_user_moved = 1;
7794           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7795                 char buf[3*MSG_SIZ];
7796
7797                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7798                         programStats.score / 100.,
7799                         programStats.depth,
7800                         programStats.time / 100.,
7801                         (unsigned int)programStats.nodes,
7802                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7803                         programStats.movelist);
7804                 SendToICS(buf);
7805 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7806           }
7807         }
7808 #endif
7809
7810         /* [AS] Clear stats for next move */
7811         ClearProgramStats();
7812         thinkOutput[0] = NULLCHAR;
7813         hiddenThinkOutputState = 0;
7814
7815         bookHit = NULL;
7816         if (gameMode == TwoMachinesPlay) {
7817             /* [HGM] relaying draw offers moved to after reception of move */
7818             /* and interpreting offer as claim if it brings draw condition */
7819             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7820                 SendToProgram("draw\n", cps->other);
7821             }
7822             if (cps->other->sendTime) {
7823                 SendTimeRemaining(cps->other,
7824                                   cps->other->twoMachinesColor[0] == 'w');
7825             }
7826             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7827             if (firstMove && !bookHit) {
7828                 firstMove = FALSE;
7829                 if (cps->other->useColors) {
7830                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7831                 }
7832                 SendToProgram("go\n", cps->other);
7833             }
7834             cps->other->maybeThinking = TRUE;
7835         }
7836
7837         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7838
7839         if (!pausing && appData.ringBellAfterMoves) {
7840             RingBell();
7841         }
7842
7843         /*
7844          * Reenable menu items that were disabled while
7845          * machine was thinking
7846          */
7847         if (gameMode != TwoMachinesPlay)
7848             SetUserThinkingEnables();
7849
7850         // [HGM] book: after book hit opponent has received move and is now in force mode
7851         // force the book reply into it, and then fake that it outputted this move by jumping
7852         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7853         if(bookHit) {
7854                 static char bookMove[MSG_SIZ]; // a bit generous?
7855
7856                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7857                 strcat(bookMove, bookHit);
7858                 message = bookMove;
7859                 cps = cps->other;
7860                 programStats.nodes = programStats.depth = programStats.time =
7861                 programStats.score = programStats.got_only_move = 0;
7862                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7863
7864                 if(cps->lastPing != cps->lastPong) {
7865                     savedMessage = message; // args for deferred call
7866                     savedState = cps;
7867                     ScheduleDelayedEvent(DeferredBookMove, 10);
7868                     return;
7869                 }
7870                 goto FakeBookMove;
7871         }
7872
7873         return;
7874     }
7875
7876     /* Set special modes for chess engines.  Later something general
7877      *  could be added here; for now there is just one kludge feature,
7878      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7879      *  when "xboard" is given as an interactive command.
7880      */
7881     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7882         cps->useSigint = FALSE;
7883         cps->useSigterm = FALSE;
7884     }
7885     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7886       ParseFeatures(message+8, cps);
7887       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7888     }
7889
7890     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7891       int dummy, s=6; char buf[MSG_SIZ];
7892       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7893       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7894       ParseFEN(boards[0], &dummy, message+s);
7895       DrawPosition(TRUE, boards[0]);
7896       startedFromSetupPosition = TRUE;
7897       return;
7898     }
7899     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7900      * want this, I was asked to put it in, and obliged.
7901      */
7902     if (!strncmp(message, "setboard ", 9)) {
7903         Board initial_position;
7904
7905         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7906
7907         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7908             DisplayError(_("Bad FEN received from engine"), 0);
7909             return ;
7910         } else {
7911            Reset(TRUE, FALSE);
7912            CopyBoard(boards[0], initial_position);
7913            initialRulePlies = FENrulePlies;
7914            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7915            else gameMode = MachinePlaysBlack;
7916            DrawPosition(FALSE, boards[currentMove]);
7917         }
7918         return;
7919     }
7920
7921     /*
7922      * Look for communication commands
7923      */
7924     if (!strncmp(message, "telluser ", 9)) {
7925         if(message[9] == '\\' && message[10] == '\\')
7926             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7927         DisplayNote(message + 9);
7928         return;
7929     }
7930     if (!strncmp(message, "tellusererror ", 14)) {
7931         cps->userError = 1;
7932         if(message[14] == '\\' && message[15] == '\\')
7933             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7934         DisplayError(message + 14, 0);
7935         return;
7936     }
7937     if (!strncmp(message, "tellopponent ", 13)) {
7938       if (appData.icsActive) {
7939         if (loggedOn) {
7940           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7941           SendToICS(buf1);
7942         }
7943       } else {
7944         DisplayNote(message + 13);
7945       }
7946       return;
7947     }
7948     if (!strncmp(message, "tellothers ", 11)) {
7949       if (appData.icsActive) {
7950         if (loggedOn) {
7951           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7952           SendToICS(buf1);
7953         }
7954       }
7955       return;
7956     }
7957     if (!strncmp(message, "tellall ", 8)) {
7958       if (appData.icsActive) {
7959         if (loggedOn) {
7960           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7961           SendToICS(buf1);
7962         }
7963       } else {
7964         DisplayNote(message + 8);
7965       }
7966       return;
7967     }
7968     if (strncmp(message, "warning", 7) == 0) {
7969         /* Undocumented feature, use tellusererror in new code */
7970         DisplayError(message, 0);
7971         return;
7972     }
7973     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7974         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7975         strcat(realname, " query");
7976         AskQuestion(realname, buf2, buf1, cps->pr);
7977         return;
7978     }
7979     /* Commands from the engine directly to ICS.  We don't allow these to be
7980      *  sent until we are logged on. Crafty kibitzes have been known to
7981      *  interfere with the login process.
7982      */
7983     if (loggedOn) {
7984         if (!strncmp(message, "tellics ", 8)) {
7985             SendToICS(message + 8);
7986             SendToICS("\n");
7987             return;
7988         }
7989         if (!strncmp(message, "tellicsnoalias ", 15)) {
7990             SendToICS(ics_prefix);
7991             SendToICS(message + 15);
7992             SendToICS("\n");
7993             return;
7994         }
7995         /* The following are for backward compatibility only */
7996         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7997             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7998             SendToICS(ics_prefix);
7999             SendToICS(message);
8000             SendToICS("\n");
8001             return;
8002         }
8003     }
8004     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8005         return;
8006     }
8007     /*
8008      * If the move is illegal, cancel it and redraw the board.
8009      * Also deal with other error cases.  Matching is rather loose
8010      * here to accommodate engines written before the spec.
8011      */
8012     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8013         strncmp(message, "Error", 5) == 0) {
8014         if (StrStr(message, "name") ||
8015             StrStr(message, "rating") || StrStr(message, "?") ||
8016             StrStr(message, "result") || StrStr(message, "board") ||
8017             StrStr(message, "bk") || StrStr(message, "computer") ||
8018             StrStr(message, "variant") || StrStr(message, "hint") ||
8019             StrStr(message, "random") || StrStr(message, "depth") ||
8020             StrStr(message, "accepted")) {
8021             return;
8022         }
8023         if (StrStr(message, "protover")) {
8024           /* Program is responding to input, so it's apparently done
8025              initializing, and this error message indicates it is
8026              protocol version 1.  So we don't need to wait any longer
8027              for it to initialize and send feature commands. */
8028           FeatureDone(cps, 1);
8029           cps->protocolVersion = 1;
8030           return;
8031         }
8032         cps->maybeThinking = FALSE;
8033
8034         if (StrStr(message, "draw")) {
8035             /* Program doesn't have "draw" command */
8036             cps->sendDrawOffers = 0;
8037             return;
8038         }
8039         if (cps->sendTime != 1 &&
8040             (StrStr(message, "time") || StrStr(message, "otim"))) {
8041           /* Program apparently doesn't have "time" or "otim" command */
8042           cps->sendTime = 0;
8043           return;
8044         }
8045         if (StrStr(message, "analyze")) {
8046             cps->analysisSupport = FALSE;
8047             cps->analyzing = FALSE;
8048             Reset(FALSE, TRUE);
8049             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8050             DisplayError(buf2, 0);
8051             return;
8052         }
8053         if (StrStr(message, "(no matching move)st")) {
8054           /* Special kludge for GNU Chess 4 only */
8055           cps->stKludge = TRUE;
8056           SendTimeControl(cps, movesPerSession, timeControl,
8057                           timeIncrement, appData.searchDepth,
8058                           searchTime);
8059           return;
8060         }
8061         if (StrStr(message, "(no matching move)sd")) {
8062           /* Special kludge for GNU Chess 4 only */
8063           cps->sdKludge = TRUE;
8064           SendTimeControl(cps, movesPerSession, timeControl,
8065                           timeIncrement, appData.searchDepth,
8066                           searchTime);
8067           return;
8068         }
8069         if (!StrStr(message, "llegal")) {
8070             return;
8071         }
8072         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8073             gameMode == IcsIdle) return;
8074         if (forwardMostMove <= backwardMostMove) return;
8075         if (pausing) PauseEvent();
8076       if(appData.forceIllegal) {
8077             // [HGM] illegal: machine refused move; force position after move into it
8078           SendToProgram("force\n", cps);
8079           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8080                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8081                 // when black is to move, while there might be nothing on a2 or black
8082                 // might already have the move. So send the board as if white has the move.
8083                 // But first we must change the stm of the engine, as it refused the last move
8084                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8085                 if(WhiteOnMove(forwardMostMove)) {
8086                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8087                     SendBoard(cps, forwardMostMove); // kludgeless board
8088                 } else {
8089                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8090                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8091                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8092                 }
8093           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8094             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8095                  gameMode == TwoMachinesPlay)
8096               SendToProgram("go\n", cps);
8097             return;
8098       } else
8099         if (gameMode == PlayFromGameFile) {
8100             /* Stop reading this game file */
8101             gameMode = EditGame;
8102             ModeHighlight();
8103         }
8104         /* [HGM] illegal-move claim should forfeit game when Xboard */
8105         /* only passes fully legal moves                            */
8106         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8107             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8108                                 "False illegal-move claim", GE_XBOARD );
8109             return; // do not take back move we tested as valid
8110         }
8111         currentMove = forwardMostMove-1;
8112         DisplayMove(currentMove-1); /* before DisplayMoveError */
8113         SwitchClocks(forwardMostMove-1); // [HGM] race
8114         DisplayBothClocks();
8115         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8116                 parseList[currentMove], _(cps->which));
8117         DisplayMoveError(buf1);
8118         DrawPosition(FALSE, boards[currentMove]);
8119         return;
8120     }
8121     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8122         /* Program has a broken "time" command that
8123            outputs a string not ending in newline.
8124            Don't use it. */
8125         cps->sendTime = 0;
8126     }
8127
8128     /*
8129      * If chess program startup fails, exit with an error message.
8130      * Attempts to recover here are futile.
8131      */
8132     if ((StrStr(message, "unknown host") != NULL)
8133         || (StrStr(message, "No remote directory") != NULL)
8134         || (StrStr(message, "not found") != NULL)
8135         || (StrStr(message, "No such file") != NULL)
8136         || (StrStr(message, "can't alloc") != NULL)
8137         || (StrStr(message, "Permission denied") != NULL)) {
8138
8139         cps->maybeThinking = FALSE;
8140         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8141                 _(cps->which), cps->program, cps->host, message);
8142         RemoveInputSource(cps->isr);
8143         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8144             if(cps == &first) appData.noChessProgram = TRUE;
8145             DisplayError(buf1, 0);
8146         }
8147         return;
8148     }
8149
8150     /*
8151      * Look for hint output
8152      */
8153     if (sscanf(message, "Hint: %s", buf1) == 1) {
8154         if (cps == &first && hintRequested) {
8155             hintRequested = FALSE;
8156             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8157                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8158                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8159                                     PosFlags(forwardMostMove),
8160                                     fromY, fromX, toY, toX, promoChar, buf1);
8161                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8162                 DisplayInformation(buf2);
8163             } else {
8164                 /* Hint move could not be parsed!? */
8165               snprintf(buf2, sizeof(buf2),
8166                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8167                         buf1, _(cps->which));
8168                 DisplayError(buf2, 0);
8169             }
8170         } else {
8171           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8172         }
8173         return;
8174     }
8175
8176     /*
8177      * Ignore other messages if game is not in progress
8178      */
8179     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8180         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8181
8182     /*
8183      * look for win, lose, draw, or draw offer
8184      */
8185     if (strncmp(message, "1-0", 3) == 0) {
8186         char *p, *q, *r = "";
8187         p = strchr(message, '{');
8188         if (p) {
8189             q = strchr(p, '}');
8190             if (q) {
8191                 *q = NULLCHAR;
8192                 r = p + 1;
8193             }
8194         }
8195         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8196         return;
8197     } else if (strncmp(message, "0-1", 3) == 0) {
8198         char *p, *q, *r = "";
8199         p = strchr(message, '{');
8200         if (p) {
8201             q = strchr(p, '}');
8202             if (q) {
8203                 *q = NULLCHAR;
8204                 r = p + 1;
8205             }
8206         }
8207         /* Kludge for Arasan 4.1 bug */
8208         if (strcmp(r, "Black resigns") == 0) {
8209             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8210             return;
8211         }
8212         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8213         return;
8214     } else if (strncmp(message, "1/2", 3) == 0) {
8215         char *p, *q, *r = "";
8216         p = strchr(message, '{');
8217         if (p) {
8218             q = strchr(p, '}');
8219             if (q) {
8220                 *q = NULLCHAR;
8221                 r = p + 1;
8222             }
8223         }
8224
8225         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8226         return;
8227
8228     } else if (strncmp(message, "White resign", 12) == 0) {
8229         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8230         return;
8231     } else if (strncmp(message, "Black resign", 12) == 0) {
8232         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8233         return;
8234     } else if (strncmp(message, "White matches", 13) == 0 ||
8235                strncmp(message, "Black matches", 13) == 0   ) {
8236         /* [HGM] ignore GNUShogi noises */
8237         return;
8238     } else if (strncmp(message, "White", 5) == 0 &&
8239                message[5] != '(' &&
8240                StrStr(message, "Black") == NULL) {
8241         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8242         return;
8243     } else if (strncmp(message, "Black", 5) == 0 &&
8244                message[5] != '(') {
8245         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8246         return;
8247     } else if (strcmp(message, "resign") == 0 ||
8248                strcmp(message, "computer resigns") == 0) {
8249         switch (gameMode) {
8250           case MachinePlaysBlack:
8251           case IcsPlayingBlack:
8252             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8253             break;
8254           case MachinePlaysWhite:
8255           case IcsPlayingWhite:
8256             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8257             break;
8258           case TwoMachinesPlay:
8259             if (cps->twoMachinesColor[0] == 'w')
8260               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8261             else
8262               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8263             break;
8264           default:
8265             /* can't happen */
8266             break;
8267         }
8268         return;
8269     } else if (strncmp(message, "opponent mates", 14) == 0) {
8270         switch (gameMode) {
8271           case MachinePlaysBlack:
8272           case IcsPlayingBlack:
8273             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8274             break;
8275           case MachinePlaysWhite:
8276           case IcsPlayingWhite:
8277             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8278             break;
8279           case TwoMachinesPlay:
8280             if (cps->twoMachinesColor[0] == 'w')
8281               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8282             else
8283               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8284             break;
8285           default:
8286             /* can't happen */
8287             break;
8288         }
8289         return;
8290     } else if (strncmp(message, "computer mates", 14) == 0) {
8291         switch (gameMode) {
8292           case MachinePlaysBlack:
8293           case IcsPlayingBlack:
8294             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8295             break;
8296           case MachinePlaysWhite:
8297           case IcsPlayingWhite:
8298             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8299             break;
8300           case TwoMachinesPlay:
8301             if (cps->twoMachinesColor[0] == 'w')
8302               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8303             else
8304               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8305             break;
8306           default:
8307             /* can't happen */
8308             break;
8309         }
8310         return;
8311     } else if (strncmp(message, "checkmate", 9) == 0) {
8312         if (WhiteOnMove(forwardMostMove)) {
8313             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8314         } else {
8315             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8316         }
8317         return;
8318     } else if (strstr(message, "Draw") != NULL ||
8319                strstr(message, "game is a draw") != NULL) {
8320         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8321         return;
8322     } else if (strstr(message, "offer") != NULL &&
8323                strstr(message, "draw") != NULL) {
8324 #if ZIPPY
8325         if (appData.zippyPlay && first.initDone) {
8326             /* Relay offer to ICS */
8327             SendToICS(ics_prefix);
8328             SendToICS("draw\n");
8329         }
8330 #endif
8331         cps->offeredDraw = 2; /* valid until this engine moves twice */
8332         if (gameMode == TwoMachinesPlay) {
8333             if (cps->other->offeredDraw) {
8334                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8335             /* [HGM] in two-machine mode we delay relaying draw offer      */
8336             /* until after we also have move, to see if it is really claim */
8337             }
8338         } else if (gameMode == MachinePlaysWhite ||
8339                    gameMode == MachinePlaysBlack) {
8340           if (userOfferedDraw) {
8341             DisplayInformation(_("Machine accepts your draw offer"));
8342             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8343           } else {
8344             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8345           }
8346         }
8347     }
8348
8349
8350     /*
8351      * Look for thinking output
8352      */
8353     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8354           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8355                                 ) {
8356         int plylev, mvleft, mvtot, curscore, time;
8357         char mvname[MOVE_LEN];
8358         u64 nodes; // [DM]
8359         char plyext;
8360         int ignore = FALSE;
8361         int prefixHint = FALSE;
8362         mvname[0] = NULLCHAR;
8363
8364         switch (gameMode) {
8365           case MachinePlaysBlack:
8366           case IcsPlayingBlack:
8367             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8368             break;
8369           case MachinePlaysWhite:
8370           case IcsPlayingWhite:
8371             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8372             break;
8373           case AnalyzeMode:
8374           case AnalyzeFile:
8375             break;
8376           case IcsObserving: /* [DM] icsEngineAnalyze */
8377             if (!appData.icsEngineAnalyze) ignore = TRUE;
8378             break;
8379           case TwoMachinesPlay:
8380             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8381                 ignore = TRUE;
8382             }
8383             break;
8384           default:
8385             ignore = TRUE;
8386             break;
8387         }
8388
8389         if (!ignore) {
8390             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8391             buf1[0] = NULLCHAR;
8392             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8393                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8394
8395                 if (plyext != ' ' && plyext != '\t') {
8396                     time *= 100;
8397                 }
8398
8399                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8400                 if( cps->scoreIsAbsolute &&
8401                     ( gameMode == MachinePlaysBlack ||
8402                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8403                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8404                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8405                      !WhiteOnMove(currentMove)
8406                     ) )
8407                 {
8408                     curscore = -curscore;
8409                 }
8410
8411
8412                 tempStats.depth = plylev;
8413                 tempStats.nodes = nodes;
8414                 tempStats.time = time;
8415                 tempStats.score = curscore;
8416                 tempStats.got_only_move = 0;
8417
8418                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8419                         int ticklen;
8420
8421                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8422                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8423                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8424                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8425                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8426                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8427                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8428                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8429                 }
8430
8431                 /* Buffer overflow protection */
8432                 if (buf1[0] != NULLCHAR) {
8433                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8434                         && appData.debugMode) {
8435                         fprintf(debugFP,
8436                                 "PV is too long; using the first %u bytes.\n",
8437                                 (unsigned) sizeof(tempStats.movelist) - 1);
8438                     }
8439
8440                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8441                 } else {
8442                     sprintf(tempStats.movelist, " no PV\n");
8443                 }
8444
8445                 if (tempStats.seen_stat) {
8446                     tempStats.ok_to_send = 1;
8447                 }
8448
8449                 if (strchr(tempStats.movelist, '(') != NULL) {
8450                     tempStats.line_is_book = 1;
8451                     tempStats.nr_moves = 0;
8452                     tempStats.moves_left = 0;
8453                 } else {
8454                     tempStats.line_is_book = 0;
8455                 }
8456
8457                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8458                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8459
8460                 SendProgramStatsToFrontend( cps, &tempStats );
8461
8462                 /*
8463                     [AS] Protect the thinkOutput buffer from overflow... this
8464                     is only useful if buf1 hasn't overflowed first!
8465                 */
8466                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8467                          plylev,
8468                          (gameMode == TwoMachinesPlay ?
8469                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8470                          ((double) curscore) / 100.0,
8471                          prefixHint ? lastHint : "",
8472                          prefixHint ? " " : "" );
8473
8474                 if( buf1[0] != NULLCHAR ) {
8475                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8476
8477                     if( strlen(buf1) > max_len ) {
8478                         if( appData.debugMode) {
8479                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8480                         }
8481                         buf1[max_len+1] = '\0';
8482                     }
8483
8484                     strcat( thinkOutput, buf1 );
8485                 }
8486
8487                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8488                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8489                     DisplayMove(currentMove - 1);
8490                 }
8491                 return;
8492
8493             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8494                 /* crafty (9.25+) says "(only move) <move>"
8495                  * if there is only 1 legal move
8496                  */
8497                 sscanf(p, "(only move) %s", buf1);
8498                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8499                 sprintf(programStats.movelist, "%s (only move)", buf1);
8500                 programStats.depth = 1;
8501                 programStats.nr_moves = 1;
8502                 programStats.moves_left = 1;
8503                 programStats.nodes = 1;
8504                 programStats.time = 1;
8505                 programStats.got_only_move = 1;
8506
8507                 /* Not really, but we also use this member to
8508                    mean "line isn't going to change" (Crafty
8509                    isn't searching, so stats won't change) */
8510                 programStats.line_is_book = 1;
8511
8512                 SendProgramStatsToFrontend( cps, &programStats );
8513
8514                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8515                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8516                     DisplayMove(currentMove - 1);
8517                 }
8518                 return;
8519             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8520                               &time, &nodes, &plylev, &mvleft,
8521                               &mvtot, mvname) >= 5) {
8522                 /* The stat01: line is from Crafty (9.29+) in response
8523                    to the "." command */
8524                 programStats.seen_stat = 1;
8525                 cps->maybeThinking = TRUE;
8526
8527                 if (programStats.got_only_move || !appData.periodicUpdates)
8528                   return;
8529
8530                 programStats.depth = plylev;
8531                 programStats.time = time;
8532                 programStats.nodes = nodes;
8533                 programStats.moves_left = mvleft;
8534                 programStats.nr_moves = mvtot;
8535                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8536                 programStats.ok_to_send = 1;
8537                 programStats.movelist[0] = '\0';
8538
8539                 SendProgramStatsToFrontend( cps, &programStats );
8540
8541                 return;
8542
8543             } else if (strncmp(message,"++",2) == 0) {
8544                 /* Crafty 9.29+ outputs this */
8545                 programStats.got_fail = 2;
8546                 return;
8547
8548             } else if (strncmp(message,"--",2) == 0) {
8549                 /* Crafty 9.29+ outputs this */
8550                 programStats.got_fail = 1;
8551                 return;
8552
8553             } else if (thinkOutput[0] != NULLCHAR &&
8554                        strncmp(message, "    ", 4) == 0) {
8555                 unsigned message_len;
8556
8557                 p = message;
8558                 while (*p && *p == ' ') p++;
8559
8560                 message_len = strlen( p );
8561
8562                 /* [AS] Avoid buffer overflow */
8563                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8564                     strcat(thinkOutput, " ");
8565                     strcat(thinkOutput, p);
8566                 }
8567
8568                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8569                     strcat(programStats.movelist, " ");
8570                     strcat(programStats.movelist, p);
8571                 }
8572
8573                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8574                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8575                     DisplayMove(currentMove - 1);
8576                 }
8577                 return;
8578             }
8579         }
8580         else {
8581             buf1[0] = NULLCHAR;
8582
8583             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8584                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8585             {
8586                 ChessProgramStats cpstats;
8587
8588                 if (plyext != ' ' && plyext != '\t') {
8589                     time *= 100;
8590                 }
8591
8592                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8593                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8594                     curscore = -curscore;
8595                 }
8596
8597                 cpstats.depth = plylev;
8598                 cpstats.nodes = nodes;
8599                 cpstats.time = time;
8600                 cpstats.score = curscore;
8601                 cpstats.got_only_move = 0;
8602                 cpstats.movelist[0] = '\0';
8603
8604                 if (buf1[0] != NULLCHAR) {
8605                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8606                 }
8607
8608                 cpstats.ok_to_send = 0;
8609                 cpstats.line_is_book = 0;
8610                 cpstats.nr_moves = 0;
8611                 cpstats.moves_left = 0;
8612
8613                 SendProgramStatsToFrontend( cps, &cpstats );
8614             }
8615         }
8616     }
8617 }
8618
8619
8620 /* Parse a game score from the character string "game", and
8621    record it as the history of the current game.  The game
8622    score is NOT assumed to start from the standard position.
8623    The display is not updated in any way.
8624    */
8625 void
8626 ParseGameHistory(game)
8627      char *game;
8628 {
8629     ChessMove moveType;
8630     int fromX, fromY, toX, toY, boardIndex;
8631     char promoChar;
8632     char *p, *q;
8633     char buf[MSG_SIZ];
8634
8635     if (appData.debugMode)
8636       fprintf(debugFP, "Parsing game history: %s\n", game);
8637
8638     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8639     gameInfo.site = StrSave(appData.icsHost);
8640     gameInfo.date = PGNDate();
8641     gameInfo.round = StrSave("-");
8642
8643     /* Parse out names of players */
8644     while (*game == ' ') game++;
8645     p = buf;
8646     while (*game != ' ') *p++ = *game++;
8647     *p = NULLCHAR;
8648     gameInfo.white = StrSave(buf);
8649     while (*game == ' ') game++;
8650     p = buf;
8651     while (*game != ' ' && *game != '\n') *p++ = *game++;
8652     *p = NULLCHAR;
8653     gameInfo.black = StrSave(buf);
8654
8655     /* Parse moves */
8656     boardIndex = blackPlaysFirst ? 1 : 0;
8657     yynewstr(game);
8658     for (;;) {
8659         yyboardindex = boardIndex;
8660         moveType = (ChessMove) Myylex();
8661         switch (moveType) {
8662           case IllegalMove:             /* maybe suicide chess, etc. */
8663   if (appData.debugMode) {
8664     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8665     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8666     setbuf(debugFP, NULL);
8667   }
8668           case WhitePromotion:
8669           case BlackPromotion:
8670           case WhiteNonPromotion:
8671           case BlackNonPromotion:
8672           case NormalMove:
8673           case WhiteCapturesEnPassant:
8674           case BlackCapturesEnPassant:
8675           case WhiteKingSideCastle:
8676           case WhiteQueenSideCastle:
8677           case BlackKingSideCastle:
8678           case BlackQueenSideCastle:
8679           case WhiteKingSideCastleWild:
8680           case WhiteQueenSideCastleWild:
8681           case BlackKingSideCastleWild:
8682           case BlackQueenSideCastleWild:
8683           /* PUSH Fabien */
8684           case WhiteHSideCastleFR:
8685           case WhiteASideCastleFR:
8686           case BlackHSideCastleFR:
8687           case BlackASideCastleFR:
8688           /* POP Fabien */
8689             fromX = currentMoveString[0] - AAA;
8690             fromY = currentMoveString[1] - ONE;
8691             toX = currentMoveString[2] - AAA;
8692             toY = currentMoveString[3] - ONE;
8693             promoChar = currentMoveString[4];
8694             break;
8695           case WhiteDrop:
8696           case BlackDrop:
8697             fromX = moveType == WhiteDrop ?
8698               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8699             (int) CharToPiece(ToLower(currentMoveString[0]));
8700             fromY = DROP_RANK;
8701             toX = currentMoveString[2] - AAA;
8702             toY = currentMoveString[3] - ONE;
8703             promoChar = NULLCHAR;
8704             break;
8705           case AmbiguousMove:
8706             /* bug? */
8707             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8708   if (appData.debugMode) {
8709     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8710     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8711     setbuf(debugFP, NULL);
8712   }
8713             DisplayError(buf, 0);
8714             return;
8715           case ImpossibleMove:
8716             /* bug? */
8717             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8718   if (appData.debugMode) {
8719     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8720     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8721     setbuf(debugFP, NULL);
8722   }
8723             DisplayError(buf, 0);
8724             return;
8725           case EndOfFile:
8726             if (boardIndex < backwardMostMove) {
8727                 /* Oops, gap.  How did that happen? */
8728                 DisplayError(_("Gap in move list"), 0);
8729                 return;
8730             }
8731             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8732             if (boardIndex > forwardMostMove) {
8733                 forwardMostMove = boardIndex;
8734             }
8735             return;
8736           case ElapsedTime:
8737             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8738                 strcat(parseList[boardIndex-1], " ");
8739                 strcat(parseList[boardIndex-1], yy_text);
8740             }
8741             continue;
8742           case Comment:
8743           case PGNTag:
8744           case NAG:
8745           default:
8746             /* ignore */
8747             continue;
8748           case WhiteWins:
8749           case BlackWins:
8750           case GameIsDrawn:
8751           case GameUnfinished:
8752             if (gameMode == IcsExamining) {
8753                 if (boardIndex < backwardMostMove) {
8754                     /* Oops, gap.  How did that happen? */
8755                     return;
8756                 }
8757                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8758                 return;
8759             }
8760             gameInfo.result = moveType;
8761             p = strchr(yy_text, '{');
8762             if (p == NULL) p = strchr(yy_text, '(');
8763             if (p == NULL) {
8764                 p = yy_text;
8765                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8766             } else {
8767                 q = strchr(p, *p == '{' ? '}' : ')');
8768                 if (q != NULL) *q = NULLCHAR;
8769                 p++;
8770             }
8771             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8772             gameInfo.resultDetails = StrSave(p);
8773             continue;
8774         }
8775         if (boardIndex >= forwardMostMove &&
8776             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8777             backwardMostMove = blackPlaysFirst ? 1 : 0;
8778             return;
8779         }
8780         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8781                                  fromY, fromX, toY, toX, promoChar,
8782                                  parseList[boardIndex]);
8783         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8784         /* currentMoveString is set as a side-effect of yylex */
8785         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8786         strcat(moveList[boardIndex], "\n");
8787         boardIndex++;
8788         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8789         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8790           case MT_NONE:
8791           case MT_STALEMATE:
8792           default:
8793             break;
8794           case MT_CHECK:
8795             if(gameInfo.variant != VariantShogi)
8796                 strcat(parseList[boardIndex - 1], "+");
8797             break;
8798           case MT_CHECKMATE:
8799           case MT_STAINMATE:
8800             strcat(parseList[boardIndex - 1], "#");
8801             break;
8802         }
8803     }
8804 }
8805
8806
8807 /* Apply a move to the given board  */
8808 void
8809 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8810      int fromX, fromY, toX, toY;
8811      int promoChar;
8812      Board board;
8813 {
8814   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8815   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8816
8817     /* [HGM] compute & store e.p. status and castling rights for new position */
8818     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8819
8820       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8821       oldEP = (signed char)board[EP_STATUS];
8822       board[EP_STATUS] = EP_NONE;
8823
8824       if( board[toY][toX] != EmptySquare )
8825            board[EP_STATUS] = EP_CAPTURE;
8826
8827   if (fromY == DROP_RANK) {
8828         /* must be first */
8829         piece = board[toY][toX] = (ChessSquare) fromX;
8830   } else {
8831       int i;
8832
8833       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8834            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8835                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8836       } else
8837       if( board[fromY][fromX] == WhitePawn ) {
8838            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8839                board[EP_STATUS] = EP_PAWN_MOVE;
8840            if( toY-fromY==2) {
8841                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8842                         gameInfo.variant != VariantBerolina || toX < fromX)
8843                       board[EP_STATUS] = toX | berolina;
8844                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8845                         gameInfo.variant != VariantBerolina || toX > fromX)
8846                       board[EP_STATUS] = toX;
8847            }
8848       } else
8849       if( board[fromY][fromX] == BlackPawn ) {
8850            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8851                board[EP_STATUS] = EP_PAWN_MOVE;
8852            if( toY-fromY== -2) {
8853                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8854                         gameInfo.variant != VariantBerolina || toX < fromX)
8855                       board[EP_STATUS] = toX | berolina;
8856                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8857                         gameInfo.variant != VariantBerolina || toX > fromX)
8858                       board[EP_STATUS] = toX;
8859            }
8860        }
8861
8862        for(i=0; i<nrCastlingRights; i++) {
8863            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8864               board[CASTLING][i] == toX   && castlingRank[i] == toY
8865              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8866        }
8867
8868      if (fromX == toX && fromY == toY) return;
8869
8870      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8871      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8872      if(gameInfo.variant == VariantKnightmate)
8873          king += (int) WhiteUnicorn - (int) WhiteKing;
8874
8875     /* Code added by Tord: */
8876     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8877     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8878         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8879       board[fromY][fromX] = EmptySquare;
8880       board[toY][toX] = EmptySquare;
8881       if((toX > fromX) != (piece == WhiteRook)) {
8882         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8883       } else {
8884         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8885       }
8886     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8887                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8888       board[fromY][fromX] = EmptySquare;
8889       board[toY][toX] = EmptySquare;
8890       if((toX > fromX) != (piece == BlackRook)) {
8891         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8892       } else {
8893         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8894       }
8895     /* End of code added by Tord */
8896
8897     } else if (board[fromY][fromX] == king
8898         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8899         && toY == fromY && toX > fromX+1) {
8900         board[fromY][fromX] = EmptySquare;
8901         board[toY][toX] = king;
8902         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8903         board[fromY][BOARD_RGHT-1] = EmptySquare;
8904     } else if (board[fromY][fromX] == king
8905         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8906                && toY == fromY && toX < fromX-1) {
8907         board[fromY][fromX] = EmptySquare;
8908         board[toY][toX] = king;
8909         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8910         board[fromY][BOARD_LEFT] = EmptySquare;
8911     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8912                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8913                && toY >= BOARD_HEIGHT-promoRank
8914                ) {
8915         /* white pawn promotion */
8916         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8917         if (board[toY][toX] == EmptySquare) {
8918             board[toY][toX] = WhiteQueen;
8919         }
8920         if(gameInfo.variant==VariantBughouse ||
8921            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8922             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8923         board[fromY][fromX] = EmptySquare;
8924     } else if ((fromY == BOARD_HEIGHT-4)
8925                && (toX != fromX)
8926                && gameInfo.variant != VariantXiangqi
8927                && gameInfo.variant != VariantBerolina
8928                && (board[fromY][fromX] == WhitePawn)
8929                && (board[toY][toX] == EmptySquare)) {
8930         board[fromY][fromX] = EmptySquare;
8931         board[toY][toX] = WhitePawn;
8932         captured = board[toY - 1][toX];
8933         board[toY - 1][toX] = EmptySquare;
8934     } else if ((fromY == BOARD_HEIGHT-4)
8935                && (toX == fromX)
8936                && gameInfo.variant == VariantBerolina
8937                && (board[fromY][fromX] == WhitePawn)
8938                && (board[toY][toX] == EmptySquare)) {
8939         board[fromY][fromX] = EmptySquare;
8940         board[toY][toX] = WhitePawn;
8941         if(oldEP & EP_BEROLIN_A) {
8942                 captured = board[fromY][fromX-1];
8943                 board[fromY][fromX-1] = EmptySquare;
8944         }else{  captured = board[fromY][fromX+1];
8945                 board[fromY][fromX+1] = EmptySquare;
8946         }
8947     } else if (board[fromY][fromX] == king
8948         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8949                && toY == fromY && toX > fromX+1) {
8950         board[fromY][fromX] = EmptySquare;
8951         board[toY][toX] = king;
8952         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8953         board[fromY][BOARD_RGHT-1] = EmptySquare;
8954     } else if (board[fromY][fromX] == king
8955         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8956                && toY == fromY && toX < fromX-1) {
8957         board[fromY][fromX] = EmptySquare;
8958         board[toY][toX] = king;
8959         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8960         board[fromY][BOARD_LEFT] = EmptySquare;
8961     } else if (fromY == 7 && fromX == 3
8962                && board[fromY][fromX] == BlackKing
8963                && toY == 7 && toX == 5) {
8964         board[fromY][fromX] = EmptySquare;
8965         board[toY][toX] = BlackKing;
8966         board[fromY][7] = EmptySquare;
8967         board[toY][4] = BlackRook;
8968     } else if (fromY == 7 && fromX == 3
8969                && board[fromY][fromX] == BlackKing
8970                && toY == 7 && toX == 1) {
8971         board[fromY][fromX] = EmptySquare;
8972         board[toY][toX] = BlackKing;
8973         board[fromY][0] = EmptySquare;
8974         board[toY][2] = BlackRook;
8975     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8976                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8977                && toY < promoRank
8978                ) {
8979         /* black pawn promotion */
8980         board[toY][toX] = CharToPiece(ToLower(promoChar));
8981         if (board[toY][toX] == EmptySquare) {
8982             board[toY][toX] = BlackQueen;
8983         }
8984         if(gameInfo.variant==VariantBughouse ||
8985            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8986             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8987         board[fromY][fromX] = EmptySquare;
8988     } else if ((fromY == 3)
8989                && (toX != fromX)
8990                && gameInfo.variant != VariantXiangqi
8991                && gameInfo.variant != VariantBerolina
8992                && (board[fromY][fromX] == BlackPawn)
8993                && (board[toY][toX] == EmptySquare)) {
8994         board[fromY][fromX] = EmptySquare;
8995         board[toY][toX] = BlackPawn;
8996         captured = board[toY + 1][toX];
8997         board[toY + 1][toX] = EmptySquare;
8998     } else if ((fromY == 3)
8999                && (toX == fromX)
9000                && gameInfo.variant == VariantBerolina
9001                && (board[fromY][fromX] == BlackPawn)
9002                && (board[toY][toX] == EmptySquare)) {
9003         board[fromY][fromX] = EmptySquare;
9004         board[toY][toX] = BlackPawn;
9005         if(oldEP & EP_BEROLIN_A) {
9006                 captured = board[fromY][fromX-1];
9007                 board[fromY][fromX-1] = EmptySquare;
9008         }else{  captured = board[fromY][fromX+1];
9009                 board[fromY][fromX+1] = EmptySquare;
9010         }
9011     } else {
9012         board[toY][toX] = board[fromY][fromX];
9013         board[fromY][fromX] = EmptySquare;
9014     }
9015   }
9016
9017     if (gameInfo.holdingsWidth != 0) {
9018
9019       /* !!A lot more code needs to be written to support holdings  */
9020       /* [HGM] OK, so I have written it. Holdings are stored in the */
9021       /* penultimate board files, so they are automaticlly stored   */
9022       /* in the game history.                                       */
9023       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9024                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9025         /* Delete from holdings, by decreasing count */
9026         /* and erasing image if necessary            */
9027         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9028         if(p < (int) BlackPawn) { /* white drop */
9029              p -= (int)WhitePawn;
9030                  p = PieceToNumber((ChessSquare)p);
9031              if(p >= gameInfo.holdingsSize) p = 0;
9032              if(--board[p][BOARD_WIDTH-2] <= 0)
9033                   board[p][BOARD_WIDTH-1] = EmptySquare;
9034              if((int)board[p][BOARD_WIDTH-2] < 0)
9035                         board[p][BOARD_WIDTH-2] = 0;
9036         } else {                  /* black drop */
9037              p -= (int)BlackPawn;
9038                  p = PieceToNumber((ChessSquare)p);
9039              if(p >= gameInfo.holdingsSize) p = 0;
9040              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9041                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9042              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9043                         board[BOARD_HEIGHT-1-p][1] = 0;
9044         }
9045       }
9046       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9047           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9048         /* [HGM] holdings: Add to holdings, if holdings exist */
9049         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9050                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9051                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9052         }
9053         p = (int) captured;
9054         if (p >= (int) BlackPawn) {
9055           p -= (int)BlackPawn;
9056           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9057                   /* in Shogi restore piece to its original  first */
9058                   captured = (ChessSquare) (DEMOTED captured);
9059                   p = DEMOTED p;
9060           }
9061           p = PieceToNumber((ChessSquare)p);
9062           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9063           board[p][BOARD_WIDTH-2]++;
9064           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9065         } else {
9066           p -= (int)WhitePawn;
9067           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9068                   captured = (ChessSquare) (DEMOTED captured);
9069                   p = DEMOTED p;
9070           }
9071           p = PieceToNumber((ChessSquare)p);
9072           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9073           board[BOARD_HEIGHT-1-p][1]++;
9074           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9075         }
9076       }
9077     } else if (gameInfo.variant == VariantAtomic) {
9078       if (captured != EmptySquare) {
9079         int y, x;
9080         for (y = toY-1; y <= toY+1; y++) {
9081           for (x = toX-1; x <= toX+1; x++) {
9082             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9083                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9084               board[y][x] = EmptySquare;
9085             }
9086           }
9087         }
9088         board[toY][toX] = EmptySquare;
9089       }
9090     }
9091     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9092         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9093     } else
9094     if(promoChar == '+') {
9095         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9096         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9097     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9098         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9099     }
9100     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9101                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9102         // [HGM] superchess: take promotion piece out of holdings
9103         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9104         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9105             if(!--board[k][BOARD_WIDTH-2])
9106                 board[k][BOARD_WIDTH-1] = EmptySquare;
9107         } else {
9108             if(!--board[BOARD_HEIGHT-1-k][1])
9109                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9110         }
9111     }
9112
9113 }
9114
9115 /* Updates forwardMostMove */
9116 void
9117 MakeMove(fromX, fromY, toX, toY, promoChar)
9118      int fromX, fromY, toX, toY;
9119      int promoChar;
9120 {
9121 //    forwardMostMove++; // [HGM] bare: moved downstream
9122
9123     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9124         int timeLeft; static int lastLoadFlag=0; int king, piece;
9125         piece = boards[forwardMostMove][fromY][fromX];
9126         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9127         if(gameInfo.variant == VariantKnightmate)
9128             king += (int) WhiteUnicorn - (int) WhiteKing;
9129         if(forwardMostMove == 0) {
9130             if(blackPlaysFirst)
9131                 fprintf(serverMoves, "%s;", second.tidy);
9132             fprintf(serverMoves, "%s;", first.tidy);
9133             if(!blackPlaysFirst)
9134                 fprintf(serverMoves, "%s;", second.tidy);
9135         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9136         lastLoadFlag = loadFlag;
9137         // print base move
9138         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9139         // print castling suffix
9140         if( toY == fromY && piece == king ) {
9141             if(toX-fromX > 1)
9142                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9143             if(fromX-toX >1)
9144                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9145         }
9146         // e.p. suffix
9147         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9148              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9149              boards[forwardMostMove][toY][toX] == EmptySquare
9150              && fromX != toX && fromY != toY)
9151                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9152         // promotion suffix
9153         if(promoChar != NULLCHAR)
9154                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9155         if(!loadFlag) {
9156             fprintf(serverMoves, "/%d/%d",
9157                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9158             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9159             else                      timeLeft = blackTimeRemaining/1000;
9160             fprintf(serverMoves, "/%d", timeLeft);
9161         }
9162         fflush(serverMoves);
9163     }
9164
9165     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9166       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9167                         0, 1);
9168       return;
9169     }
9170     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9171     if (commentList[forwardMostMove+1] != NULL) {
9172         free(commentList[forwardMostMove+1]);
9173         commentList[forwardMostMove+1] = NULL;
9174     }
9175     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9176     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9177     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9178     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9179     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9180     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9181     gameInfo.result = GameUnfinished;
9182     if (gameInfo.resultDetails != NULL) {
9183         free(gameInfo.resultDetails);
9184         gameInfo.resultDetails = NULL;
9185     }
9186     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9187                               moveList[forwardMostMove - 1]);
9188     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9189                              PosFlags(forwardMostMove - 1),
9190                              fromY, fromX, toY, toX, promoChar,
9191                              parseList[forwardMostMove - 1]);
9192     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9193       case MT_NONE:
9194       case MT_STALEMATE:
9195       default:
9196         break;
9197       case MT_CHECK:
9198         if(gameInfo.variant != VariantShogi)
9199             strcat(parseList[forwardMostMove - 1], "+");
9200         break;
9201       case MT_CHECKMATE:
9202       case MT_STAINMATE:
9203         strcat(parseList[forwardMostMove - 1], "#");
9204         break;
9205     }
9206     if (appData.debugMode) {
9207         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9208     }
9209
9210 }
9211
9212 /* Updates currentMove if not pausing */
9213 void
9214 ShowMove(fromX, fromY, toX, toY)
9215 {
9216     int instant = (gameMode == PlayFromGameFile) ?
9217         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9218     if(appData.noGUI) return;
9219     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9220         if (!instant) {
9221             if (forwardMostMove == currentMove + 1) {
9222                 AnimateMove(boards[forwardMostMove - 1],
9223                             fromX, fromY, toX, toY);
9224             }
9225             if (appData.highlightLastMove) {
9226                 SetHighlights(fromX, fromY, toX, toY);
9227             }
9228         }
9229         currentMove = forwardMostMove;
9230     }
9231
9232     if (instant) return;
9233
9234     DisplayMove(currentMove - 1);
9235     DrawPosition(FALSE, boards[currentMove]);
9236     DisplayBothClocks();
9237     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9238 }
9239
9240 void SendEgtPath(ChessProgramState *cps)
9241 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9242         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9243
9244         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9245
9246         while(*p) {
9247             char c, *q = name+1, *r, *s;
9248
9249             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9250             while(*p && *p != ',') *q++ = *p++;
9251             *q++ = ':'; *q = 0;
9252             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9253                 strcmp(name, ",nalimov:") == 0 ) {
9254                 // take nalimov path from the menu-changeable option first, if it is defined
9255               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9256                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9257             } else
9258             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9259                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9260                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9261                 s = r = StrStr(s, ":") + 1; // beginning of path info
9262                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9263                 c = *r; *r = 0;             // temporarily null-terminate path info
9264                     *--q = 0;               // strip of trailig ':' from name
9265                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9266                 *r = c;
9267                 SendToProgram(buf,cps);     // send egtbpath command for this format
9268             }
9269             if(*p == ',') p++; // read away comma to position for next format name
9270         }
9271 }
9272
9273 void
9274 InitChessProgram(cps, setup)
9275      ChessProgramState *cps;
9276      int setup; /* [HGM] needed to setup FRC opening position */
9277 {
9278     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9279     if (appData.noChessProgram) return;
9280     hintRequested = FALSE;
9281     bookRequested = FALSE;
9282
9283     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9284     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9285     if(cps->memSize) { /* [HGM] memory */
9286       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9287         SendToProgram(buf, cps);
9288     }
9289     SendEgtPath(cps); /* [HGM] EGT */
9290     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9291       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9292         SendToProgram(buf, cps);
9293     }
9294
9295     SendToProgram(cps->initString, cps);
9296     if (gameInfo.variant != VariantNormal &&
9297         gameInfo.variant != VariantLoadable
9298         /* [HGM] also send variant if board size non-standard */
9299         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9300                                             ) {
9301       char *v = VariantName(gameInfo.variant);
9302       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9303         /* [HGM] in protocol 1 we have to assume all variants valid */
9304         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9305         DisplayFatalError(buf, 0, 1);
9306         return;
9307       }
9308
9309       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9310       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9311       if( gameInfo.variant == VariantXiangqi )
9312            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9313       if( gameInfo.variant == VariantShogi )
9314            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9315       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9316            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9317       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9318           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9319            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9320       if( gameInfo.variant == VariantCourier )
9321            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9322       if( gameInfo.variant == VariantSuper )
9323            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9324       if( gameInfo.variant == VariantGreat )
9325            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9326       if( gameInfo.variant == VariantSChess )
9327            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9328
9329       if(overruled) {
9330         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9331                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9332            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9333            if(StrStr(cps->variants, b) == NULL) {
9334                // specific sized variant not known, check if general sizing allowed
9335                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9336                    if(StrStr(cps->variants, "boardsize") == NULL) {
9337                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9338                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9339                        DisplayFatalError(buf, 0, 1);
9340                        return;
9341                    }
9342                    /* [HGM] here we really should compare with the maximum supported board size */
9343                }
9344            }
9345       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9346       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9347       SendToProgram(buf, cps);
9348     }
9349     currentlyInitializedVariant = gameInfo.variant;
9350
9351     /* [HGM] send opening position in FRC to first engine */
9352     if(setup) {
9353           SendToProgram("force\n", cps);
9354           SendBoard(cps, 0);
9355           /* engine is now in force mode! Set flag to wake it up after first move. */
9356           setboardSpoiledMachineBlack = 1;
9357     }
9358
9359     if (cps->sendICS) {
9360       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9361       SendToProgram(buf, cps);
9362     }
9363     cps->maybeThinking = FALSE;
9364     cps->offeredDraw = 0;
9365     if (!appData.icsActive) {
9366         SendTimeControl(cps, movesPerSession, timeControl,
9367                         timeIncrement, appData.searchDepth,
9368                         searchTime);
9369     }
9370     if (appData.showThinking
9371         // [HGM] thinking: four options require thinking output to be sent
9372         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9373                                 ) {
9374         SendToProgram("post\n", cps);
9375     }
9376     SendToProgram("hard\n", cps);
9377     if (!appData.ponderNextMove) {
9378         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9379            it without being sure what state we are in first.  "hard"
9380            is not a toggle, so that one is OK.
9381          */
9382         SendToProgram("easy\n", cps);
9383     }
9384     if (cps->usePing) {
9385       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9386       SendToProgram(buf, cps);
9387     }
9388     cps->initDone = TRUE;
9389 }
9390
9391
9392 void
9393 StartChessProgram(cps)
9394      ChessProgramState *cps;
9395 {
9396     char buf[MSG_SIZ];
9397     int err;
9398
9399     if (appData.noChessProgram) return;
9400     cps->initDone = FALSE;
9401
9402     if (strcmp(cps->host, "localhost") == 0) {
9403         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9404     } else if (*appData.remoteShell == NULLCHAR) {
9405         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9406     } else {
9407         if (*appData.remoteUser == NULLCHAR) {
9408           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9409                     cps->program);
9410         } else {
9411           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9412                     cps->host, appData.remoteUser, cps->program);
9413         }
9414         err = StartChildProcess(buf, "", &cps->pr);
9415     }
9416
9417     if (err != 0) {
9418       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9419         DisplayFatalError(buf, err, 1);
9420         cps->pr = NoProc;
9421         cps->isr = NULL;
9422         return;
9423     }
9424
9425     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9426     if (cps->protocolVersion > 1) {
9427       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9428       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9429       cps->comboCnt = 0;  //                and values of combo boxes
9430       SendToProgram(buf, cps);
9431     } else {
9432       SendToProgram("xboard\n", cps);
9433     }
9434 }
9435
9436 void
9437 TwoMachinesEventIfReady P((void))
9438 {
9439   static int curMess = 0;
9440   if (first.lastPing != first.lastPong) {
9441     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9442     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9443     return;
9444   }
9445   if (second.lastPing != second.lastPong) {
9446     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9447     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9448     return;
9449   }
9450   DisplayMessage("", ""); curMess = 0;
9451   ThawUI();
9452   TwoMachinesEvent();
9453 }
9454
9455 int
9456 CreateTourney(char *name)
9457 {
9458         FILE *f;
9459         if(name[0] == NULLCHAR) return 0;
9460         f = fopen(appData.tourneyFile, "r");
9461         if(f) { // file exists
9462             ParseArgsFromFile(f); // parse it
9463         } else {
9464             f = fopen(appData.tourneyFile, "w");
9465             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9466                 // create a file with tournament description
9467                 fprintf(f, "-participants {%s}\n", appData.participants);
9468                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9469                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9470                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9471                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9472                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9473                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9474                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9475                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9476                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9477                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9478                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9479                 fprintf(f, "-results \"\"\n");
9480             }
9481         }
9482         fclose(f);
9483         appData.noChessProgram = FALSE;
9484         appData.clockMode = TRUE;
9485         SetGNUMode();
9486         return 1;
9487 }
9488
9489 #define MAXENGINES 1000
9490 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9491
9492 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9493 {
9494     char buf[MSG_SIZ], *p, *q;
9495     int i=1;
9496     while(*names) {
9497         p = names; q = buf;
9498         while(*p && *p != '\n') *q++ = *p++;
9499         *q = 0;
9500         if(engineList[i]) free(engineList[i]);
9501         engineList[i] = strdup(buf);
9502         if(*p == '\n') p++;
9503         TidyProgramName(engineList[i], "localhost", buf);
9504         if(engineMnemonic[i]) free(engineMnemonic[i]);
9505         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9506             strcat(buf, " (");
9507             sscanf(q + 8, "%s", buf + strlen(buf));
9508             strcat(buf, ")");
9509         }
9510         engineMnemonic[i] = strdup(buf);
9511         names = p; i++;
9512       if(i > MAXENGINES - 2) break;
9513     }
9514     engineList[i] = NULL;
9515 }
9516
9517 // following implemented as macro to avoid type limitations
9518 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9519
9520 void SwapEngines(int n)
9521 {   // swap settings for first engine and other engine (so far only some selected options)
9522     int h;
9523     char *p;
9524     if(n == 0) return;
9525     SWAP(directory, p)
9526     SWAP(chessProgram, p)
9527     SWAP(isUCI, h)
9528     SWAP(hasOwnBookUCI, h)
9529     SWAP(protocolVersion, h)
9530     SWAP(reuse, h)
9531     SWAP(scoreIsAbsolute, h)
9532     SWAP(timeOdds, h)
9533     SWAP(logo, p)
9534 }
9535
9536 void
9537 SetPlayer(int player)
9538 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9539     int i;
9540     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9541     static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9542                                  "-firstNeedsNoncompliantFEN false -firstNPS -1";
9543     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9544     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9545     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9546     if(mnemonic[i]) {
9547         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9548         ParseArgsFromString(resetOptions);
9549         ParseArgsFromString(buf);
9550     }
9551     free(engineName);
9552 }
9553
9554 int
9555 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9556 {   // determine players from game number
9557     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9558
9559     if(appData.tourneyType == 0) {
9560         roundsPerCycle = (nPlayers - 1) | 1;
9561         pairingsPerRound = nPlayers / 2;
9562     } else if(appData.tourneyType > 0) {
9563         roundsPerCycle = nPlayers - appData.tourneyType;
9564         pairingsPerRound = appData.tourneyType;
9565     }
9566     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9567     gamesPerCycle = gamesPerRound * roundsPerCycle;
9568     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9569     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9570     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9571     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9572     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9573     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9574
9575     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9576     if(appData.roundSync) *syncInterval = gamesPerRound;
9577
9578     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9579
9580     if(appData.tourneyType == 0) {
9581         if(curPairing == (nPlayers-1)/2 ) {
9582             *whitePlayer = curRound;
9583             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9584         } else {
9585             *whitePlayer = curRound - pairingsPerRound + curPairing;
9586             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9587             *blackPlayer = curRound + pairingsPerRound - curPairing;
9588             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9589         }
9590     } else if(appData.tourneyType > 0) {
9591         *whitePlayer = curPairing;
9592         *blackPlayer = curRound + appData.tourneyType;
9593     }
9594
9595     // take care of white/black alternation per round. 
9596     // For cycles and games this is already taken care of by default, derived from matchGame!
9597     return curRound & 1;
9598 }
9599
9600 int
9601 NextTourneyGame(int nr, int *swapColors)
9602 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9603     char *p, *q;
9604     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9605     FILE *tf;
9606     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9607     tf = fopen(appData.tourneyFile, "r");
9608     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9609     ParseArgsFromFile(tf); fclose(tf);
9610
9611     p = appData.participants;
9612     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9613     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9614
9615     if(syncInterval) {
9616         p = q = appData.results;
9617         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9618         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9619             DisplayMessage(_("Waiting for other game(s)"),"");
9620             waitingForGame = TRUE;
9621             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9622             return 0;
9623         }
9624         waitingForGame = FALSE;
9625     }
9626
9627     if(first.pr != NoProc) return 1; // engines already loaded
9628
9629     // redefine engines, engine dir, etc.
9630     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9631     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9632     SwapEngines(1);
9633     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9634     SwapEngines(1);         // and make that valid for second engine by swapping
9635     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9636     InitEngine(&second, 1);
9637     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9638     return 1;
9639 }
9640
9641 void
9642 NextMatchGame()
9643 {   // performs game initialization that does not invoke engines, and then tries to start the game
9644     int firstWhite, swapColors = 0;
9645     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9646     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9647     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9648     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9649     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9650     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9651     Reset(FALSE, first.pr != NoProc);
9652     appData.noChessProgram = FALSE;
9653     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9654     TwoMachinesEvent();
9655 }
9656
9657 void UserAdjudicationEvent( int result )
9658 {
9659     ChessMove gameResult = GameIsDrawn;
9660
9661     if( result > 0 ) {
9662         gameResult = WhiteWins;
9663     }
9664     else if( result < 0 ) {
9665         gameResult = BlackWins;
9666     }
9667
9668     if( gameMode == TwoMachinesPlay ) {
9669         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9670     }
9671 }
9672
9673
9674 // [HGM] save: calculate checksum of game to make games easily identifiable
9675 int StringCheckSum(char *s)
9676 {
9677         int i = 0;
9678         if(s==NULL) return 0;
9679         while(*s) i = i*259 + *s++;
9680         return i;
9681 }
9682
9683 int GameCheckSum()
9684 {
9685         int i, sum=0;
9686         for(i=backwardMostMove; i<forwardMostMove; i++) {
9687                 sum += pvInfoList[i].depth;
9688                 sum += StringCheckSum(parseList[i]);
9689                 sum += StringCheckSum(commentList[i]);
9690                 sum *= 261;
9691         }
9692         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9693         return sum + StringCheckSum(commentList[i]);
9694 } // end of save patch
9695
9696 void
9697 GameEnds(result, resultDetails, whosays)
9698      ChessMove result;
9699      char *resultDetails;
9700      int whosays;
9701 {
9702     GameMode nextGameMode;
9703     int isIcsGame;
9704     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9705
9706     if(endingGame) return; /* [HGM] crash: forbid recursion */
9707     endingGame = 1;
9708     if(twoBoards) { // [HGM] dual: switch back to one board
9709         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9710         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9711     }
9712     if (appData.debugMode) {
9713       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9714               result, resultDetails ? resultDetails : "(null)", whosays);
9715     }
9716
9717     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9718
9719     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9720         /* If we are playing on ICS, the server decides when the
9721            game is over, but the engine can offer to draw, claim
9722            a draw, or resign.
9723          */
9724 #if ZIPPY
9725         if (appData.zippyPlay && first.initDone) {
9726             if (result == GameIsDrawn) {
9727                 /* In case draw still needs to be claimed */
9728                 SendToICS(ics_prefix);
9729                 SendToICS("draw\n");
9730             } else if (StrCaseStr(resultDetails, "resign")) {
9731                 SendToICS(ics_prefix);
9732                 SendToICS("resign\n");
9733             }
9734         }
9735 #endif
9736         endingGame = 0; /* [HGM] crash */
9737         return;
9738     }
9739
9740     /* If we're loading the game from a file, stop */
9741     if (whosays == GE_FILE) {
9742       (void) StopLoadGameTimer();
9743       gameFileFP = NULL;
9744     }
9745
9746     /* Cancel draw offers */
9747     first.offeredDraw = second.offeredDraw = 0;
9748
9749     /* If this is an ICS game, only ICS can really say it's done;
9750        if not, anyone can. */
9751     isIcsGame = (gameMode == IcsPlayingWhite ||
9752                  gameMode == IcsPlayingBlack ||
9753                  gameMode == IcsObserving    ||
9754                  gameMode == IcsExamining);
9755
9756     if (!isIcsGame || whosays == GE_ICS) {
9757         /* OK -- not an ICS game, or ICS said it was done */
9758         StopClocks();
9759         if (!isIcsGame && !appData.noChessProgram)
9760           SetUserThinkingEnables();
9761
9762         /* [HGM] if a machine claims the game end we verify this claim */
9763         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9764             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9765                 char claimer;
9766                 ChessMove trueResult = (ChessMove) -1;
9767
9768                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9769                                             first.twoMachinesColor[0] :
9770                                             second.twoMachinesColor[0] ;
9771
9772                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9773                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9774                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9775                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9776                 } else
9777                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9778                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9779                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9780                 } else
9781                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9782                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9783                 }
9784
9785                 // now verify win claims, but not in drop games, as we don't understand those yet
9786                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9787                                                  || gameInfo.variant == VariantGreat) &&
9788                     (result == WhiteWins && claimer == 'w' ||
9789                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9790                       if (appData.debugMode) {
9791                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9792                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9793                       }
9794                       if(result != trueResult) {
9795                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9796                               result = claimer == 'w' ? BlackWins : WhiteWins;
9797                               resultDetails = buf;
9798                       }
9799                 } else
9800                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9801                     && (forwardMostMove <= backwardMostMove ||
9802                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9803                         (claimer=='b')==(forwardMostMove&1))
9804                                                                                   ) {
9805                       /* [HGM] verify: draws that were not flagged are false claims */
9806                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9807                       result = claimer == 'w' ? BlackWins : WhiteWins;
9808                       resultDetails = buf;
9809                 }
9810                 /* (Claiming a loss is accepted no questions asked!) */
9811             }
9812             /* [HGM] bare: don't allow bare King to win */
9813             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9814                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9815                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9816                && result != GameIsDrawn)
9817             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9818                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9819                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9820                         if(p >= 0 && p <= (int)WhiteKing) k++;
9821                 }
9822                 if (appData.debugMode) {
9823                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9824                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9825                 }
9826                 if(k <= 1) {
9827                         result = GameIsDrawn;
9828                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9829                         resultDetails = buf;
9830                 }
9831             }
9832         }
9833
9834
9835         if(serverMoves != NULL && !loadFlag) { char c = '=';
9836             if(result==WhiteWins) c = '+';
9837             if(result==BlackWins) c = '-';
9838             if(resultDetails != NULL)
9839                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9840         }
9841         if (resultDetails != NULL) {
9842             gameInfo.result = result;
9843             gameInfo.resultDetails = StrSave(resultDetails);
9844
9845             /* display last move only if game was not loaded from file */
9846             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9847                 DisplayMove(currentMove - 1);
9848
9849             if (forwardMostMove != 0) {
9850                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9851                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9852                                                                 ) {
9853                     if (*appData.saveGameFile != NULLCHAR) {
9854                         SaveGameToFile(appData.saveGameFile, TRUE);
9855                     } else if (appData.autoSaveGames) {
9856                         AutoSaveGame();
9857                     }
9858                     if (*appData.savePositionFile != NULLCHAR) {
9859                         SavePositionToFile(appData.savePositionFile);
9860                     }
9861                 }
9862             }
9863
9864             /* Tell program how game ended in case it is learning */
9865             /* [HGM] Moved this to after saving the PGN, just in case */
9866             /* engine died and we got here through time loss. In that */
9867             /* case we will get a fatal error writing the pipe, which */
9868             /* would otherwise lose us the PGN.                       */
9869             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9870             /* output during GameEnds should never be fatal anymore   */
9871             if (gameMode == MachinePlaysWhite ||
9872                 gameMode == MachinePlaysBlack ||
9873                 gameMode == TwoMachinesPlay ||
9874                 gameMode == IcsPlayingWhite ||
9875                 gameMode == IcsPlayingBlack ||
9876                 gameMode == BeginningOfGame) {
9877                 char buf[MSG_SIZ];
9878                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9879                         resultDetails);
9880                 if (first.pr != NoProc) {
9881                     SendToProgram(buf, &first);
9882                 }
9883                 if (second.pr != NoProc &&
9884                     gameMode == TwoMachinesPlay) {
9885                     SendToProgram(buf, &second);
9886                 }
9887             }
9888         }
9889
9890         if (appData.icsActive) {
9891             if (appData.quietPlay &&
9892                 (gameMode == IcsPlayingWhite ||
9893                  gameMode == IcsPlayingBlack)) {
9894                 SendToICS(ics_prefix);
9895                 SendToICS("set shout 1\n");
9896             }
9897             nextGameMode = IcsIdle;
9898             ics_user_moved = FALSE;
9899             /* clean up premove.  It's ugly when the game has ended and the
9900              * premove highlights are still on the board.
9901              */
9902             if (gotPremove) {
9903               gotPremove = FALSE;
9904               ClearPremoveHighlights();
9905               DrawPosition(FALSE, boards[currentMove]);
9906             }
9907             if (whosays == GE_ICS) {
9908                 switch (result) {
9909                 case WhiteWins:
9910                     if (gameMode == IcsPlayingWhite)
9911                         PlayIcsWinSound();
9912                     else if(gameMode == IcsPlayingBlack)
9913                         PlayIcsLossSound();
9914                     break;
9915                 case BlackWins:
9916                     if (gameMode == IcsPlayingBlack)
9917                         PlayIcsWinSound();
9918                     else if(gameMode == IcsPlayingWhite)
9919                         PlayIcsLossSound();
9920                     break;
9921                 case GameIsDrawn:
9922                     PlayIcsDrawSound();
9923                     break;
9924                 default:
9925                     PlayIcsUnfinishedSound();
9926                 }
9927             }
9928         } else if (gameMode == EditGame ||
9929                    gameMode == PlayFromGameFile ||
9930                    gameMode == AnalyzeMode ||
9931                    gameMode == AnalyzeFile) {
9932             nextGameMode = gameMode;
9933         } else {
9934             nextGameMode = EndOfGame;
9935         }
9936         pausing = FALSE;
9937         ModeHighlight();
9938     } else {
9939         nextGameMode = gameMode;
9940     }
9941
9942     if (appData.noChessProgram) {
9943         gameMode = nextGameMode;
9944         ModeHighlight();
9945         endingGame = 0; /* [HGM] crash */
9946         return;
9947     }
9948
9949     if (first.reuse) {
9950         /* Put first chess program into idle state */
9951         if (first.pr != NoProc &&
9952             (gameMode == MachinePlaysWhite ||
9953              gameMode == MachinePlaysBlack ||
9954              gameMode == TwoMachinesPlay ||
9955              gameMode == IcsPlayingWhite ||
9956              gameMode == IcsPlayingBlack ||
9957              gameMode == BeginningOfGame)) {
9958             SendToProgram("force\n", &first);
9959             if (first.usePing) {
9960               char buf[MSG_SIZ];
9961               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9962               SendToProgram(buf, &first);
9963             }
9964         }
9965     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9966         /* Kill off first chess program */
9967         if (first.isr != NULL)
9968           RemoveInputSource(first.isr);
9969         first.isr = NULL;
9970
9971         if (first.pr != NoProc) {
9972             ExitAnalyzeMode();
9973             DoSleep( appData.delayBeforeQuit );
9974             SendToProgram("quit\n", &first);
9975             DoSleep( appData.delayAfterQuit );
9976             DestroyChildProcess(first.pr, first.useSigterm);
9977         }
9978         first.pr = NoProc;
9979     }
9980     if (second.reuse) {
9981         /* Put second chess program into idle state */
9982         if (second.pr != NoProc &&
9983             gameMode == TwoMachinesPlay) {
9984             SendToProgram("force\n", &second);
9985             if (second.usePing) {
9986               char buf[MSG_SIZ];
9987               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9988               SendToProgram(buf, &second);
9989             }
9990         }
9991     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9992         /* Kill off second chess program */
9993         if (second.isr != NULL)
9994           RemoveInputSource(second.isr);
9995         second.isr = NULL;
9996
9997         if (second.pr != NoProc) {
9998             DoSleep( appData.delayBeforeQuit );
9999             SendToProgram("quit\n", &second);
10000             DoSleep( appData.delayAfterQuit );
10001             DestroyChildProcess(second.pr, second.useSigterm);
10002         }
10003         second.pr = NoProc;
10004     }
10005
10006     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10007         char resChar = '=';
10008         switch (result) {
10009         case WhiteWins:
10010           resChar = '+';
10011           if (first.twoMachinesColor[0] == 'w') {
10012             first.matchWins++;
10013           } else {
10014             second.matchWins++;
10015           }
10016           break;
10017         case BlackWins:
10018           resChar = '-';
10019           if (first.twoMachinesColor[0] == 'b') {
10020             first.matchWins++;
10021           } else {
10022             second.matchWins++;
10023           }
10024           break;
10025         case GameUnfinished:
10026           resChar = ' ';
10027         default:
10028           break;
10029         }
10030
10031         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10032         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10033             ReserveGame(nextGame, resChar); // sets nextGame
10034             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10035         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10036
10037         if (nextGame <= appData.matchGames && !abortMatch) {
10038             gameMode = nextGameMode;
10039             matchGame = nextGame; // this will be overruled in tourney mode!
10040             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10041             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10042             endingGame = 0; /* [HGM] crash */
10043             return;
10044         } else {
10045             gameMode = nextGameMode;
10046             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10047                      first.tidy, second.tidy,
10048                      first.matchWins, second.matchWins,
10049                      appData.matchGames - (first.matchWins + second.matchWins));
10050             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10051             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10052                 first.twoMachinesColor = "black\n";
10053                 second.twoMachinesColor = "white\n";
10054             } else {
10055                 first.twoMachinesColor = "white\n";
10056                 second.twoMachinesColor = "black\n";
10057             }
10058         }
10059     }
10060     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10061         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10062       ExitAnalyzeMode();
10063     gameMode = nextGameMode;
10064     ModeHighlight();
10065     endingGame = 0;  /* [HGM] crash */
10066     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10067         if(matchMode == TRUE) { // match through command line: exit with or without popup
10068             if(ranking) {
10069                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10070                 else ExitEvent(0);
10071             } else DisplayFatalError(buf, 0, 0);
10072         } else { // match through menu; just stop, with or without popup
10073             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10074             if(ranking){
10075                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10076             } else DisplayNote(buf);
10077       }
10078       if(ranking) free(ranking);
10079     }
10080 }
10081
10082 /* Assumes program was just initialized (initString sent).
10083    Leaves program in force mode. */
10084 void
10085 FeedMovesToProgram(cps, upto)
10086      ChessProgramState *cps;
10087      int upto;
10088 {
10089     int i;
10090
10091     if (appData.debugMode)
10092       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10093               startedFromSetupPosition ? "position and " : "",
10094               backwardMostMove, upto, cps->which);
10095     if(currentlyInitializedVariant != gameInfo.variant) {
10096       char buf[MSG_SIZ];
10097         // [HGM] variantswitch: make engine aware of new variant
10098         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10099                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10100         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10101         SendToProgram(buf, cps);
10102         currentlyInitializedVariant = gameInfo.variant;
10103     }
10104     SendToProgram("force\n", cps);
10105     if (startedFromSetupPosition) {
10106         SendBoard(cps, backwardMostMove);
10107     if (appData.debugMode) {
10108         fprintf(debugFP, "feedMoves\n");
10109     }
10110     }
10111     for (i = backwardMostMove; i < upto; i++) {
10112         SendMoveToProgram(i, cps);
10113     }
10114 }
10115
10116
10117 int
10118 ResurrectChessProgram()
10119 {
10120      /* The chess program may have exited.
10121         If so, restart it and feed it all the moves made so far. */
10122     static int doInit = 0;
10123
10124     if (appData.noChessProgram) return 1;
10125
10126     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10127         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10128         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10129         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10130     } else {
10131         if (first.pr != NoProc) return 1;
10132         StartChessProgram(&first);
10133     }
10134     InitChessProgram(&first, FALSE);
10135     FeedMovesToProgram(&first, currentMove);
10136
10137     if (!first.sendTime) {
10138         /* can't tell gnuchess what its clock should read,
10139            so we bow to its notion. */
10140         ResetClocks();
10141         timeRemaining[0][currentMove] = whiteTimeRemaining;
10142         timeRemaining[1][currentMove] = blackTimeRemaining;
10143     }
10144
10145     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10146                 appData.icsEngineAnalyze) && first.analysisSupport) {
10147       SendToProgram("analyze\n", &first);
10148       first.analyzing = TRUE;
10149     }
10150     return 1;
10151 }
10152
10153 /*
10154  * Button procedures
10155  */
10156 void
10157 Reset(redraw, init)
10158      int redraw, init;
10159 {
10160     int i;
10161
10162     if (appData.debugMode) {
10163         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10164                 redraw, init, gameMode);
10165     }
10166     CleanupTail(); // [HGM] vari: delete any stored variations
10167     pausing = pauseExamInvalid = FALSE;
10168     startedFromSetupPosition = blackPlaysFirst = FALSE;
10169     firstMove = TRUE;
10170     whiteFlag = blackFlag = FALSE;
10171     userOfferedDraw = FALSE;
10172     hintRequested = bookRequested = FALSE;
10173     first.maybeThinking = FALSE;
10174     second.maybeThinking = FALSE;
10175     first.bookSuspend = FALSE; // [HGM] book
10176     second.bookSuspend = FALSE;
10177     thinkOutput[0] = NULLCHAR;
10178     lastHint[0] = NULLCHAR;
10179     ClearGameInfo(&gameInfo);
10180     gameInfo.variant = StringToVariant(appData.variant);
10181     ics_user_moved = ics_clock_paused = FALSE;
10182     ics_getting_history = H_FALSE;
10183     ics_gamenum = -1;
10184     white_holding[0] = black_holding[0] = NULLCHAR;
10185     ClearProgramStats();
10186     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10187
10188     ResetFrontEnd();
10189     ClearHighlights();
10190     flipView = appData.flipView;
10191     ClearPremoveHighlights();
10192     gotPremove = FALSE;
10193     alarmSounded = FALSE;
10194
10195     GameEnds(EndOfFile, NULL, GE_PLAYER);
10196     if(appData.serverMovesName != NULL) {
10197         /* [HGM] prepare to make moves file for broadcasting */
10198         clock_t t = clock();
10199         if(serverMoves != NULL) fclose(serverMoves);
10200         serverMoves = fopen(appData.serverMovesName, "r");
10201         if(serverMoves != NULL) {
10202             fclose(serverMoves);
10203             /* delay 15 sec before overwriting, so all clients can see end */
10204             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10205         }
10206         serverMoves = fopen(appData.serverMovesName, "w");
10207     }
10208
10209     ExitAnalyzeMode();
10210     gameMode = BeginningOfGame;
10211     ModeHighlight();
10212     if(appData.icsActive) gameInfo.variant = VariantNormal;
10213     currentMove = forwardMostMove = backwardMostMove = 0;
10214     InitPosition(redraw);
10215     for (i = 0; i < MAX_MOVES; i++) {
10216         if (commentList[i] != NULL) {
10217             free(commentList[i]);
10218             commentList[i] = NULL;
10219         }
10220     }
10221     ResetClocks();
10222     timeRemaining[0][0] = whiteTimeRemaining;
10223     timeRemaining[1][0] = blackTimeRemaining;
10224
10225     if (first.pr == NULL) {
10226         StartChessProgram(&first);
10227     }
10228     if (init) {
10229             InitChessProgram(&first, startedFromSetupPosition);
10230     }
10231     DisplayTitle("");
10232     DisplayMessage("", "");
10233     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10234     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10235 }
10236
10237 void
10238 AutoPlayGameLoop()
10239 {
10240     for (;;) {
10241         if (!AutoPlayOneMove())
10242           return;
10243         if (matchMode || appData.timeDelay == 0)
10244           continue;
10245         if (appData.timeDelay < 0)
10246           return;
10247         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10248         break;
10249     }
10250 }
10251
10252
10253 int
10254 AutoPlayOneMove()
10255 {
10256     int fromX, fromY, toX, toY;
10257
10258     if (appData.debugMode) {
10259       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10260     }
10261
10262     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10263       return FALSE;
10264
10265     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10266       pvInfoList[currentMove].depth = programStats.depth;
10267       pvInfoList[currentMove].score = programStats.score;
10268       pvInfoList[currentMove].time  = 0;
10269       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10270     }
10271
10272     if (currentMove >= forwardMostMove) {
10273       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10274       gameMode = EditGame;
10275       ModeHighlight();
10276
10277       /* [AS] Clear current move marker at the end of a game */
10278       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10279
10280       return FALSE;
10281     }
10282
10283     toX = moveList[currentMove][2] - AAA;
10284     toY = moveList[currentMove][3] - ONE;
10285
10286     if (moveList[currentMove][1] == '@') {
10287         if (appData.highlightLastMove) {
10288             SetHighlights(-1, -1, toX, toY);
10289         }
10290     } else {
10291         fromX = moveList[currentMove][0] - AAA;
10292         fromY = moveList[currentMove][1] - ONE;
10293
10294         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10295
10296         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10297
10298         if (appData.highlightLastMove) {
10299             SetHighlights(fromX, fromY, toX, toY);
10300         }
10301     }
10302     DisplayMove(currentMove);
10303     SendMoveToProgram(currentMove++, &first);
10304     DisplayBothClocks();
10305     DrawPosition(FALSE, boards[currentMove]);
10306     // [HGM] PV info: always display, routine tests if empty
10307     DisplayComment(currentMove - 1, commentList[currentMove]);
10308     return TRUE;
10309 }
10310
10311
10312 int
10313 LoadGameOneMove(readAhead)
10314      ChessMove readAhead;
10315 {
10316     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10317     char promoChar = NULLCHAR;
10318     ChessMove moveType;
10319     char move[MSG_SIZ];
10320     char *p, *q;
10321
10322     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10323         gameMode != AnalyzeMode && gameMode != Training) {
10324         gameFileFP = NULL;
10325         return FALSE;
10326     }
10327
10328     yyboardindex = forwardMostMove;
10329     if (readAhead != EndOfFile) {
10330       moveType = readAhead;
10331     } else {
10332       if (gameFileFP == NULL)
10333           return FALSE;
10334       moveType = (ChessMove) Myylex();
10335     }
10336
10337     done = FALSE;
10338     switch (moveType) {
10339       case Comment:
10340         if (appData.debugMode)
10341           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10342         p = yy_text;
10343
10344         /* append the comment but don't display it */
10345         AppendComment(currentMove, p, FALSE);
10346         return TRUE;
10347
10348       case WhiteCapturesEnPassant:
10349       case BlackCapturesEnPassant:
10350       case WhitePromotion:
10351       case BlackPromotion:
10352       case WhiteNonPromotion:
10353       case BlackNonPromotion:
10354       case NormalMove:
10355       case WhiteKingSideCastle:
10356       case WhiteQueenSideCastle:
10357       case BlackKingSideCastle:
10358       case BlackQueenSideCastle:
10359       case WhiteKingSideCastleWild:
10360       case WhiteQueenSideCastleWild:
10361       case BlackKingSideCastleWild:
10362       case BlackQueenSideCastleWild:
10363       /* PUSH Fabien */
10364       case WhiteHSideCastleFR:
10365       case WhiteASideCastleFR:
10366       case BlackHSideCastleFR:
10367       case BlackASideCastleFR:
10368       /* POP Fabien */
10369         if (appData.debugMode)
10370           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10371         fromX = currentMoveString[0] - AAA;
10372         fromY = currentMoveString[1] - ONE;
10373         toX = currentMoveString[2] - AAA;
10374         toY = currentMoveString[3] - ONE;
10375         promoChar = currentMoveString[4];
10376         break;
10377
10378       case WhiteDrop:
10379       case BlackDrop:
10380         if (appData.debugMode)
10381           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10382         fromX = moveType == WhiteDrop ?
10383           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10384         (int) CharToPiece(ToLower(currentMoveString[0]));
10385         fromY = DROP_RANK;
10386         toX = currentMoveString[2] - AAA;
10387         toY = currentMoveString[3] - ONE;
10388         break;
10389
10390       case WhiteWins:
10391       case BlackWins:
10392       case GameIsDrawn:
10393       case GameUnfinished:
10394         if (appData.debugMode)
10395           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10396         p = strchr(yy_text, '{');
10397         if (p == NULL) p = strchr(yy_text, '(');
10398         if (p == NULL) {
10399             p = yy_text;
10400             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10401         } else {
10402             q = strchr(p, *p == '{' ? '}' : ')');
10403             if (q != NULL) *q = NULLCHAR;
10404             p++;
10405         }
10406         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10407         GameEnds(moveType, p, GE_FILE);
10408         done = TRUE;
10409         if (cmailMsgLoaded) {
10410             ClearHighlights();
10411             flipView = WhiteOnMove(currentMove);
10412             if (moveType == GameUnfinished) flipView = !flipView;
10413             if (appData.debugMode)
10414               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10415         }
10416         break;
10417
10418       case EndOfFile:
10419         if (appData.debugMode)
10420           fprintf(debugFP, "Parser hit end of file\n");
10421         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10422           case MT_NONE:
10423           case MT_CHECK:
10424             break;
10425           case MT_CHECKMATE:
10426           case MT_STAINMATE:
10427             if (WhiteOnMove(currentMove)) {
10428                 GameEnds(BlackWins, "Black mates", GE_FILE);
10429             } else {
10430                 GameEnds(WhiteWins, "White mates", GE_FILE);
10431             }
10432             break;
10433           case MT_STALEMATE:
10434             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10435             break;
10436         }
10437         done = TRUE;
10438         break;
10439
10440       case MoveNumberOne:
10441         if (lastLoadGameStart == GNUChessGame) {
10442             /* GNUChessGames have numbers, but they aren't move numbers */
10443             if (appData.debugMode)
10444               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10445                       yy_text, (int) moveType);
10446             return LoadGameOneMove(EndOfFile); /* tail recursion */
10447         }
10448         /* else fall thru */
10449
10450       case XBoardGame:
10451       case GNUChessGame:
10452       case PGNTag:
10453         /* Reached start of next game in file */
10454         if (appData.debugMode)
10455           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10456         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10457           case MT_NONE:
10458           case MT_CHECK:
10459             break;
10460           case MT_CHECKMATE:
10461           case MT_STAINMATE:
10462             if (WhiteOnMove(currentMove)) {
10463                 GameEnds(BlackWins, "Black mates", GE_FILE);
10464             } else {
10465                 GameEnds(WhiteWins, "White mates", GE_FILE);
10466             }
10467             break;
10468           case MT_STALEMATE:
10469             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10470             break;
10471         }
10472         done = TRUE;
10473         break;
10474
10475       case PositionDiagram:     /* should not happen; ignore */
10476       case ElapsedTime:         /* ignore */
10477       case NAG:                 /* ignore */
10478         if (appData.debugMode)
10479           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10480                   yy_text, (int) moveType);
10481         return LoadGameOneMove(EndOfFile); /* tail recursion */
10482
10483       case IllegalMove:
10484         if (appData.testLegality) {
10485             if (appData.debugMode)
10486               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10487             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10488                     (forwardMostMove / 2) + 1,
10489                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10490             DisplayError(move, 0);
10491             done = TRUE;
10492         } else {
10493             if (appData.debugMode)
10494               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10495                       yy_text, currentMoveString);
10496             fromX = currentMoveString[0] - AAA;
10497             fromY = currentMoveString[1] - ONE;
10498             toX = currentMoveString[2] - AAA;
10499             toY = currentMoveString[3] - ONE;
10500             promoChar = currentMoveString[4];
10501         }
10502         break;
10503
10504       case AmbiguousMove:
10505         if (appData.debugMode)
10506           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10507         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10508                 (forwardMostMove / 2) + 1,
10509                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10510         DisplayError(move, 0);
10511         done = TRUE;
10512         break;
10513
10514       default:
10515       case ImpossibleMove:
10516         if (appData.debugMode)
10517           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10518         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10519                 (forwardMostMove / 2) + 1,
10520                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10521         DisplayError(move, 0);
10522         done = TRUE;
10523         break;
10524     }
10525
10526     if (done) {
10527         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10528             DrawPosition(FALSE, boards[currentMove]);
10529             DisplayBothClocks();
10530             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10531               DisplayComment(currentMove - 1, commentList[currentMove]);
10532         }
10533         (void) StopLoadGameTimer();
10534         gameFileFP = NULL;
10535         cmailOldMove = forwardMostMove;
10536         return FALSE;
10537     } else {
10538         /* currentMoveString is set as a side-effect of yylex */
10539
10540         thinkOutput[0] = NULLCHAR;
10541         MakeMove(fromX, fromY, toX, toY, promoChar);
10542         currentMove = forwardMostMove;
10543         return TRUE;
10544     }
10545 }
10546
10547 /* Load the nth game from the given file */
10548 int
10549 LoadGameFromFile(filename, n, title, useList)
10550      char *filename;
10551      int n;
10552      char *title;
10553      /*Boolean*/ int useList;
10554 {
10555     FILE *f;
10556     char buf[MSG_SIZ];
10557
10558     if (strcmp(filename, "-") == 0) {
10559         f = stdin;
10560         title = "stdin";
10561     } else {
10562         f = fopen(filename, "rb");
10563         if (f == NULL) {
10564           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10565             DisplayError(buf, errno);
10566             return FALSE;
10567         }
10568     }
10569     if (fseek(f, 0, 0) == -1) {
10570         /* f is not seekable; probably a pipe */
10571         useList = FALSE;
10572     }
10573     if (useList && n == 0) {
10574         int error = GameListBuild(f);
10575         if (error) {
10576             DisplayError(_("Cannot build game list"), error);
10577         } else if (!ListEmpty(&gameList) &&
10578                    ((ListGame *) gameList.tailPred)->number > 1) {
10579             GameListPopUp(f, title);
10580             return TRUE;
10581         }
10582         GameListDestroy();
10583         n = 1;
10584     }
10585     if (n == 0) n = 1;
10586     return LoadGame(f, n, title, FALSE);
10587 }
10588
10589
10590 void
10591 MakeRegisteredMove()
10592 {
10593     int fromX, fromY, toX, toY;
10594     char promoChar;
10595     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10596         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10597           case CMAIL_MOVE:
10598           case CMAIL_DRAW:
10599             if (appData.debugMode)
10600               fprintf(debugFP, "Restoring %s for game %d\n",
10601                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10602
10603             thinkOutput[0] = NULLCHAR;
10604             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10605             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10606             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10607             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10608             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10609             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10610             MakeMove(fromX, fromY, toX, toY, promoChar);
10611             ShowMove(fromX, fromY, toX, toY);
10612
10613             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10614               case MT_NONE:
10615               case MT_CHECK:
10616                 break;
10617
10618               case MT_CHECKMATE:
10619               case MT_STAINMATE:
10620                 if (WhiteOnMove(currentMove)) {
10621                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10622                 } else {
10623                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10624                 }
10625                 break;
10626
10627               case MT_STALEMATE:
10628                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10629                 break;
10630             }
10631
10632             break;
10633
10634           case CMAIL_RESIGN:
10635             if (WhiteOnMove(currentMove)) {
10636                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10637             } else {
10638                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10639             }
10640             break;
10641
10642           case CMAIL_ACCEPT:
10643             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10644             break;
10645
10646           default:
10647             break;
10648         }
10649     }
10650
10651     return;
10652 }
10653
10654 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10655 int
10656 CmailLoadGame(f, gameNumber, title, useList)
10657      FILE *f;
10658      int gameNumber;
10659      char *title;
10660      int useList;
10661 {
10662     int retVal;
10663
10664     if (gameNumber > nCmailGames) {
10665         DisplayError(_("No more games in this message"), 0);
10666         return FALSE;
10667     }
10668     if (f == lastLoadGameFP) {
10669         int offset = gameNumber - lastLoadGameNumber;
10670         if (offset == 0) {
10671             cmailMsg[0] = NULLCHAR;
10672             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10673                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10674                 nCmailMovesRegistered--;
10675             }
10676             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10677             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10678                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10679             }
10680         } else {
10681             if (! RegisterMove()) return FALSE;
10682         }
10683     }
10684
10685     retVal = LoadGame(f, gameNumber, title, useList);
10686
10687     /* Make move registered during previous look at this game, if any */
10688     MakeRegisteredMove();
10689
10690     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10691         commentList[currentMove]
10692           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10693         DisplayComment(currentMove - 1, commentList[currentMove]);
10694     }
10695
10696     return retVal;
10697 }
10698
10699 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10700 int
10701 ReloadGame(offset)
10702      int offset;
10703 {
10704     int gameNumber = lastLoadGameNumber + offset;
10705     if (lastLoadGameFP == NULL) {
10706         DisplayError(_("No game has been loaded yet"), 0);
10707         return FALSE;
10708     }
10709     if (gameNumber <= 0) {
10710         DisplayError(_("Can't back up any further"), 0);
10711         return FALSE;
10712     }
10713     if (cmailMsgLoaded) {
10714         return CmailLoadGame(lastLoadGameFP, gameNumber,
10715                              lastLoadGameTitle, lastLoadGameUseList);
10716     } else {
10717         return LoadGame(lastLoadGameFP, gameNumber,
10718                         lastLoadGameTitle, lastLoadGameUseList);
10719     }
10720 }
10721
10722
10723
10724 /* Load the nth game from open file f */
10725 int
10726 LoadGame(f, gameNumber, title, useList)
10727      FILE *f;
10728      int gameNumber;
10729      char *title;
10730      int useList;
10731 {
10732     ChessMove cm;
10733     char buf[MSG_SIZ];
10734     int gn = gameNumber;
10735     ListGame *lg = NULL;
10736     int numPGNTags = 0;
10737     int err;
10738     GameMode oldGameMode;
10739     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10740
10741     if (appData.debugMode)
10742         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10743
10744     if (gameMode == Training )
10745         SetTrainingModeOff();
10746
10747     oldGameMode = gameMode;
10748     if (gameMode != BeginningOfGame) {
10749       Reset(FALSE, TRUE);
10750     }
10751
10752     gameFileFP = f;
10753     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10754         fclose(lastLoadGameFP);
10755     }
10756
10757     if (useList) {
10758         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10759
10760         if (lg) {
10761             fseek(f, lg->offset, 0);
10762             GameListHighlight(gameNumber);
10763             gn = 1;
10764         }
10765         else {
10766             DisplayError(_("Game number out of range"), 0);
10767             return FALSE;
10768         }
10769     } else {
10770         GameListDestroy();
10771         if (fseek(f, 0, 0) == -1) {
10772             if (f == lastLoadGameFP ?
10773                 gameNumber == lastLoadGameNumber + 1 :
10774                 gameNumber == 1) {
10775                 gn = 1;
10776             } else {
10777                 DisplayError(_("Can't seek on game file"), 0);
10778                 return FALSE;
10779             }
10780         }
10781     }
10782     lastLoadGameFP = f;
10783     lastLoadGameNumber = gameNumber;
10784     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10785     lastLoadGameUseList = useList;
10786
10787     yynewfile(f);
10788
10789     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10790       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10791                 lg->gameInfo.black);
10792             DisplayTitle(buf);
10793     } else if (*title != NULLCHAR) {
10794         if (gameNumber > 1) {
10795           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10796             DisplayTitle(buf);
10797         } else {
10798             DisplayTitle(title);
10799         }
10800     }
10801
10802     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10803         gameMode = PlayFromGameFile;
10804         ModeHighlight();
10805     }
10806
10807     currentMove = forwardMostMove = backwardMostMove = 0;
10808     CopyBoard(boards[0], initialPosition);
10809     StopClocks();
10810
10811     /*
10812      * Skip the first gn-1 games in the file.
10813      * Also skip over anything that precedes an identifiable
10814      * start of game marker, to avoid being confused by
10815      * garbage at the start of the file.  Currently
10816      * recognized start of game markers are the move number "1",
10817      * the pattern "gnuchess .* game", the pattern
10818      * "^[#;%] [^ ]* game file", and a PGN tag block.
10819      * A game that starts with one of the latter two patterns
10820      * will also have a move number 1, possibly
10821      * following a position diagram.
10822      * 5-4-02: Let's try being more lenient and allowing a game to
10823      * start with an unnumbered move.  Does that break anything?
10824      */
10825     cm = lastLoadGameStart = EndOfFile;
10826     while (gn > 0) {
10827         yyboardindex = forwardMostMove;
10828         cm = (ChessMove) Myylex();
10829         switch (cm) {
10830           case EndOfFile:
10831             if (cmailMsgLoaded) {
10832                 nCmailGames = CMAIL_MAX_GAMES - gn;
10833             } else {
10834                 Reset(TRUE, TRUE);
10835                 DisplayError(_("Game not found in file"), 0);
10836             }
10837             return FALSE;
10838
10839           case GNUChessGame:
10840           case XBoardGame:
10841             gn--;
10842             lastLoadGameStart = cm;
10843             break;
10844
10845           case MoveNumberOne:
10846             switch (lastLoadGameStart) {
10847               case GNUChessGame:
10848               case XBoardGame:
10849               case PGNTag:
10850                 break;
10851               case MoveNumberOne:
10852               case EndOfFile:
10853                 gn--;           /* count this game */
10854                 lastLoadGameStart = cm;
10855                 break;
10856               default:
10857                 /* impossible */
10858                 break;
10859             }
10860             break;
10861
10862           case PGNTag:
10863             switch (lastLoadGameStart) {
10864               case GNUChessGame:
10865               case PGNTag:
10866               case MoveNumberOne:
10867               case EndOfFile:
10868                 gn--;           /* count this game */
10869                 lastLoadGameStart = cm;
10870                 break;
10871               case XBoardGame:
10872                 lastLoadGameStart = cm; /* game counted already */
10873                 break;
10874               default:
10875                 /* impossible */
10876                 break;
10877             }
10878             if (gn > 0) {
10879                 do {
10880                     yyboardindex = forwardMostMove;
10881                     cm = (ChessMove) Myylex();
10882                 } while (cm == PGNTag || cm == Comment);
10883             }
10884             break;
10885
10886           case WhiteWins:
10887           case BlackWins:
10888           case GameIsDrawn:
10889             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10890                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10891                     != CMAIL_OLD_RESULT) {
10892                     nCmailResults ++ ;
10893                     cmailResult[  CMAIL_MAX_GAMES
10894                                 - gn - 1] = CMAIL_OLD_RESULT;
10895                 }
10896             }
10897             break;
10898
10899           case NormalMove:
10900             /* Only a NormalMove can be at the start of a game
10901              * without a position diagram. */
10902             if (lastLoadGameStart == EndOfFile ) {
10903               gn--;
10904               lastLoadGameStart = MoveNumberOne;
10905             }
10906             break;
10907
10908           default:
10909             break;
10910         }
10911     }
10912
10913     if (appData.debugMode)
10914       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10915
10916     if (cm == XBoardGame) {
10917         /* Skip any header junk before position diagram and/or move 1 */
10918         for (;;) {
10919             yyboardindex = forwardMostMove;
10920             cm = (ChessMove) Myylex();
10921
10922             if (cm == EndOfFile ||
10923                 cm == GNUChessGame || cm == XBoardGame) {
10924                 /* Empty game; pretend end-of-file and handle later */
10925                 cm = EndOfFile;
10926                 break;
10927             }
10928
10929             if (cm == MoveNumberOne || cm == PositionDiagram ||
10930                 cm == PGNTag || cm == Comment)
10931               break;
10932         }
10933     } else if (cm == GNUChessGame) {
10934         if (gameInfo.event != NULL) {
10935             free(gameInfo.event);
10936         }
10937         gameInfo.event = StrSave(yy_text);
10938     }
10939
10940     startedFromSetupPosition = FALSE;
10941     while (cm == PGNTag) {
10942         if (appData.debugMode)
10943           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10944         err = ParsePGNTag(yy_text, &gameInfo);
10945         if (!err) numPGNTags++;
10946
10947         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10948         if(gameInfo.variant != oldVariant) {
10949             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10950             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10951             InitPosition(TRUE);
10952             oldVariant = gameInfo.variant;
10953             if (appData.debugMode)
10954               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10955         }
10956
10957
10958         if (gameInfo.fen != NULL) {
10959           Board initial_position;
10960           startedFromSetupPosition = TRUE;
10961           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10962             Reset(TRUE, TRUE);
10963             DisplayError(_("Bad FEN position in file"), 0);
10964             return FALSE;
10965           }
10966           CopyBoard(boards[0], initial_position);
10967           if (blackPlaysFirst) {
10968             currentMove = forwardMostMove = backwardMostMove = 1;
10969             CopyBoard(boards[1], initial_position);
10970             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10971             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10972             timeRemaining[0][1] = whiteTimeRemaining;
10973             timeRemaining[1][1] = blackTimeRemaining;
10974             if (commentList[0] != NULL) {
10975               commentList[1] = commentList[0];
10976               commentList[0] = NULL;
10977             }
10978           } else {
10979             currentMove = forwardMostMove = backwardMostMove = 0;
10980           }
10981           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10982           {   int i;
10983               initialRulePlies = FENrulePlies;
10984               for( i=0; i< nrCastlingRights; i++ )
10985                   initialRights[i] = initial_position[CASTLING][i];
10986           }
10987           yyboardindex = forwardMostMove;
10988           free(gameInfo.fen);
10989           gameInfo.fen = NULL;
10990         }
10991
10992         yyboardindex = forwardMostMove;
10993         cm = (ChessMove) Myylex();
10994
10995         /* Handle comments interspersed among the tags */
10996         while (cm == Comment) {
10997             char *p;
10998             if (appData.debugMode)
10999               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11000             p = yy_text;
11001             AppendComment(currentMove, p, FALSE);
11002             yyboardindex = forwardMostMove;
11003             cm = (ChessMove) Myylex();
11004         }
11005     }
11006
11007     /* don't rely on existence of Event tag since if game was
11008      * pasted from clipboard the Event tag may not exist
11009      */
11010     if (numPGNTags > 0){
11011         char *tags;
11012         if (gameInfo.variant == VariantNormal) {
11013           VariantClass v = StringToVariant(gameInfo.event);
11014           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11015           if(v < VariantShogi) gameInfo.variant = v;
11016         }
11017         if (!matchMode) {
11018           if( appData.autoDisplayTags ) {
11019             tags = PGNTags(&gameInfo);
11020             TagsPopUp(tags, CmailMsg());
11021             free(tags);
11022           }
11023         }
11024     } else {
11025         /* Make something up, but don't display it now */
11026         SetGameInfo();
11027         TagsPopDown();
11028     }
11029
11030     if (cm == PositionDiagram) {
11031         int i, j;
11032         char *p;
11033         Board initial_position;
11034
11035         if (appData.debugMode)
11036           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11037
11038         if (!startedFromSetupPosition) {
11039             p = yy_text;
11040             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11041               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11042                 switch (*p) {
11043                   case '{':
11044                   case '[':
11045                   case '-':
11046                   case ' ':
11047                   case '\t':
11048                   case '\n':
11049                   case '\r':
11050                     break;
11051                   default:
11052                     initial_position[i][j++] = CharToPiece(*p);
11053                     break;
11054                 }
11055             while (*p == ' ' || *p == '\t' ||
11056                    *p == '\n' || *p == '\r') p++;
11057
11058             if (strncmp(p, "black", strlen("black"))==0)
11059               blackPlaysFirst = TRUE;
11060             else
11061               blackPlaysFirst = FALSE;
11062             startedFromSetupPosition = TRUE;
11063
11064             CopyBoard(boards[0], initial_position);
11065             if (blackPlaysFirst) {
11066                 currentMove = forwardMostMove = backwardMostMove = 1;
11067                 CopyBoard(boards[1], initial_position);
11068                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11069                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11070                 timeRemaining[0][1] = whiteTimeRemaining;
11071                 timeRemaining[1][1] = blackTimeRemaining;
11072                 if (commentList[0] != NULL) {
11073                     commentList[1] = commentList[0];
11074                     commentList[0] = NULL;
11075                 }
11076             } else {
11077                 currentMove = forwardMostMove = backwardMostMove = 0;
11078             }
11079         }
11080         yyboardindex = forwardMostMove;
11081         cm = (ChessMove) Myylex();
11082     }
11083
11084     if (first.pr == NoProc) {
11085         StartChessProgram(&first);
11086     }
11087     InitChessProgram(&first, FALSE);
11088     SendToProgram("force\n", &first);
11089     if (startedFromSetupPosition) {
11090         SendBoard(&first, forwardMostMove);
11091     if (appData.debugMode) {
11092         fprintf(debugFP, "Load Game\n");
11093     }
11094         DisplayBothClocks();
11095     }
11096
11097     /* [HGM] server: flag to write setup moves in broadcast file as one */
11098     loadFlag = appData.suppressLoadMoves;
11099
11100     while (cm == Comment) {
11101         char *p;
11102         if (appData.debugMode)
11103           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11104         p = yy_text;
11105         AppendComment(currentMove, p, FALSE);
11106         yyboardindex = forwardMostMove;
11107         cm = (ChessMove) Myylex();
11108     }
11109
11110     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11111         cm == WhiteWins || cm == BlackWins ||
11112         cm == GameIsDrawn || cm == GameUnfinished) {
11113         DisplayMessage("", _("No moves in game"));
11114         if (cmailMsgLoaded) {
11115             if (appData.debugMode)
11116               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11117             ClearHighlights();
11118             flipView = FALSE;
11119         }
11120         DrawPosition(FALSE, boards[currentMove]);
11121         DisplayBothClocks();
11122         gameMode = EditGame;
11123         ModeHighlight();
11124         gameFileFP = NULL;
11125         cmailOldMove = 0;
11126         return TRUE;
11127     }
11128
11129     // [HGM] PV info: routine tests if comment empty
11130     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11131         DisplayComment(currentMove - 1, commentList[currentMove]);
11132     }
11133     if (!matchMode && appData.timeDelay != 0)
11134       DrawPosition(FALSE, boards[currentMove]);
11135
11136     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11137       programStats.ok_to_send = 1;
11138     }
11139
11140     /* if the first token after the PGN tags is a move
11141      * and not move number 1, retrieve it from the parser
11142      */
11143     if (cm != MoveNumberOne)
11144         LoadGameOneMove(cm);
11145
11146     /* load the remaining moves from the file */
11147     while (LoadGameOneMove(EndOfFile)) {
11148       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11149       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11150     }
11151
11152     /* rewind to the start of the game */
11153     currentMove = backwardMostMove;
11154
11155     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11156
11157     if (oldGameMode == AnalyzeFile ||
11158         oldGameMode == AnalyzeMode) {
11159       AnalyzeFileEvent();
11160     }
11161
11162     if (matchMode || appData.timeDelay == 0) {
11163       ToEndEvent();
11164       gameMode = EditGame;
11165       ModeHighlight();
11166     } else if (appData.timeDelay > 0) {
11167       AutoPlayGameLoop();
11168     }
11169
11170     if (appData.debugMode)
11171         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11172
11173     loadFlag = 0; /* [HGM] true game starts */
11174     return TRUE;
11175 }
11176
11177 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11178 int
11179 ReloadPosition(offset)
11180      int offset;
11181 {
11182     int positionNumber = lastLoadPositionNumber + offset;
11183     if (lastLoadPositionFP == NULL) {
11184         DisplayError(_("No position has been loaded yet"), 0);
11185         return FALSE;
11186     }
11187     if (positionNumber <= 0) {
11188         DisplayError(_("Can't back up any further"), 0);
11189         return FALSE;
11190     }
11191     return LoadPosition(lastLoadPositionFP, positionNumber,
11192                         lastLoadPositionTitle);
11193 }
11194
11195 /* Load the nth position from the given file */
11196 int
11197 LoadPositionFromFile(filename, n, title)
11198      char *filename;
11199      int n;
11200      char *title;
11201 {
11202     FILE *f;
11203     char buf[MSG_SIZ];
11204
11205     if (strcmp(filename, "-") == 0) {
11206         return LoadPosition(stdin, n, "stdin");
11207     } else {
11208         f = fopen(filename, "rb");
11209         if (f == NULL) {
11210             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11211             DisplayError(buf, errno);
11212             return FALSE;
11213         } else {
11214             return LoadPosition(f, n, title);
11215         }
11216     }
11217 }
11218
11219 /* Load the nth position from the given open file, and close it */
11220 int
11221 LoadPosition(f, positionNumber, title)
11222      FILE *f;
11223      int positionNumber;
11224      char *title;
11225 {
11226     char *p, line[MSG_SIZ];
11227     Board initial_position;
11228     int i, j, fenMode, pn;
11229
11230     if (gameMode == Training )
11231         SetTrainingModeOff();
11232
11233     if (gameMode != BeginningOfGame) {
11234         Reset(FALSE, TRUE);
11235     }
11236     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11237         fclose(lastLoadPositionFP);
11238     }
11239     if (positionNumber == 0) positionNumber = 1;
11240     lastLoadPositionFP = f;
11241     lastLoadPositionNumber = positionNumber;
11242     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11243     if (first.pr == NoProc) {
11244       StartChessProgram(&first);
11245       InitChessProgram(&first, FALSE);
11246     }
11247     pn = positionNumber;
11248     if (positionNumber < 0) {
11249         /* Negative position number means to seek to that byte offset */
11250         if (fseek(f, -positionNumber, 0) == -1) {
11251             DisplayError(_("Can't seek on position file"), 0);
11252             return FALSE;
11253         };
11254         pn = 1;
11255     } else {
11256         if (fseek(f, 0, 0) == -1) {
11257             if (f == lastLoadPositionFP ?
11258                 positionNumber == lastLoadPositionNumber + 1 :
11259                 positionNumber == 1) {
11260                 pn = 1;
11261             } else {
11262                 DisplayError(_("Can't seek on position file"), 0);
11263                 return FALSE;
11264             }
11265         }
11266     }
11267     /* See if this file is FEN or old-style xboard */
11268     if (fgets(line, MSG_SIZ, f) == NULL) {
11269         DisplayError(_("Position not found in file"), 0);
11270         return FALSE;
11271     }
11272     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11273     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11274
11275     if (pn >= 2) {
11276         if (fenMode || line[0] == '#') pn--;
11277         while (pn > 0) {
11278             /* skip positions before number pn */
11279             if (fgets(line, MSG_SIZ, f) == NULL) {
11280                 Reset(TRUE, TRUE);
11281                 DisplayError(_("Position not found in file"), 0);
11282                 return FALSE;
11283             }
11284             if (fenMode || line[0] == '#') pn--;
11285         }
11286     }
11287
11288     if (fenMode) {
11289         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11290             DisplayError(_("Bad FEN position in file"), 0);
11291             return FALSE;
11292         }
11293     } else {
11294         (void) fgets(line, MSG_SIZ, f);
11295         (void) fgets(line, MSG_SIZ, f);
11296
11297         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11298             (void) fgets(line, MSG_SIZ, f);
11299             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11300                 if (*p == ' ')
11301                   continue;
11302                 initial_position[i][j++] = CharToPiece(*p);
11303             }
11304         }
11305
11306         blackPlaysFirst = FALSE;
11307         if (!feof(f)) {
11308             (void) fgets(line, MSG_SIZ, f);
11309             if (strncmp(line, "black", strlen("black"))==0)
11310               blackPlaysFirst = TRUE;
11311         }
11312     }
11313     startedFromSetupPosition = TRUE;
11314
11315     SendToProgram("force\n", &first);
11316     CopyBoard(boards[0], initial_position);
11317     if (blackPlaysFirst) {
11318         currentMove = forwardMostMove = backwardMostMove = 1;
11319         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11320         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11321         CopyBoard(boards[1], initial_position);
11322         DisplayMessage("", _("Black to play"));
11323     } else {
11324         currentMove = forwardMostMove = backwardMostMove = 0;
11325         DisplayMessage("", _("White to play"));
11326     }
11327     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11328     SendBoard(&first, forwardMostMove);
11329     if (appData.debugMode) {
11330 int i, j;
11331   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11332   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11333         fprintf(debugFP, "Load Position\n");
11334     }
11335
11336     if (positionNumber > 1) {
11337       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11338         DisplayTitle(line);
11339     } else {
11340         DisplayTitle(title);
11341     }
11342     gameMode = EditGame;
11343     ModeHighlight();
11344     ResetClocks();
11345     timeRemaining[0][1] = whiteTimeRemaining;
11346     timeRemaining[1][1] = blackTimeRemaining;
11347     DrawPosition(FALSE, boards[currentMove]);
11348
11349     return TRUE;
11350 }
11351
11352
11353 void
11354 CopyPlayerNameIntoFileName(dest, src)
11355      char **dest, *src;
11356 {
11357     while (*src != NULLCHAR && *src != ',') {
11358         if (*src == ' ') {
11359             *(*dest)++ = '_';
11360             src++;
11361         } else {
11362             *(*dest)++ = *src++;
11363         }
11364     }
11365 }
11366
11367 char *DefaultFileName(ext)
11368      char *ext;
11369 {
11370     static char def[MSG_SIZ];
11371     char *p;
11372
11373     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11374         p = def;
11375         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11376         *p++ = '-';
11377         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11378         *p++ = '.';
11379         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11380     } else {
11381         def[0] = NULLCHAR;
11382     }
11383     return def;
11384 }
11385
11386 /* Save the current game to the given file */
11387 int
11388 SaveGameToFile(filename, append)
11389      char *filename;
11390      int append;
11391 {
11392     FILE *f;
11393     char buf[MSG_SIZ];
11394     int result;
11395
11396     if (strcmp(filename, "-") == 0) {
11397         return SaveGame(stdout, 0, NULL);
11398     } else {
11399         f = fopen(filename, append ? "a" : "w");
11400         if (f == NULL) {
11401             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11402             DisplayError(buf, errno);
11403             return FALSE;
11404         } else {
11405             safeStrCpy(buf, lastMsg, MSG_SIZ);
11406             DisplayMessage(_("Waiting for access to save file"), "");
11407             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11408             DisplayMessage(_("Saving game"), "");
11409             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11410             result = SaveGame(f, 0, NULL);
11411             DisplayMessage(buf, "");
11412             return result;
11413         }
11414     }
11415 }
11416
11417 char *
11418 SavePart(str)
11419      char *str;
11420 {
11421     static char buf[MSG_SIZ];
11422     char *p;
11423
11424     p = strchr(str, ' ');
11425     if (p == NULL) return str;
11426     strncpy(buf, str, p - str);
11427     buf[p - str] = NULLCHAR;
11428     return buf;
11429 }
11430
11431 #define PGN_MAX_LINE 75
11432
11433 #define PGN_SIDE_WHITE  0
11434 #define PGN_SIDE_BLACK  1
11435
11436 /* [AS] */
11437 static int FindFirstMoveOutOfBook( int side )
11438 {
11439     int result = -1;
11440
11441     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11442         int index = backwardMostMove;
11443         int has_book_hit = 0;
11444
11445         if( (index % 2) != side ) {
11446             index++;
11447         }
11448
11449         while( index < forwardMostMove ) {
11450             /* Check to see if engine is in book */
11451             int depth = pvInfoList[index].depth;
11452             int score = pvInfoList[index].score;
11453             int in_book = 0;
11454
11455             if( depth <= 2 ) {
11456                 in_book = 1;
11457             }
11458             else if( score == 0 && depth == 63 ) {
11459                 in_book = 1; /* Zappa */
11460             }
11461             else if( score == 2 && depth == 99 ) {
11462                 in_book = 1; /* Abrok */
11463             }
11464
11465             has_book_hit += in_book;
11466
11467             if( ! in_book ) {
11468                 result = index;
11469
11470                 break;
11471             }
11472
11473             index += 2;
11474         }
11475     }
11476
11477     return result;
11478 }
11479
11480 /* [AS] */
11481 void GetOutOfBookInfo( char * buf )
11482 {
11483     int oob[2];
11484     int i;
11485     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11486
11487     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11488     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11489
11490     *buf = '\0';
11491
11492     if( oob[0] >= 0 || oob[1] >= 0 ) {
11493         for( i=0; i<2; i++ ) {
11494             int idx = oob[i];
11495
11496             if( idx >= 0 ) {
11497                 if( i > 0 && oob[0] >= 0 ) {
11498                     strcat( buf, "   " );
11499                 }
11500
11501                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11502                 sprintf( buf+strlen(buf), "%s%.2f",
11503                     pvInfoList[idx].score >= 0 ? "+" : "",
11504                     pvInfoList[idx].score / 100.0 );
11505             }
11506         }
11507     }
11508 }
11509
11510 /* Save game in PGN style and close the file */
11511 int
11512 SaveGamePGN(f)
11513      FILE *f;
11514 {
11515     int i, offset, linelen, newblock;
11516     time_t tm;
11517 //    char *movetext;
11518     char numtext[32];
11519     int movelen, numlen, blank;
11520     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11521
11522     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11523
11524     tm = time((time_t *) NULL);
11525
11526     PrintPGNTags(f, &gameInfo);
11527
11528     if (backwardMostMove > 0 || startedFromSetupPosition) {
11529         char *fen = PositionToFEN(backwardMostMove, NULL);
11530         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11531         fprintf(f, "\n{--------------\n");
11532         PrintPosition(f, backwardMostMove);
11533         fprintf(f, "--------------}\n");
11534         free(fen);
11535     }
11536     else {
11537         /* [AS] Out of book annotation */
11538         if( appData.saveOutOfBookInfo ) {
11539             char buf[64];
11540
11541             GetOutOfBookInfo( buf );
11542
11543             if( buf[0] != '\0' ) {
11544                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11545             }
11546         }
11547
11548         fprintf(f, "\n");
11549     }
11550
11551     i = backwardMostMove;
11552     linelen = 0;
11553     newblock = TRUE;
11554
11555     while (i < forwardMostMove) {
11556         /* Print comments preceding this move */
11557         if (commentList[i] != NULL) {
11558             if (linelen > 0) fprintf(f, "\n");
11559             fprintf(f, "%s", commentList[i]);
11560             linelen = 0;
11561             newblock = TRUE;
11562         }
11563
11564         /* Format move number */
11565         if ((i % 2) == 0)
11566           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11567         else
11568           if (newblock)
11569             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11570           else
11571             numtext[0] = NULLCHAR;
11572
11573         numlen = strlen(numtext);
11574         newblock = FALSE;
11575
11576         /* Print move number */
11577         blank = linelen > 0 && numlen > 0;
11578         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11579             fprintf(f, "\n");
11580             linelen = 0;
11581             blank = 0;
11582         }
11583         if (blank) {
11584             fprintf(f, " ");
11585             linelen++;
11586         }
11587         fprintf(f, "%s", numtext);
11588         linelen += numlen;
11589
11590         /* Get move */
11591         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11592         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11593
11594         /* Print move */
11595         blank = linelen > 0 && movelen > 0;
11596         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11597             fprintf(f, "\n");
11598             linelen = 0;
11599             blank = 0;
11600         }
11601         if (blank) {
11602             fprintf(f, " ");
11603             linelen++;
11604         }
11605         fprintf(f, "%s", move_buffer);
11606         linelen += movelen;
11607
11608         /* [AS] Add PV info if present */
11609         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11610             /* [HGM] add time */
11611             char buf[MSG_SIZ]; int seconds;
11612
11613             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11614
11615             if( seconds <= 0)
11616               buf[0] = 0;
11617             else
11618               if( seconds < 30 )
11619                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11620               else
11621                 {
11622                   seconds = (seconds + 4)/10; // round to full seconds
11623                   if( seconds < 60 )
11624                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11625                   else
11626                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11627                 }
11628
11629             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11630                       pvInfoList[i].score >= 0 ? "+" : "",
11631                       pvInfoList[i].score / 100.0,
11632                       pvInfoList[i].depth,
11633                       buf );
11634
11635             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11636
11637             /* Print score/depth */
11638             blank = linelen > 0 && movelen > 0;
11639             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11640                 fprintf(f, "\n");
11641                 linelen = 0;
11642                 blank = 0;
11643             }
11644             if (blank) {
11645                 fprintf(f, " ");
11646                 linelen++;
11647             }
11648             fprintf(f, "%s", move_buffer);
11649             linelen += movelen;
11650         }
11651
11652         i++;
11653     }
11654
11655     /* Start a new line */
11656     if (linelen > 0) fprintf(f, "\n");
11657
11658     /* Print comments after last move */
11659     if (commentList[i] != NULL) {
11660         fprintf(f, "%s\n", commentList[i]);
11661     }
11662
11663     /* Print result */
11664     if (gameInfo.resultDetails != NULL &&
11665         gameInfo.resultDetails[0] != NULLCHAR) {
11666         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11667                 PGNResult(gameInfo.result));
11668     } else {
11669         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11670     }
11671
11672     fclose(f);
11673     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11674     return TRUE;
11675 }
11676
11677 /* Save game in old style and close the file */
11678 int
11679 SaveGameOldStyle(f)
11680      FILE *f;
11681 {
11682     int i, offset;
11683     time_t tm;
11684
11685     tm = time((time_t *) NULL);
11686
11687     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11688     PrintOpponents(f);
11689
11690     if (backwardMostMove > 0 || startedFromSetupPosition) {
11691         fprintf(f, "\n[--------------\n");
11692         PrintPosition(f, backwardMostMove);
11693         fprintf(f, "--------------]\n");
11694     } else {
11695         fprintf(f, "\n");
11696     }
11697
11698     i = backwardMostMove;
11699     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11700
11701     while (i < forwardMostMove) {
11702         if (commentList[i] != NULL) {
11703             fprintf(f, "[%s]\n", commentList[i]);
11704         }
11705
11706         if ((i % 2) == 1) {
11707             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11708             i++;
11709         } else {
11710             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11711             i++;
11712             if (commentList[i] != NULL) {
11713                 fprintf(f, "\n");
11714                 continue;
11715             }
11716             if (i >= forwardMostMove) {
11717                 fprintf(f, "\n");
11718                 break;
11719             }
11720             fprintf(f, "%s\n", parseList[i]);
11721             i++;
11722         }
11723     }
11724
11725     if (commentList[i] != NULL) {
11726         fprintf(f, "[%s]\n", commentList[i]);
11727     }
11728
11729     /* This isn't really the old style, but it's close enough */
11730     if (gameInfo.resultDetails != NULL &&
11731         gameInfo.resultDetails[0] != NULLCHAR) {
11732         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11733                 gameInfo.resultDetails);
11734     } else {
11735         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11736     }
11737
11738     fclose(f);
11739     return TRUE;
11740 }
11741
11742 /* Save the current game to open file f and close the file */
11743 int
11744 SaveGame(f, dummy, dummy2)
11745      FILE *f;
11746      int dummy;
11747      char *dummy2;
11748 {
11749     if (gameMode == EditPosition) EditPositionDone(TRUE);
11750     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11751     if (appData.oldSaveStyle)
11752       return SaveGameOldStyle(f);
11753     else
11754       return SaveGamePGN(f);
11755 }
11756
11757 /* Save the current position to the given file */
11758 int
11759 SavePositionToFile(filename)
11760      char *filename;
11761 {
11762     FILE *f;
11763     char buf[MSG_SIZ];
11764
11765     if (strcmp(filename, "-") == 0) {
11766         return SavePosition(stdout, 0, NULL);
11767     } else {
11768         f = fopen(filename, "a");
11769         if (f == NULL) {
11770             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11771             DisplayError(buf, errno);
11772             return FALSE;
11773         } else {
11774             safeStrCpy(buf, lastMsg, MSG_SIZ);
11775             DisplayMessage(_("Waiting for access to save file"), "");
11776             flock(fileno(f), LOCK_EX); // [HGM] lock
11777             DisplayMessage(_("Saving position"), "");
11778             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11779             SavePosition(f, 0, NULL);
11780             DisplayMessage(buf, "");
11781             return TRUE;
11782         }
11783     }
11784 }
11785
11786 /* Save the current position to the given open file and close the file */
11787 int
11788 SavePosition(f, dummy, dummy2)
11789      FILE *f;
11790      int dummy;
11791      char *dummy2;
11792 {
11793     time_t tm;
11794     char *fen;
11795
11796     if (gameMode == EditPosition) EditPositionDone(TRUE);
11797     if (appData.oldSaveStyle) {
11798         tm = time((time_t *) NULL);
11799
11800         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11801         PrintOpponents(f);
11802         fprintf(f, "[--------------\n");
11803         PrintPosition(f, currentMove);
11804         fprintf(f, "--------------]\n");
11805     } else {
11806         fen = PositionToFEN(currentMove, NULL);
11807         fprintf(f, "%s\n", fen);
11808         free(fen);
11809     }
11810     fclose(f);
11811     return TRUE;
11812 }
11813
11814 void
11815 ReloadCmailMsgEvent(unregister)
11816      int unregister;
11817 {
11818 #if !WIN32
11819     static char *inFilename = NULL;
11820     static char *outFilename;
11821     int i;
11822     struct stat inbuf, outbuf;
11823     int status;
11824
11825     /* Any registered moves are unregistered if unregister is set, */
11826     /* i.e. invoked by the signal handler */
11827     if (unregister) {
11828         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11829             cmailMoveRegistered[i] = FALSE;
11830             if (cmailCommentList[i] != NULL) {
11831                 free(cmailCommentList[i]);
11832                 cmailCommentList[i] = NULL;
11833             }
11834         }
11835         nCmailMovesRegistered = 0;
11836     }
11837
11838     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11839         cmailResult[i] = CMAIL_NOT_RESULT;
11840     }
11841     nCmailResults = 0;
11842
11843     if (inFilename == NULL) {
11844         /* Because the filenames are static they only get malloced once  */
11845         /* and they never get freed                                      */
11846         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11847         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11848
11849         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11850         sprintf(outFilename, "%s.out", appData.cmailGameName);
11851     }
11852
11853     status = stat(outFilename, &outbuf);
11854     if (status < 0) {
11855         cmailMailedMove = FALSE;
11856     } else {
11857         status = stat(inFilename, &inbuf);
11858         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11859     }
11860
11861     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11862        counts the games, notes how each one terminated, etc.
11863
11864        It would be nice to remove this kludge and instead gather all
11865        the information while building the game list.  (And to keep it
11866        in the game list nodes instead of having a bunch of fixed-size
11867        parallel arrays.)  Note this will require getting each game's
11868        termination from the PGN tags, as the game list builder does
11869        not process the game moves.  --mann
11870        */
11871     cmailMsgLoaded = TRUE;
11872     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11873
11874     /* Load first game in the file or popup game menu */
11875     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11876
11877 #endif /* !WIN32 */
11878     return;
11879 }
11880
11881 int
11882 RegisterMove()
11883 {
11884     FILE *f;
11885     char string[MSG_SIZ];
11886
11887     if (   cmailMailedMove
11888         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11889         return TRUE;            /* Allow free viewing  */
11890     }
11891
11892     /* Unregister move to ensure that we don't leave RegisterMove        */
11893     /* with the move registered when the conditions for registering no   */
11894     /* longer hold                                                       */
11895     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11896         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11897         nCmailMovesRegistered --;
11898
11899         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11900           {
11901               free(cmailCommentList[lastLoadGameNumber - 1]);
11902               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11903           }
11904     }
11905
11906     if (cmailOldMove == -1) {
11907         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11908         return FALSE;
11909     }
11910
11911     if (currentMove > cmailOldMove + 1) {
11912         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11913         return FALSE;
11914     }
11915
11916     if (currentMove < cmailOldMove) {
11917         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11918         return FALSE;
11919     }
11920
11921     if (forwardMostMove > currentMove) {
11922         /* Silently truncate extra moves */
11923         TruncateGame();
11924     }
11925
11926     if (   (currentMove == cmailOldMove + 1)
11927         || (   (currentMove == cmailOldMove)
11928             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11929                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11930         if (gameInfo.result != GameUnfinished) {
11931             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11932         }
11933
11934         if (commentList[currentMove] != NULL) {
11935             cmailCommentList[lastLoadGameNumber - 1]
11936               = StrSave(commentList[currentMove]);
11937         }
11938         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11939
11940         if (appData.debugMode)
11941           fprintf(debugFP, "Saving %s for game %d\n",
11942                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11943
11944         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11945
11946         f = fopen(string, "w");
11947         if (appData.oldSaveStyle) {
11948             SaveGameOldStyle(f); /* also closes the file */
11949
11950             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11951             f = fopen(string, "w");
11952             SavePosition(f, 0, NULL); /* also closes the file */
11953         } else {
11954             fprintf(f, "{--------------\n");
11955             PrintPosition(f, currentMove);
11956             fprintf(f, "--------------}\n\n");
11957
11958             SaveGame(f, 0, NULL); /* also closes the file*/
11959         }
11960
11961         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11962         nCmailMovesRegistered ++;
11963     } else if (nCmailGames == 1) {
11964         DisplayError(_("You have not made a move yet"), 0);
11965         return FALSE;
11966     }
11967
11968     return TRUE;
11969 }
11970
11971 void
11972 MailMoveEvent()
11973 {
11974 #if !WIN32
11975     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11976     FILE *commandOutput;
11977     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11978     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11979     int nBuffers;
11980     int i;
11981     int archived;
11982     char *arcDir;
11983
11984     if (! cmailMsgLoaded) {
11985         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11986         return;
11987     }
11988
11989     if (nCmailGames == nCmailResults) {
11990         DisplayError(_("No unfinished games"), 0);
11991         return;
11992     }
11993
11994 #if CMAIL_PROHIBIT_REMAIL
11995     if (cmailMailedMove) {
11996       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);
11997         DisplayError(msg, 0);
11998         return;
11999     }
12000 #endif
12001
12002     if (! (cmailMailedMove || RegisterMove())) return;
12003
12004     if (   cmailMailedMove
12005         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12006       snprintf(string, MSG_SIZ, partCommandString,
12007                appData.debugMode ? " -v" : "", appData.cmailGameName);
12008         commandOutput = popen(string, "r");
12009
12010         if (commandOutput == NULL) {
12011             DisplayError(_("Failed to invoke cmail"), 0);
12012         } else {
12013             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12014                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12015             }
12016             if (nBuffers > 1) {
12017                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12018                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12019                 nBytes = MSG_SIZ - 1;
12020             } else {
12021                 (void) memcpy(msg, buffer, nBytes);
12022             }
12023             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12024
12025             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12026                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12027
12028                 archived = TRUE;
12029                 for (i = 0; i < nCmailGames; i ++) {
12030                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12031                         archived = FALSE;
12032                     }
12033                 }
12034                 if (   archived
12035                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12036                         != NULL)) {
12037                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12038                            arcDir,
12039                            appData.cmailGameName,
12040                            gameInfo.date);
12041                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12042                     cmailMsgLoaded = FALSE;
12043                 }
12044             }
12045
12046             DisplayInformation(msg);
12047             pclose(commandOutput);
12048         }
12049     } else {
12050         if ((*cmailMsg) != '\0') {
12051             DisplayInformation(cmailMsg);
12052         }
12053     }
12054
12055     return;
12056 #endif /* !WIN32 */
12057 }
12058
12059 char *
12060 CmailMsg()
12061 {
12062 #if WIN32
12063     return NULL;
12064 #else
12065     int  prependComma = 0;
12066     char number[5];
12067     char string[MSG_SIZ];       /* Space for game-list */
12068     int  i;
12069
12070     if (!cmailMsgLoaded) return "";
12071
12072     if (cmailMailedMove) {
12073       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12074     } else {
12075         /* Create a list of games left */
12076       snprintf(string, MSG_SIZ, "[");
12077         for (i = 0; i < nCmailGames; i ++) {
12078             if (! (   cmailMoveRegistered[i]
12079                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12080                 if (prependComma) {
12081                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12082                 } else {
12083                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12084                     prependComma = 1;
12085                 }
12086
12087                 strcat(string, number);
12088             }
12089         }
12090         strcat(string, "]");
12091
12092         if (nCmailMovesRegistered + nCmailResults == 0) {
12093             switch (nCmailGames) {
12094               case 1:
12095                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12096                 break;
12097
12098               case 2:
12099                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12100                 break;
12101
12102               default:
12103                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12104                          nCmailGames);
12105                 break;
12106             }
12107         } else {
12108             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12109               case 1:
12110                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12111                          string);
12112                 break;
12113
12114               case 0:
12115                 if (nCmailResults == nCmailGames) {
12116                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12117                 } else {
12118                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12119                 }
12120                 break;
12121
12122               default:
12123                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12124                          string);
12125             }
12126         }
12127     }
12128     return cmailMsg;
12129 #endif /* WIN32 */
12130 }
12131
12132 void
12133 ResetGameEvent()
12134 {
12135     if (gameMode == Training)
12136       SetTrainingModeOff();
12137
12138     Reset(TRUE, TRUE);
12139     cmailMsgLoaded = FALSE;
12140     if (appData.icsActive) {
12141       SendToICS(ics_prefix);
12142       SendToICS("refresh\n");
12143     }
12144 }
12145
12146 void
12147 ExitEvent(status)
12148      int status;
12149 {
12150     exiting++;
12151     if (exiting > 2) {
12152       /* Give up on clean exit */
12153       exit(status);
12154     }
12155     if (exiting > 1) {
12156       /* Keep trying for clean exit */
12157       return;
12158     }
12159
12160     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12161
12162     if (telnetISR != NULL) {
12163       RemoveInputSource(telnetISR);
12164     }
12165     if (icsPR != NoProc) {
12166       DestroyChildProcess(icsPR, TRUE);
12167     }
12168
12169     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12170     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12171
12172     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12173     /* make sure this other one finishes before killing it!                  */
12174     if(endingGame) { int count = 0;
12175         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12176         while(endingGame && count++ < 10) DoSleep(1);
12177         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12178     }
12179
12180     /* Kill off chess programs */
12181     if (first.pr != NoProc) {
12182         ExitAnalyzeMode();
12183
12184         DoSleep( appData.delayBeforeQuit );
12185         SendToProgram("quit\n", &first);
12186         DoSleep( appData.delayAfterQuit );
12187         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12188     }
12189     if (second.pr != NoProc) {
12190         DoSleep( appData.delayBeforeQuit );
12191         SendToProgram("quit\n", &second);
12192         DoSleep( appData.delayAfterQuit );
12193         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12194     }
12195     if (first.isr != NULL) {
12196         RemoveInputSource(first.isr);
12197     }
12198     if (second.isr != NULL) {
12199         RemoveInputSource(second.isr);
12200     }
12201
12202     ShutDownFrontEnd();
12203     exit(status);
12204 }
12205
12206 void
12207 PauseEvent()
12208 {
12209     if (appData.debugMode)
12210         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12211     if (pausing) {
12212         pausing = FALSE;
12213         ModeHighlight();
12214         if (gameMode == MachinePlaysWhite ||
12215             gameMode == MachinePlaysBlack) {
12216             StartClocks();
12217         } else {
12218             DisplayBothClocks();
12219         }
12220         if (gameMode == PlayFromGameFile) {
12221             if (appData.timeDelay >= 0)
12222                 AutoPlayGameLoop();
12223         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12224             Reset(FALSE, TRUE);
12225             SendToICS(ics_prefix);
12226             SendToICS("refresh\n");
12227         } else if (currentMove < forwardMostMove) {
12228             ForwardInner(forwardMostMove);
12229         }
12230         pauseExamInvalid = FALSE;
12231     } else {
12232         switch (gameMode) {
12233           default:
12234             return;
12235           case IcsExamining:
12236             pauseExamForwardMostMove = forwardMostMove;
12237             pauseExamInvalid = FALSE;
12238             /* fall through */
12239           case IcsObserving:
12240           case IcsPlayingWhite:
12241           case IcsPlayingBlack:
12242             pausing = TRUE;
12243             ModeHighlight();
12244             return;
12245           case PlayFromGameFile:
12246             (void) StopLoadGameTimer();
12247             pausing = TRUE;
12248             ModeHighlight();
12249             break;
12250           case BeginningOfGame:
12251             if (appData.icsActive) return;
12252             /* else fall through */
12253           case MachinePlaysWhite:
12254           case MachinePlaysBlack:
12255           case TwoMachinesPlay:
12256             if (forwardMostMove == 0)
12257               return;           /* don't pause if no one has moved */
12258             if ((gameMode == MachinePlaysWhite &&
12259                  !WhiteOnMove(forwardMostMove)) ||
12260                 (gameMode == MachinePlaysBlack &&
12261                  WhiteOnMove(forwardMostMove))) {
12262                 StopClocks();
12263             }
12264             pausing = TRUE;
12265             ModeHighlight();
12266             break;
12267         }
12268     }
12269 }
12270
12271 void
12272 EditCommentEvent()
12273 {
12274     char title[MSG_SIZ];
12275
12276     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12277       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12278     } else {
12279       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12280                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12281                parseList[currentMove - 1]);
12282     }
12283
12284     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12285 }
12286
12287
12288 void
12289 EditTagsEvent()
12290 {
12291     char *tags = PGNTags(&gameInfo);
12292     EditTagsPopUp(tags, NULL);
12293     free(tags);
12294 }
12295
12296 void
12297 AnalyzeModeEvent()
12298 {
12299     if (appData.noChessProgram || gameMode == AnalyzeMode)
12300       return;
12301
12302     if (gameMode != AnalyzeFile) {
12303         if (!appData.icsEngineAnalyze) {
12304                EditGameEvent();
12305                if (gameMode != EditGame) return;
12306         }
12307         ResurrectChessProgram();
12308         SendToProgram("analyze\n", &first);
12309         first.analyzing = TRUE;
12310         /*first.maybeThinking = TRUE;*/
12311         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12312         EngineOutputPopUp();
12313     }
12314     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12315     pausing = FALSE;
12316     ModeHighlight();
12317     SetGameInfo();
12318
12319     StartAnalysisClock();
12320     GetTimeMark(&lastNodeCountTime);
12321     lastNodeCount = 0;
12322 }
12323
12324 void
12325 AnalyzeFileEvent()
12326 {
12327     if (appData.noChessProgram || gameMode == AnalyzeFile)
12328       return;
12329
12330     if (gameMode != AnalyzeMode) {
12331         EditGameEvent();
12332         if (gameMode != EditGame) return;
12333         ResurrectChessProgram();
12334         SendToProgram("analyze\n", &first);
12335         first.analyzing = TRUE;
12336         /*first.maybeThinking = TRUE;*/
12337         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12338         EngineOutputPopUp();
12339     }
12340     gameMode = AnalyzeFile;
12341     pausing = FALSE;
12342     ModeHighlight();
12343     SetGameInfo();
12344
12345     StartAnalysisClock();
12346     GetTimeMark(&lastNodeCountTime);
12347     lastNodeCount = 0;
12348 }
12349
12350 void
12351 MachineWhiteEvent()
12352 {
12353     char buf[MSG_SIZ];
12354     char *bookHit = NULL;
12355
12356     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12357       return;
12358
12359
12360     if (gameMode == PlayFromGameFile ||
12361         gameMode == TwoMachinesPlay  ||
12362         gameMode == Training         ||
12363         gameMode == AnalyzeMode      ||
12364         gameMode == EndOfGame)
12365         EditGameEvent();
12366
12367     if (gameMode == EditPosition)
12368         EditPositionDone(TRUE);
12369
12370     if (!WhiteOnMove(currentMove)) {
12371         DisplayError(_("It is not White's turn"), 0);
12372         return;
12373     }
12374
12375     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12376       ExitAnalyzeMode();
12377
12378     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12379         gameMode == AnalyzeFile)
12380         TruncateGame();
12381
12382     ResurrectChessProgram();    /* in case it isn't running */
12383     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12384         gameMode = MachinePlaysWhite;
12385         ResetClocks();
12386     } else
12387     gameMode = MachinePlaysWhite;
12388     pausing = FALSE;
12389     ModeHighlight();
12390     SetGameInfo();
12391     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12392     DisplayTitle(buf);
12393     if (first.sendName) {
12394       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12395       SendToProgram(buf, &first);
12396     }
12397     if (first.sendTime) {
12398       if (first.useColors) {
12399         SendToProgram("black\n", &first); /*gnu kludge*/
12400       }
12401       SendTimeRemaining(&first, TRUE);
12402     }
12403     if (first.useColors) {
12404       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12405     }
12406     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12407     SetMachineThinkingEnables();
12408     first.maybeThinking = TRUE;
12409     StartClocks();
12410     firstMove = FALSE;
12411
12412     if (appData.autoFlipView && !flipView) {
12413       flipView = !flipView;
12414       DrawPosition(FALSE, NULL);
12415       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12416     }
12417
12418     if(bookHit) { // [HGM] book: simulate book reply
12419         static char bookMove[MSG_SIZ]; // a bit generous?
12420
12421         programStats.nodes = programStats.depth = programStats.time =
12422         programStats.score = programStats.got_only_move = 0;
12423         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12424
12425         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12426         strcat(bookMove, bookHit);
12427         HandleMachineMove(bookMove, &first);
12428     }
12429 }
12430
12431 void
12432 MachineBlackEvent()
12433 {
12434   char buf[MSG_SIZ];
12435   char *bookHit = NULL;
12436
12437     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12438         return;
12439
12440
12441     if (gameMode == PlayFromGameFile ||
12442         gameMode == TwoMachinesPlay  ||
12443         gameMode == Training         ||
12444         gameMode == AnalyzeMode      ||
12445         gameMode == EndOfGame)
12446         EditGameEvent();
12447
12448     if (gameMode == EditPosition)
12449         EditPositionDone(TRUE);
12450
12451     if (WhiteOnMove(currentMove)) {
12452         DisplayError(_("It is not Black's turn"), 0);
12453         return;
12454     }
12455
12456     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12457       ExitAnalyzeMode();
12458
12459     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12460         gameMode == AnalyzeFile)
12461         TruncateGame();
12462
12463     ResurrectChessProgram();    /* in case it isn't running */
12464     gameMode = MachinePlaysBlack;
12465     pausing = FALSE;
12466     ModeHighlight();
12467     SetGameInfo();
12468     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12469     DisplayTitle(buf);
12470     if (first.sendName) {
12471       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12472       SendToProgram(buf, &first);
12473     }
12474     if (first.sendTime) {
12475       if (first.useColors) {
12476         SendToProgram("white\n", &first); /*gnu kludge*/
12477       }
12478       SendTimeRemaining(&first, FALSE);
12479     }
12480     if (first.useColors) {
12481       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12482     }
12483     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12484     SetMachineThinkingEnables();
12485     first.maybeThinking = TRUE;
12486     StartClocks();
12487
12488     if (appData.autoFlipView && flipView) {
12489       flipView = !flipView;
12490       DrawPosition(FALSE, NULL);
12491       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12492     }
12493     if(bookHit) { // [HGM] book: simulate book reply
12494         static char bookMove[MSG_SIZ]; // a bit generous?
12495
12496         programStats.nodes = programStats.depth = programStats.time =
12497         programStats.score = programStats.got_only_move = 0;
12498         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12499
12500         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12501         strcat(bookMove, bookHit);
12502         HandleMachineMove(bookMove, &first);
12503     }
12504 }
12505
12506
12507 void
12508 DisplayTwoMachinesTitle()
12509 {
12510     char buf[MSG_SIZ];
12511     if (appData.matchGames > 0) {
12512         if (first.twoMachinesColor[0] == 'w') {
12513           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12514                    gameInfo.white, gameInfo.black,
12515                    first.matchWins, second.matchWins,
12516                    matchGame - 1 - (first.matchWins + second.matchWins));
12517         } else {
12518           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12519                    gameInfo.white, gameInfo.black,
12520                    second.matchWins, first.matchWins,
12521                    matchGame - 1 - (first.matchWins + second.matchWins));
12522         }
12523     } else {
12524       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12525     }
12526     DisplayTitle(buf);
12527 }
12528
12529 void
12530 SettingsMenuIfReady()
12531 {
12532   if (second.lastPing != second.lastPong) {
12533     DisplayMessage("", _("Waiting for second chess program"));
12534     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12535     return;
12536   }
12537   ThawUI();
12538   DisplayMessage("", "");
12539   SettingsPopUp(&second);
12540 }
12541
12542 int
12543 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12544 {
12545     char buf[MSG_SIZ];
12546     if (cps->pr == NULL) {
12547         StartChessProgram(cps);
12548         if (cps->protocolVersion == 1) {
12549           retry();
12550         } else {
12551           /* kludge: allow timeout for initial "feature" command */
12552           FreezeUI();
12553           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12554           DisplayMessage("", buf);
12555           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12556         }
12557         return 1;
12558     }
12559     return 0;
12560 }
12561
12562 void
12563 TwoMachinesEvent P((void))
12564 {
12565     int i;
12566     char buf[MSG_SIZ];
12567     ChessProgramState *onmove;
12568     char *bookHit = NULL;
12569     static int stalling = 0;
12570     TimeMark now;
12571     long wait;
12572
12573     if (appData.noChessProgram) return;
12574
12575     switch (gameMode) {
12576       case TwoMachinesPlay:
12577         return;
12578       case MachinePlaysWhite:
12579       case MachinePlaysBlack:
12580         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12581             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12582             return;
12583         }
12584         /* fall through */
12585       case BeginningOfGame:
12586       case PlayFromGameFile:
12587       case EndOfGame:
12588         EditGameEvent();
12589         if (gameMode != EditGame) return;
12590         break;
12591       case EditPosition:
12592         EditPositionDone(TRUE);
12593         break;
12594       case AnalyzeMode:
12595       case AnalyzeFile:
12596         ExitAnalyzeMode();
12597         break;
12598       case EditGame:
12599       default:
12600         break;
12601     }
12602
12603 //    forwardMostMove = currentMove;
12604     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12605
12606     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12607
12608     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12609     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12610       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12611       return;
12612     }
12613     if(!stalling) {
12614       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12615       SendToProgram("force\n", &second);
12616       stalling = 1;
12617       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12618       return;
12619     }
12620     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12621     if(appData.matchPause>10000 || appData.matchPause<10)
12622                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12623     wait = SubtractTimeMarks(&now, &pauseStart);
12624     if(wait < appData.matchPause) {
12625         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12626         return;
12627     }
12628     stalling = 0;
12629     DisplayMessage("", "");
12630     if (startedFromSetupPosition) {
12631         SendBoard(&second, backwardMostMove);
12632     if (appData.debugMode) {
12633         fprintf(debugFP, "Two Machines\n");
12634     }
12635     }
12636     for (i = backwardMostMove; i < forwardMostMove; i++) {
12637         SendMoveToProgram(i, &second);
12638     }
12639
12640     gameMode = TwoMachinesPlay;
12641     pausing = FALSE;
12642     ModeHighlight();
12643     SetGameInfo();
12644     DisplayTwoMachinesTitle();
12645     firstMove = TRUE;
12646     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12647         onmove = &first;
12648     } else {
12649         onmove = &second;
12650     }
12651     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12652     SendToProgram(first.computerString, &first);
12653     if (first.sendName) {
12654       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12655       SendToProgram(buf, &first);
12656     }
12657     SendToProgram(second.computerString, &second);
12658     if (second.sendName) {
12659       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12660       SendToProgram(buf, &second);
12661     }
12662
12663     ResetClocks();
12664     if (!first.sendTime || !second.sendTime) {
12665         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12666         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12667     }
12668     if (onmove->sendTime) {
12669       if (onmove->useColors) {
12670         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12671       }
12672       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12673     }
12674     if (onmove->useColors) {
12675       SendToProgram(onmove->twoMachinesColor, onmove);
12676     }
12677     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12678 //    SendToProgram("go\n", onmove);
12679     onmove->maybeThinking = TRUE;
12680     SetMachineThinkingEnables();
12681
12682     StartClocks();
12683
12684     if(bookHit) { // [HGM] book: simulate book reply
12685         static char bookMove[MSG_SIZ]; // a bit generous?
12686
12687         programStats.nodes = programStats.depth = programStats.time =
12688         programStats.score = programStats.got_only_move = 0;
12689         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12690
12691         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12692         strcat(bookMove, bookHit);
12693         savedMessage = bookMove; // args for deferred call
12694         savedState = onmove;
12695         ScheduleDelayedEvent(DeferredBookMove, 1);
12696     }
12697 }
12698
12699 void
12700 TrainingEvent()
12701 {
12702     if (gameMode == Training) {
12703       SetTrainingModeOff();
12704       gameMode = PlayFromGameFile;
12705       DisplayMessage("", _("Training mode off"));
12706     } else {
12707       gameMode = Training;
12708       animateTraining = appData.animate;
12709
12710       /* make sure we are not already at the end of the game */
12711       if (currentMove < forwardMostMove) {
12712         SetTrainingModeOn();
12713         DisplayMessage("", _("Training mode on"));
12714       } else {
12715         gameMode = PlayFromGameFile;
12716         DisplayError(_("Already at end of game"), 0);
12717       }
12718     }
12719     ModeHighlight();
12720 }
12721
12722 void
12723 IcsClientEvent()
12724 {
12725     if (!appData.icsActive) return;
12726     switch (gameMode) {
12727       case IcsPlayingWhite:
12728       case IcsPlayingBlack:
12729       case IcsObserving:
12730       case IcsIdle:
12731       case BeginningOfGame:
12732       case IcsExamining:
12733         return;
12734
12735       case EditGame:
12736         break;
12737
12738       case EditPosition:
12739         EditPositionDone(TRUE);
12740         break;
12741
12742       case AnalyzeMode:
12743       case AnalyzeFile:
12744         ExitAnalyzeMode();
12745         break;
12746
12747       default:
12748         EditGameEvent();
12749         break;
12750     }
12751
12752     gameMode = IcsIdle;
12753     ModeHighlight();
12754     return;
12755 }
12756
12757
12758 void
12759 EditGameEvent()
12760 {
12761     int i;
12762
12763     switch (gameMode) {
12764       case Training:
12765         SetTrainingModeOff();
12766         break;
12767       case MachinePlaysWhite:
12768       case MachinePlaysBlack:
12769       case BeginningOfGame:
12770         SendToProgram("force\n", &first);
12771         SetUserThinkingEnables();
12772         break;
12773       case PlayFromGameFile:
12774         (void) StopLoadGameTimer();
12775         if (gameFileFP != NULL) {
12776             gameFileFP = NULL;
12777         }
12778         break;
12779       case EditPosition:
12780         EditPositionDone(TRUE);
12781         break;
12782       case AnalyzeMode:
12783       case AnalyzeFile:
12784         ExitAnalyzeMode();
12785         SendToProgram("force\n", &first);
12786         break;
12787       case TwoMachinesPlay:
12788         GameEnds(EndOfFile, NULL, GE_PLAYER);
12789         ResurrectChessProgram();
12790         SetUserThinkingEnables();
12791         break;
12792       case EndOfGame:
12793         ResurrectChessProgram();
12794         break;
12795       case IcsPlayingBlack:
12796       case IcsPlayingWhite:
12797         DisplayError(_("Warning: You are still playing a game"), 0);
12798         break;
12799       case IcsObserving:
12800         DisplayError(_("Warning: You are still observing a game"), 0);
12801         break;
12802       case IcsExamining:
12803         DisplayError(_("Warning: You are still examining a game"), 0);
12804         break;
12805       case IcsIdle:
12806         break;
12807       case EditGame:
12808       default:
12809         return;
12810     }
12811
12812     pausing = FALSE;
12813     StopClocks();
12814     first.offeredDraw = second.offeredDraw = 0;
12815
12816     if (gameMode == PlayFromGameFile) {
12817         whiteTimeRemaining = timeRemaining[0][currentMove];
12818         blackTimeRemaining = timeRemaining[1][currentMove];
12819         DisplayTitle("");
12820     }
12821
12822     if (gameMode == MachinePlaysWhite ||
12823         gameMode == MachinePlaysBlack ||
12824         gameMode == TwoMachinesPlay ||
12825         gameMode == EndOfGame) {
12826         i = forwardMostMove;
12827         while (i > currentMove) {
12828             SendToProgram("undo\n", &first);
12829             i--;
12830         }
12831         whiteTimeRemaining = timeRemaining[0][currentMove];
12832         blackTimeRemaining = timeRemaining[1][currentMove];
12833         DisplayBothClocks();
12834         if (whiteFlag || blackFlag) {
12835             whiteFlag = blackFlag = 0;
12836         }
12837         DisplayTitle("");
12838     }
12839
12840     gameMode = EditGame;
12841     ModeHighlight();
12842     SetGameInfo();
12843 }
12844
12845
12846 void
12847 EditPositionEvent()
12848 {
12849     if (gameMode == EditPosition) {
12850         EditGameEvent();
12851         return;
12852     }
12853
12854     EditGameEvent();
12855     if (gameMode != EditGame) return;
12856
12857     gameMode = EditPosition;
12858     ModeHighlight();
12859     SetGameInfo();
12860     if (currentMove > 0)
12861       CopyBoard(boards[0], boards[currentMove]);
12862
12863     blackPlaysFirst = !WhiteOnMove(currentMove);
12864     ResetClocks();
12865     currentMove = forwardMostMove = backwardMostMove = 0;
12866     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12867     DisplayMove(-1);
12868 }
12869
12870 void
12871 ExitAnalyzeMode()
12872 {
12873     /* [DM] icsEngineAnalyze - possible call from other functions */
12874     if (appData.icsEngineAnalyze) {
12875         appData.icsEngineAnalyze = FALSE;
12876
12877         DisplayMessage("",_("Close ICS engine analyze..."));
12878     }
12879     if (first.analysisSupport && first.analyzing) {
12880       SendToProgram("exit\n", &first);
12881       first.analyzing = FALSE;
12882     }
12883     thinkOutput[0] = NULLCHAR;
12884 }
12885
12886 void
12887 EditPositionDone(Boolean fakeRights)
12888 {
12889     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12890
12891     startedFromSetupPosition = TRUE;
12892     InitChessProgram(&first, FALSE);
12893     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12894       boards[0][EP_STATUS] = EP_NONE;
12895       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12896     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12897         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12898         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12899       } else boards[0][CASTLING][2] = NoRights;
12900     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12901         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12902         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12903       } else boards[0][CASTLING][5] = NoRights;
12904     }
12905     SendToProgram("force\n", &first);
12906     if (blackPlaysFirst) {
12907         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12908         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12909         currentMove = forwardMostMove = backwardMostMove = 1;
12910         CopyBoard(boards[1], boards[0]);
12911     } else {
12912         currentMove = forwardMostMove = backwardMostMove = 0;
12913     }
12914     SendBoard(&first, forwardMostMove);
12915     if (appData.debugMode) {
12916         fprintf(debugFP, "EditPosDone\n");
12917     }
12918     DisplayTitle("");
12919     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12920     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12921     gameMode = EditGame;
12922     ModeHighlight();
12923     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12924     ClearHighlights(); /* [AS] */
12925 }
12926
12927 /* Pause for `ms' milliseconds */
12928 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12929 void
12930 TimeDelay(ms)
12931      long ms;
12932 {
12933     TimeMark m1, m2;
12934
12935     GetTimeMark(&m1);
12936     do {
12937         GetTimeMark(&m2);
12938     } while (SubtractTimeMarks(&m2, &m1) < ms);
12939 }
12940
12941 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12942 void
12943 SendMultiLineToICS(buf)
12944      char *buf;
12945 {
12946     char temp[MSG_SIZ+1], *p;
12947     int len;
12948
12949     len = strlen(buf);
12950     if (len > MSG_SIZ)
12951       len = MSG_SIZ;
12952
12953     strncpy(temp, buf, len);
12954     temp[len] = 0;
12955
12956     p = temp;
12957     while (*p) {
12958         if (*p == '\n' || *p == '\r')
12959           *p = ' ';
12960         ++p;
12961     }
12962
12963     strcat(temp, "\n");
12964     SendToICS(temp);
12965     SendToPlayer(temp, strlen(temp));
12966 }
12967
12968 void
12969 SetWhiteToPlayEvent()
12970 {
12971     if (gameMode == EditPosition) {
12972         blackPlaysFirst = FALSE;
12973         DisplayBothClocks();    /* works because currentMove is 0 */
12974     } else if (gameMode == IcsExamining) {
12975         SendToICS(ics_prefix);
12976         SendToICS("tomove white\n");
12977     }
12978 }
12979
12980 void
12981 SetBlackToPlayEvent()
12982 {
12983     if (gameMode == EditPosition) {
12984         blackPlaysFirst = TRUE;
12985         currentMove = 1;        /* kludge */
12986         DisplayBothClocks();
12987         currentMove = 0;
12988     } else if (gameMode == IcsExamining) {
12989         SendToICS(ics_prefix);
12990         SendToICS("tomove black\n");
12991     }
12992 }
12993
12994 void
12995 EditPositionMenuEvent(selection, x, y)
12996      ChessSquare selection;
12997      int x, y;
12998 {
12999     char buf[MSG_SIZ];
13000     ChessSquare piece = boards[0][y][x];
13001
13002     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13003
13004     switch (selection) {
13005       case ClearBoard:
13006         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13007             SendToICS(ics_prefix);
13008             SendToICS("bsetup clear\n");
13009         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13010             SendToICS(ics_prefix);
13011             SendToICS("clearboard\n");
13012         } else {
13013             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13014                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13015                 for (y = 0; y < BOARD_HEIGHT; y++) {
13016                     if (gameMode == IcsExamining) {
13017                         if (boards[currentMove][y][x] != EmptySquare) {
13018                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13019                                     AAA + x, ONE + y);
13020                             SendToICS(buf);
13021                         }
13022                     } else {
13023                         boards[0][y][x] = p;
13024                     }
13025                 }
13026             }
13027         }
13028         if (gameMode == EditPosition) {
13029             DrawPosition(FALSE, boards[0]);
13030         }
13031         break;
13032
13033       case WhitePlay:
13034         SetWhiteToPlayEvent();
13035         break;
13036
13037       case BlackPlay:
13038         SetBlackToPlayEvent();
13039         break;
13040
13041       case EmptySquare:
13042         if (gameMode == IcsExamining) {
13043             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13044             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13045             SendToICS(buf);
13046         } else {
13047             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13048                 if(x == BOARD_LEFT-2) {
13049                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13050                     boards[0][y][1] = 0;
13051                 } else
13052                 if(x == BOARD_RGHT+1) {
13053                     if(y >= gameInfo.holdingsSize) break;
13054                     boards[0][y][BOARD_WIDTH-2] = 0;
13055                 } else break;
13056             }
13057             boards[0][y][x] = EmptySquare;
13058             DrawPosition(FALSE, boards[0]);
13059         }
13060         break;
13061
13062       case PromotePiece:
13063         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13064            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13065             selection = (ChessSquare) (PROMOTED piece);
13066         } else if(piece == EmptySquare) selection = WhiteSilver;
13067         else selection = (ChessSquare)((int)piece - 1);
13068         goto defaultlabel;
13069
13070       case DemotePiece:
13071         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13072            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13073             selection = (ChessSquare) (DEMOTED piece);
13074         } else if(piece == EmptySquare) selection = BlackSilver;
13075         else selection = (ChessSquare)((int)piece + 1);
13076         goto defaultlabel;
13077
13078       case WhiteQueen:
13079       case BlackQueen:
13080         if(gameInfo.variant == VariantShatranj ||
13081            gameInfo.variant == VariantXiangqi  ||
13082            gameInfo.variant == VariantCourier  ||
13083            gameInfo.variant == VariantMakruk     )
13084             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13085         goto defaultlabel;
13086
13087       case WhiteKing:
13088       case BlackKing:
13089         if(gameInfo.variant == VariantXiangqi)
13090             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13091         if(gameInfo.variant == VariantKnightmate)
13092             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13093       default:
13094         defaultlabel:
13095         if (gameMode == IcsExamining) {
13096             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13097             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13098                      PieceToChar(selection), AAA + x, ONE + y);
13099             SendToICS(buf);
13100         } else {
13101             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13102                 int n;
13103                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13104                     n = PieceToNumber(selection - BlackPawn);
13105                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13106                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13107                     boards[0][BOARD_HEIGHT-1-n][1]++;
13108                 } else
13109                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13110                     n = PieceToNumber(selection);
13111                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13112                     boards[0][n][BOARD_WIDTH-1] = selection;
13113                     boards[0][n][BOARD_WIDTH-2]++;
13114                 }
13115             } else
13116             boards[0][y][x] = selection;
13117             DrawPosition(TRUE, boards[0]);
13118         }
13119         break;
13120     }
13121 }
13122
13123
13124 void
13125 DropMenuEvent(selection, x, y)
13126      ChessSquare selection;
13127      int x, y;
13128 {
13129     ChessMove moveType;
13130
13131     switch (gameMode) {
13132       case IcsPlayingWhite:
13133       case MachinePlaysBlack:
13134         if (!WhiteOnMove(currentMove)) {
13135             DisplayMoveError(_("It is Black's turn"));
13136             return;
13137         }
13138         moveType = WhiteDrop;
13139         break;
13140       case IcsPlayingBlack:
13141       case MachinePlaysWhite:
13142         if (WhiteOnMove(currentMove)) {
13143             DisplayMoveError(_("It is White's turn"));
13144             return;
13145         }
13146         moveType = BlackDrop;
13147         break;
13148       case EditGame:
13149         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13150         break;
13151       default:
13152         return;
13153     }
13154
13155     if (moveType == BlackDrop && selection < BlackPawn) {
13156       selection = (ChessSquare) ((int) selection
13157                                  + (int) BlackPawn - (int) WhitePawn);
13158     }
13159     if (boards[currentMove][y][x] != EmptySquare) {
13160         DisplayMoveError(_("That square is occupied"));
13161         return;
13162     }
13163
13164     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13165 }
13166
13167 void
13168 AcceptEvent()
13169 {
13170     /* Accept a pending offer of any kind from opponent */
13171
13172     if (appData.icsActive) {
13173         SendToICS(ics_prefix);
13174         SendToICS("accept\n");
13175     } else if (cmailMsgLoaded) {
13176         if (currentMove == cmailOldMove &&
13177             commentList[cmailOldMove] != NULL &&
13178             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13179                    "Black offers a draw" : "White offers a draw")) {
13180             TruncateGame();
13181             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13182             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13183         } else {
13184             DisplayError(_("There is no pending offer on this move"), 0);
13185             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13186         }
13187     } else {
13188         /* Not used for offers from chess program */
13189     }
13190 }
13191
13192 void
13193 DeclineEvent()
13194 {
13195     /* Decline a pending offer of any kind from opponent */
13196
13197     if (appData.icsActive) {
13198         SendToICS(ics_prefix);
13199         SendToICS("decline\n");
13200     } else if (cmailMsgLoaded) {
13201         if (currentMove == cmailOldMove &&
13202             commentList[cmailOldMove] != NULL &&
13203             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13204                    "Black offers a draw" : "White offers a draw")) {
13205 #ifdef NOTDEF
13206             AppendComment(cmailOldMove, "Draw declined", TRUE);
13207             DisplayComment(cmailOldMove - 1, "Draw declined");
13208 #endif /*NOTDEF*/
13209         } else {
13210             DisplayError(_("There is no pending offer on this move"), 0);
13211         }
13212     } else {
13213         /* Not used for offers from chess program */
13214     }
13215 }
13216
13217 void
13218 RematchEvent()
13219 {
13220     /* Issue ICS rematch command */
13221     if (appData.icsActive) {
13222         SendToICS(ics_prefix);
13223         SendToICS("rematch\n");
13224     }
13225 }
13226
13227 void
13228 CallFlagEvent()
13229 {
13230     /* Call your opponent's flag (claim a win on time) */
13231     if (appData.icsActive) {
13232         SendToICS(ics_prefix);
13233         SendToICS("flag\n");
13234     } else {
13235         switch (gameMode) {
13236           default:
13237             return;
13238           case MachinePlaysWhite:
13239             if (whiteFlag) {
13240                 if (blackFlag)
13241                   GameEnds(GameIsDrawn, "Both players ran out of time",
13242                            GE_PLAYER);
13243                 else
13244                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13245             } else {
13246                 DisplayError(_("Your opponent is not out of time"), 0);
13247             }
13248             break;
13249           case MachinePlaysBlack:
13250             if (blackFlag) {
13251                 if (whiteFlag)
13252                   GameEnds(GameIsDrawn, "Both players ran out of time",
13253                            GE_PLAYER);
13254                 else
13255                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13256             } else {
13257                 DisplayError(_("Your opponent is not out of time"), 0);
13258             }
13259             break;
13260         }
13261     }
13262 }
13263
13264 void
13265 ClockClick(int which)
13266 {       // [HGM] code moved to back-end from winboard.c
13267         if(which) { // black clock
13268           if (gameMode == EditPosition || gameMode == IcsExamining) {
13269             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13270             SetBlackToPlayEvent();
13271           } else if (gameMode == EditGame || shiftKey) {
13272             AdjustClock(which, -1);
13273           } else if (gameMode == IcsPlayingWhite ||
13274                      gameMode == MachinePlaysBlack) {
13275             CallFlagEvent();
13276           }
13277         } else { // white clock
13278           if (gameMode == EditPosition || gameMode == IcsExamining) {
13279             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13280             SetWhiteToPlayEvent();
13281           } else if (gameMode == EditGame || shiftKey) {
13282             AdjustClock(which, -1);
13283           } else if (gameMode == IcsPlayingBlack ||
13284                    gameMode == MachinePlaysWhite) {
13285             CallFlagEvent();
13286           }
13287         }
13288 }
13289
13290 void
13291 DrawEvent()
13292 {
13293     /* Offer draw or accept pending draw offer from opponent */
13294
13295     if (appData.icsActive) {
13296         /* Note: tournament rules require draw offers to be
13297            made after you make your move but before you punch
13298            your clock.  Currently ICS doesn't let you do that;
13299            instead, you immediately punch your clock after making
13300            a move, but you can offer a draw at any time. */
13301
13302         SendToICS(ics_prefix);
13303         SendToICS("draw\n");
13304         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13305     } else if (cmailMsgLoaded) {
13306         if (currentMove == cmailOldMove &&
13307             commentList[cmailOldMove] != NULL &&
13308             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13309                    "Black offers a draw" : "White offers a draw")) {
13310             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13311             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13312         } else if (currentMove == cmailOldMove + 1) {
13313             char *offer = WhiteOnMove(cmailOldMove) ?
13314               "White offers a draw" : "Black offers a draw";
13315             AppendComment(currentMove, offer, TRUE);
13316             DisplayComment(currentMove - 1, offer);
13317             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13318         } else {
13319             DisplayError(_("You must make your move before offering a draw"), 0);
13320             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13321         }
13322     } else if (first.offeredDraw) {
13323         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13324     } else {
13325         if (first.sendDrawOffers) {
13326             SendToProgram("draw\n", &first);
13327             userOfferedDraw = TRUE;
13328         }
13329     }
13330 }
13331
13332 void
13333 AdjournEvent()
13334 {
13335     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13336
13337     if (appData.icsActive) {
13338         SendToICS(ics_prefix);
13339         SendToICS("adjourn\n");
13340     } else {
13341         /* Currently GNU Chess doesn't offer or accept Adjourns */
13342     }
13343 }
13344
13345
13346 void
13347 AbortEvent()
13348 {
13349     /* Offer Abort or accept pending Abort offer from opponent */
13350
13351     if (appData.icsActive) {
13352         SendToICS(ics_prefix);
13353         SendToICS("abort\n");
13354     } else {
13355         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13356     }
13357 }
13358
13359 void
13360 ResignEvent()
13361 {
13362     /* Resign.  You can do this even if it's not your turn. */
13363
13364     if (appData.icsActive) {
13365         SendToICS(ics_prefix);
13366         SendToICS("resign\n");
13367     } else {
13368         switch (gameMode) {
13369           case MachinePlaysWhite:
13370             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13371             break;
13372           case MachinePlaysBlack:
13373             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13374             break;
13375           case EditGame:
13376             if (cmailMsgLoaded) {
13377                 TruncateGame();
13378                 if (WhiteOnMove(cmailOldMove)) {
13379                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13380                 } else {
13381                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13382                 }
13383                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13384             }
13385             break;
13386           default:
13387             break;
13388         }
13389     }
13390 }
13391
13392
13393 void
13394 StopObservingEvent()
13395 {
13396     /* Stop observing current games */
13397     SendToICS(ics_prefix);
13398     SendToICS("unobserve\n");
13399 }
13400
13401 void
13402 StopExaminingEvent()
13403 {
13404     /* Stop observing current game */
13405     SendToICS(ics_prefix);
13406     SendToICS("unexamine\n");
13407 }
13408
13409 void
13410 ForwardInner(target)
13411      int target;
13412 {
13413     int limit;
13414
13415     if (appData.debugMode)
13416         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13417                 target, currentMove, forwardMostMove);
13418
13419     if (gameMode == EditPosition)
13420       return;
13421
13422     if (gameMode == PlayFromGameFile && !pausing)
13423       PauseEvent();
13424
13425     if (gameMode == IcsExamining && pausing)
13426       limit = pauseExamForwardMostMove;
13427     else
13428       limit = forwardMostMove;
13429
13430     if (target > limit) target = limit;
13431
13432     if (target > 0 && moveList[target - 1][0]) {
13433         int fromX, fromY, toX, toY;
13434         toX = moveList[target - 1][2] - AAA;
13435         toY = moveList[target - 1][3] - ONE;
13436         if (moveList[target - 1][1] == '@') {
13437             if (appData.highlightLastMove) {
13438                 SetHighlights(-1, -1, toX, toY);
13439             }
13440         } else {
13441             fromX = moveList[target - 1][0] - AAA;
13442             fromY = moveList[target - 1][1] - ONE;
13443             if (target == currentMove + 1) {
13444                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13445             }
13446             if (appData.highlightLastMove) {
13447                 SetHighlights(fromX, fromY, toX, toY);
13448             }
13449         }
13450     }
13451     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13452         gameMode == Training || gameMode == PlayFromGameFile ||
13453         gameMode == AnalyzeFile) {
13454         while (currentMove < target) {
13455             SendMoveToProgram(currentMove++, &first);
13456         }
13457     } else {
13458         currentMove = target;
13459     }
13460
13461     if (gameMode == EditGame || gameMode == EndOfGame) {
13462         whiteTimeRemaining = timeRemaining[0][currentMove];
13463         blackTimeRemaining = timeRemaining[1][currentMove];
13464     }
13465     DisplayBothClocks();
13466     DisplayMove(currentMove - 1);
13467     DrawPosition(FALSE, boards[currentMove]);
13468     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13469     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13470         DisplayComment(currentMove - 1, commentList[currentMove]);
13471     }
13472 }
13473
13474
13475 void
13476 ForwardEvent()
13477 {
13478     if (gameMode == IcsExamining && !pausing) {
13479         SendToICS(ics_prefix);
13480         SendToICS("forward\n");
13481     } else {
13482         ForwardInner(currentMove + 1);
13483     }
13484 }
13485
13486 void
13487 ToEndEvent()
13488 {
13489     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13490         /* to optimze, we temporarily turn off analysis mode while we feed
13491          * the remaining moves to the engine. Otherwise we get analysis output
13492          * after each move.
13493          */
13494         if (first.analysisSupport) {
13495           SendToProgram("exit\nforce\n", &first);
13496           first.analyzing = FALSE;
13497         }
13498     }
13499
13500     if (gameMode == IcsExamining && !pausing) {
13501         SendToICS(ics_prefix);
13502         SendToICS("forward 999999\n");
13503     } else {
13504         ForwardInner(forwardMostMove);
13505     }
13506
13507     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13508         /* we have fed all the moves, so reactivate analysis mode */
13509         SendToProgram("analyze\n", &first);
13510         first.analyzing = TRUE;
13511         /*first.maybeThinking = TRUE;*/
13512         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13513     }
13514 }
13515
13516 void
13517 BackwardInner(target)
13518      int target;
13519 {
13520     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13521
13522     if (appData.debugMode)
13523         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13524                 target, currentMove, forwardMostMove);
13525
13526     if (gameMode == EditPosition) return;
13527     if (currentMove <= backwardMostMove) {
13528         ClearHighlights();
13529         DrawPosition(full_redraw, boards[currentMove]);
13530         return;
13531     }
13532     if (gameMode == PlayFromGameFile && !pausing)
13533       PauseEvent();
13534
13535     if (moveList[target][0]) {
13536         int fromX, fromY, toX, toY;
13537         toX = moveList[target][2] - AAA;
13538         toY = moveList[target][3] - ONE;
13539         if (moveList[target][1] == '@') {
13540             if (appData.highlightLastMove) {
13541                 SetHighlights(-1, -1, toX, toY);
13542             }
13543         } else {
13544             fromX = moveList[target][0] - AAA;
13545             fromY = moveList[target][1] - ONE;
13546             if (target == currentMove - 1) {
13547                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13548             }
13549             if (appData.highlightLastMove) {
13550                 SetHighlights(fromX, fromY, toX, toY);
13551             }
13552         }
13553     }
13554     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13555         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13556         while (currentMove > target) {
13557             SendToProgram("undo\n", &first);
13558             currentMove--;
13559         }
13560     } else {
13561         currentMove = target;
13562     }
13563
13564     if (gameMode == EditGame || gameMode == EndOfGame) {
13565         whiteTimeRemaining = timeRemaining[0][currentMove];
13566         blackTimeRemaining = timeRemaining[1][currentMove];
13567     }
13568     DisplayBothClocks();
13569     DisplayMove(currentMove - 1);
13570     DrawPosition(full_redraw, boards[currentMove]);
13571     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13572     // [HGM] PV info: routine tests if comment empty
13573     DisplayComment(currentMove - 1, commentList[currentMove]);
13574 }
13575
13576 void
13577 BackwardEvent()
13578 {
13579     if (gameMode == IcsExamining && !pausing) {
13580         SendToICS(ics_prefix);
13581         SendToICS("backward\n");
13582     } else {
13583         BackwardInner(currentMove - 1);
13584     }
13585 }
13586
13587 void
13588 ToStartEvent()
13589 {
13590     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13591         /* to optimize, we temporarily turn off analysis mode while we undo
13592          * all the moves. Otherwise we get analysis output after each undo.
13593          */
13594         if (first.analysisSupport) {
13595           SendToProgram("exit\nforce\n", &first);
13596           first.analyzing = FALSE;
13597         }
13598     }
13599
13600     if (gameMode == IcsExamining && !pausing) {
13601         SendToICS(ics_prefix);
13602         SendToICS("backward 999999\n");
13603     } else {
13604         BackwardInner(backwardMostMove);
13605     }
13606
13607     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13608         /* we have fed all the moves, so reactivate analysis mode */
13609         SendToProgram("analyze\n", &first);
13610         first.analyzing = TRUE;
13611         /*first.maybeThinking = TRUE;*/
13612         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13613     }
13614 }
13615
13616 void
13617 ToNrEvent(int to)
13618 {
13619   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13620   if (to >= forwardMostMove) to = forwardMostMove;
13621   if (to <= backwardMostMove) to = backwardMostMove;
13622   if (to < currentMove) {
13623     BackwardInner(to);
13624   } else {
13625     ForwardInner(to);
13626   }
13627 }
13628
13629 void
13630 RevertEvent(Boolean annotate)
13631 {
13632     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13633         return;
13634     }
13635     if (gameMode != IcsExamining) {
13636         DisplayError(_("You are not examining a game"), 0);
13637         return;
13638     }
13639     if (pausing) {
13640         DisplayError(_("You can't revert while pausing"), 0);
13641         return;
13642     }
13643     SendToICS(ics_prefix);
13644     SendToICS("revert\n");
13645 }
13646
13647 void
13648 RetractMoveEvent()
13649 {
13650     switch (gameMode) {
13651       case MachinePlaysWhite:
13652       case MachinePlaysBlack:
13653         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13654             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13655             return;
13656         }
13657         if (forwardMostMove < 2) return;
13658         currentMove = forwardMostMove = forwardMostMove - 2;
13659         whiteTimeRemaining = timeRemaining[0][currentMove];
13660         blackTimeRemaining = timeRemaining[1][currentMove];
13661         DisplayBothClocks();
13662         DisplayMove(currentMove - 1);
13663         ClearHighlights();/*!! could figure this out*/
13664         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13665         SendToProgram("remove\n", &first);
13666         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13667         break;
13668
13669       case BeginningOfGame:
13670       default:
13671         break;
13672
13673       case IcsPlayingWhite:
13674       case IcsPlayingBlack:
13675         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13676             SendToICS(ics_prefix);
13677             SendToICS("takeback 2\n");
13678         } else {
13679             SendToICS(ics_prefix);
13680             SendToICS("takeback 1\n");
13681         }
13682         break;
13683     }
13684 }
13685
13686 void
13687 MoveNowEvent()
13688 {
13689     ChessProgramState *cps;
13690
13691     switch (gameMode) {
13692       case MachinePlaysWhite:
13693         if (!WhiteOnMove(forwardMostMove)) {
13694             DisplayError(_("It is your turn"), 0);
13695             return;
13696         }
13697         cps = &first;
13698         break;
13699       case MachinePlaysBlack:
13700         if (WhiteOnMove(forwardMostMove)) {
13701             DisplayError(_("It is your turn"), 0);
13702             return;
13703         }
13704         cps = &first;
13705         break;
13706       case TwoMachinesPlay:
13707         if (WhiteOnMove(forwardMostMove) ==
13708             (first.twoMachinesColor[0] == 'w')) {
13709             cps = &first;
13710         } else {
13711             cps = &second;
13712         }
13713         break;
13714       case BeginningOfGame:
13715       default:
13716         return;
13717     }
13718     SendToProgram("?\n", cps);
13719 }
13720
13721 void
13722 TruncateGameEvent()
13723 {
13724     EditGameEvent();
13725     if (gameMode != EditGame) return;
13726     TruncateGame();
13727 }
13728
13729 void
13730 TruncateGame()
13731 {
13732     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13733     if (forwardMostMove > currentMove) {
13734         if (gameInfo.resultDetails != NULL) {
13735             free(gameInfo.resultDetails);
13736             gameInfo.resultDetails = NULL;
13737             gameInfo.result = GameUnfinished;
13738         }
13739         forwardMostMove = currentMove;
13740         HistorySet(parseList, backwardMostMove, forwardMostMove,
13741                    currentMove-1);
13742     }
13743 }
13744
13745 void
13746 HintEvent()
13747 {
13748     if (appData.noChessProgram) return;
13749     switch (gameMode) {
13750       case MachinePlaysWhite:
13751         if (WhiteOnMove(forwardMostMove)) {
13752             DisplayError(_("Wait until your turn"), 0);
13753             return;
13754         }
13755         break;
13756       case BeginningOfGame:
13757       case MachinePlaysBlack:
13758         if (!WhiteOnMove(forwardMostMove)) {
13759             DisplayError(_("Wait until your turn"), 0);
13760             return;
13761         }
13762         break;
13763       default:
13764         DisplayError(_("No hint available"), 0);
13765         return;
13766     }
13767     SendToProgram("hint\n", &first);
13768     hintRequested = TRUE;
13769 }
13770
13771 void
13772 BookEvent()
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       case EditPosition:
13790         EditPositionDone(TRUE);
13791         break;
13792       case TwoMachinesPlay:
13793         return;
13794       default:
13795         break;
13796     }
13797     SendToProgram("bk\n", &first);
13798     bookOutput[0] = NULLCHAR;
13799     bookRequested = TRUE;
13800 }
13801
13802 void
13803 AboutGameEvent()
13804 {
13805     char *tags = PGNTags(&gameInfo);
13806     TagsPopUp(tags, CmailMsg());
13807     free(tags);
13808 }
13809
13810 /* end button procedures */
13811
13812 void
13813 PrintPosition(fp, move)
13814      FILE *fp;
13815      int move;
13816 {
13817     int i, j;
13818
13819     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13820         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13821             char c = PieceToChar(boards[move][i][j]);
13822             fputc(c == 'x' ? '.' : c, fp);
13823             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13824         }
13825     }
13826     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13827       fprintf(fp, "white to play\n");
13828     else
13829       fprintf(fp, "black to play\n");
13830 }
13831
13832 void
13833 PrintOpponents(fp)
13834      FILE *fp;
13835 {
13836     if (gameInfo.white != NULL) {
13837         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13838     } else {
13839         fprintf(fp, "\n");
13840     }
13841 }
13842
13843 /* Find last component of program's own name, using some heuristics */
13844 void
13845 TidyProgramName(prog, host, buf)
13846      char *prog, *host, buf[MSG_SIZ];
13847 {
13848     char *p, *q;
13849     int local = (strcmp(host, "localhost") == 0);
13850     while (!local && (p = strchr(prog, ';')) != NULL) {
13851         p++;
13852         while (*p == ' ') p++;
13853         prog = p;
13854     }
13855     if (*prog == '"' || *prog == '\'') {
13856         q = strchr(prog + 1, *prog);
13857     } else {
13858         q = strchr(prog, ' ');
13859     }
13860     if (q == NULL) q = prog + strlen(prog);
13861     p = q;
13862     while (p >= prog && *p != '/' && *p != '\\') p--;
13863     p++;
13864     if(p == prog && *p == '"') p++;
13865     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13866     memcpy(buf, p, q - p);
13867     buf[q - p] = NULLCHAR;
13868     if (!local) {
13869         strcat(buf, "@");
13870         strcat(buf, host);
13871     }
13872 }
13873
13874 char *
13875 TimeControlTagValue()
13876 {
13877     char buf[MSG_SIZ];
13878     if (!appData.clockMode) {
13879       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13880     } else if (movesPerSession > 0) {
13881       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13882     } else if (timeIncrement == 0) {
13883       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13884     } else {
13885       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13886     }
13887     return StrSave(buf);
13888 }
13889
13890 void
13891 SetGameInfo()
13892 {
13893     /* This routine is used only for certain modes */
13894     VariantClass v = gameInfo.variant;
13895     ChessMove r = GameUnfinished;
13896     char *p = NULL;
13897
13898     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13899         r = gameInfo.result;
13900         p = gameInfo.resultDetails;
13901         gameInfo.resultDetails = NULL;
13902     }
13903     ClearGameInfo(&gameInfo);
13904     gameInfo.variant = v;
13905
13906     switch (gameMode) {
13907       case MachinePlaysWhite:
13908         gameInfo.event = StrSave( appData.pgnEventHeader );
13909         gameInfo.site = StrSave(HostName());
13910         gameInfo.date = PGNDate();
13911         gameInfo.round = StrSave("-");
13912         gameInfo.white = StrSave(first.tidy);
13913         gameInfo.black = StrSave(UserName());
13914         gameInfo.timeControl = TimeControlTagValue();
13915         break;
13916
13917       case MachinePlaysBlack:
13918         gameInfo.event = StrSave( appData.pgnEventHeader );
13919         gameInfo.site = StrSave(HostName());
13920         gameInfo.date = PGNDate();
13921         gameInfo.round = StrSave("-");
13922         gameInfo.white = StrSave(UserName());
13923         gameInfo.black = StrSave(first.tidy);
13924         gameInfo.timeControl = TimeControlTagValue();
13925         break;
13926
13927       case TwoMachinesPlay:
13928         gameInfo.event = StrSave( appData.pgnEventHeader );
13929         gameInfo.site = StrSave(HostName());
13930         gameInfo.date = PGNDate();
13931         if (roundNr > 0) {
13932             char buf[MSG_SIZ];
13933             snprintf(buf, MSG_SIZ, "%d", roundNr);
13934             gameInfo.round = StrSave(buf);
13935         } else {
13936             gameInfo.round = StrSave("-");
13937         }
13938         if (first.twoMachinesColor[0] == 'w') {
13939             gameInfo.white = StrSave(first.tidy);
13940             gameInfo.black = StrSave(second.tidy);
13941         } else {
13942             gameInfo.white = StrSave(second.tidy);
13943             gameInfo.black = StrSave(first.tidy);
13944         }
13945         gameInfo.timeControl = TimeControlTagValue();
13946         break;
13947
13948       case EditGame:
13949         gameInfo.event = StrSave("Edited game");
13950         gameInfo.site = StrSave(HostName());
13951         gameInfo.date = PGNDate();
13952         gameInfo.round = StrSave("-");
13953         gameInfo.white = StrSave("-");
13954         gameInfo.black = StrSave("-");
13955         gameInfo.result = r;
13956         gameInfo.resultDetails = p;
13957         break;
13958
13959       case EditPosition:
13960         gameInfo.event = StrSave("Edited position");
13961         gameInfo.site = StrSave(HostName());
13962         gameInfo.date = PGNDate();
13963         gameInfo.round = StrSave("-");
13964         gameInfo.white = StrSave("-");
13965         gameInfo.black = StrSave("-");
13966         break;
13967
13968       case IcsPlayingWhite:
13969       case IcsPlayingBlack:
13970       case IcsObserving:
13971       case IcsExamining:
13972         break;
13973
13974       case PlayFromGameFile:
13975         gameInfo.event = StrSave("Game from non-PGN file");
13976         gameInfo.site = StrSave(HostName());
13977         gameInfo.date = PGNDate();
13978         gameInfo.round = StrSave("-");
13979         gameInfo.white = StrSave("?");
13980         gameInfo.black = StrSave("?");
13981         break;
13982
13983       default:
13984         break;
13985     }
13986 }
13987
13988 void
13989 ReplaceComment(index, text)
13990      int index;
13991      char *text;
13992 {
13993     int len;
13994     char *p;
13995     float score;
13996
13997     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13998        pvInfoList[index-1].depth == len &&
13999        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14000        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14001     while (*text == '\n') text++;
14002     len = strlen(text);
14003     while (len > 0 && text[len - 1] == '\n') len--;
14004
14005     if (commentList[index] != NULL)
14006       free(commentList[index]);
14007
14008     if (len == 0) {
14009         commentList[index] = NULL;
14010         return;
14011     }
14012   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14013       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14014       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14015     commentList[index] = (char *) malloc(len + 2);
14016     strncpy(commentList[index], text, len);
14017     commentList[index][len] = '\n';
14018     commentList[index][len + 1] = NULLCHAR;
14019   } else {
14020     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14021     char *p;
14022     commentList[index] = (char *) malloc(len + 7);
14023     safeStrCpy(commentList[index], "{\n", 3);
14024     safeStrCpy(commentList[index]+2, text, len+1);
14025     commentList[index][len+2] = NULLCHAR;
14026     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14027     strcat(commentList[index], "\n}\n");
14028   }
14029 }
14030
14031 void
14032 CrushCRs(text)
14033      char *text;
14034 {
14035   char *p = text;
14036   char *q = text;
14037   char ch;
14038
14039   do {
14040     ch = *p++;
14041     if (ch == '\r') continue;
14042     *q++ = ch;
14043   } while (ch != '\0');
14044 }
14045
14046 void
14047 AppendComment(index, text, addBraces)
14048      int index;
14049      char *text;
14050      Boolean addBraces; // [HGM] braces: tells if we should add {}
14051 {
14052     int oldlen, len;
14053     char *old;
14054
14055 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14056     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14057
14058     CrushCRs(text);
14059     while (*text == '\n') text++;
14060     len = strlen(text);
14061     while (len > 0 && text[len - 1] == '\n') len--;
14062
14063     if (len == 0) return;
14064
14065     if (commentList[index] != NULL) {
14066         old = commentList[index];
14067         oldlen = strlen(old);
14068         while(commentList[index][oldlen-1] ==  '\n')
14069           commentList[index][--oldlen] = NULLCHAR;
14070         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14071         safeStrCpy(commentList[index], old, oldlen + len + 6);
14072         free(old);
14073         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14074         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14075           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14076           while (*text == '\n') { text++; len--; }
14077           commentList[index][--oldlen] = NULLCHAR;
14078       }
14079         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14080         else          strcat(commentList[index], "\n");
14081         strcat(commentList[index], text);
14082         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14083         else          strcat(commentList[index], "\n");
14084     } else {
14085         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14086         if(addBraces)
14087           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14088         else commentList[index][0] = NULLCHAR;
14089         strcat(commentList[index], text);
14090         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14091         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14092     }
14093 }
14094
14095 static char * FindStr( char * text, char * sub_text )
14096 {
14097     char * result = strstr( text, sub_text );
14098
14099     if( result != NULL ) {
14100         result += strlen( sub_text );
14101     }
14102
14103     return result;
14104 }
14105
14106 /* [AS] Try to extract PV info from PGN comment */
14107 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14108 char *GetInfoFromComment( int index, char * text )
14109 {
14110     char * sep = text, *p;
14111
14112     if( text != NULL && index > 0 ) {
14113         int score = 0;
14114         int depth = 0;
14115         int time = -1, sec = 0, deci;
14116         char * s_eval = FindStr( text, "[%eval " );
14117         char * s_emt = FindStr( text, "[%emt " );
14118
14119         if( s_eval != NULL || s_emt != NULL ) {
14120             /* New style */
14121             char delim;
14122
14123             if( s_eval != NULL ) {
14124                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14125                     return text;
14126                 }
14127
14128                 if( delim != ']' ) {
14129                     return text;
14130                 }
14131             }
14132
14133             if( s_emt != NULL ) {
14134             }
14135                 return text;
14136         }
14137         else {
14138             /* We expect something like: [+|-]nnn.nn/dd */
14139             int score_lo = 0;
14140
14141             if(*text != '{') return text; // [HGM] braces: must be normal comment
14142
14143             sep = strchr( text, '/' );
14144             if( sep == NULL || sep < (text+4) ) {
14145                 return text;
14146             }
14147
14148             p = text;
14149             if(p[1] == '(') { // comment starts with PV
14150                p = strchr(p, ')'); // locate end of PV
14151                if(p == NULL || sep < p+5) return text;
14152                // at this point we have something like "{(.*) +0.23/6 ..."
14153                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14154                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14155                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14156             }
14157             time = -1; sec = -1; deci = -1;
14158             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14159                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14160                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14161                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14162                 return text;
14163             }
14164
14165             if( score_lo < 0 || score_lo >= 100 ) {
14166                 return text;
14167             }
14168
14169             if(sec >= 0) time = 600*time + 10*sec; else
14170             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14171
14172             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14173
14174             /* [HGM] PV time: now locate end of PV info */
14175             while( *++sep >= '0' && *sep <= '9'); // strip depth
14176             if(time >= 0)
14177             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14178             if(sec >= 0)
14179             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14180             if(deci >= 0)
14181             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14182             while(*sep == ' ') sep++;
14183         }
14184
14185         if( depth <= 0 ) {
14186             return text;
14187         }
14188
14189         if( time < 0 ) {
14190             time = -1;
14191         }
14192
14193         pvInfoList[index-1].depth = depth;
14194         pvInfoList[index-1].score = score;
14195         pvInfoList[index-1].time  = 10*time; // centi-sec
14196         if(*sep == '}') *sep = 0; else *--sep = '{';
14197         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14198     }
14199     return sep;
14200 }
14201
14202 void
14203 SendToProgram(message, cps)
14204      char *message;
14205      ChessProgramState *cps;
14206 {
14207     int count, outCount, error;
14208     char buf[MSG_SIZ];
14209
14210     if (cps->pr == NULL) return;
14211     Attention(cps);
14212
14213     if (appData.debugMode) {
14214         TimeMark now;
14215         GetTimeMark(&now);
14216         fprintf(debugFP, "%ld >%-6s: %s",
14217                 SubtractTimeMarks(&now, &programStartTime),
14218                 cps->which, message);
14219     }
14220
14221     count = strlen(message);
14222     outCount = OutputToProcess(cps->pr, message, count, &error);
14223     if (outCount < count && !exiting
14224                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14225       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14226       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14227         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14228             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14229                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14230                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14231                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14232             } else {
14233                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14234                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14235                 gameInfo.result = res;
14236             }
14237             gameInfo.resultDetails = StrSave(buf);
14238         }
14239         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14240         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14241     }
14242 }
14243
14244 void
14245 ReceiveFromProgram(isr, closure, message, count, error)
14246      InputSourceRef isr;
14247      VOIDSTAR closure;
14248      char *message;
14249      int count;
14250      int error;
14251 {
14252     char *end_str;
14253     char buf[MSG_SIZ];
14254     ChessProgramState *cps = (ChessProgramState *)closure;
14255
14256     if (isr != cps->isr) return; /* Killed intentionally */
14257     if (count <= 0) {
14258         if (count == 0) {
14259             RemoveInputSource(cps->isr);
14260             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14261                     _(cps->which), cps->program);
14262         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14263                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14264                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14265                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14266                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14267                 } else {
14268                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14269                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14270                     gameInfo.result = res;
14271                 }
14272                 gameInfo.resultDetails = StrSave(buf);
14273             }
14274             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14275             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14276         } else {
14277             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14278                     _(cps->which), cps->program);
14279             RemoveInputSource(cps->isr);
14280
14281             /* [AS] Program is misbehaving badly... kill it */
14282             if( count == -2 ) {
14283                 DestroyChildProcess( cps->pr, 9 );
14284                 cps->pr = NoProc;
14285             }
14286
14287             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14288         }
14289         return;
14290     }
14291
14292     if ((end_str = strchr(message, '\r')) != NULL)
14293       *end_str = NULLCHAR;
14294     if ((end_str = strchr(message, '\n')) != NULL)
14295       *end_str = NULLCHAR;
14296
14297     if (appData.debugMode) {
14298         TimeMark now; int print = 1;
14299         char *quote = ""; char c; int i;
14300
14301         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14302                 char start = message[0];
14303                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14304                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14305                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14306                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14307                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14308                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14309                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14310                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14311                    sscanf(message, "hint: %c", &c)!=1 && 
14312                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14313                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14314                     print = (appData.engineComments >= 2);
14315                 }
14316                 message[0] = start; // restore original message
14317         }
14318         if(print) {
14319                 GetTimeMark(&now);
14320                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14321                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14322                         quote,
14323                         message);
14324         }
14325     }
14326
14327     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14328     if (appData.icsEngineAnalyze) {
14329         if (strstr(message, "whisper") != NULL ||
14330              strstr(message, "kibitz") != NULL ||
14331             strstr(message, "tellics") != NULL) return;
14332     }
14333
14334     HandleMachineMove(message, cps);
14335 }
14336
14337
14338 void
14339 SendTimeControl(cps, mps, tc, inc, sd, st)
14340      ChessProgramState *cps;
14341      int mps, inc, sd, st;
14342      long tc;
14343 {
14344     char buf[MSG_SIZ];
14345     int seconds;
14346
14347     if( timeControl_2 > 0 ) {
14348         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14349             tc = timeControl_2;
14350         }
14351     }
14352     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14353     inc /= cps->timeOdds;
14354     st  /= cps->timeOdds;
14355
14356     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14357
14358     if (st > 0) {
14359       /* Set exact time per move, normally using st command */
14360       if (cps->stKludge) {
14361         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14362         seconds = st % 60;
14363         if (seconds == 0) {
14364           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14365         } else {
14366           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14367         }
14368       } else {
14369         snprintf(buf, MSG_SIZ, "st %d\n", st);
14370       }
14371     } else {
14372       /* Set conventional or incremental time control, using level command */
14373       if (seconds == 0) {
14374         /* Note old gnuchess bug -- minutes:seconds used to not work.
14375            Fixed in later versions, but still avoid :seconds
14376            when seconds is 0. */
14377         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14378       } else {
14379         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14380                  seconds, inc/1000.);
14381       }
14382     }
14383     SendToProgram(buf, cps);
14384
14385     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14386     /* Orthogonally, limit search to given depth */
14387     if (sd > 0) {
14388       if (cps->sdKludge) {
14389         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14390       } else {
14391         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14392       }
14393       SendToProgram(buf, cps);
14394     }
14395
14396     if(cps->nps >= 0) { /* [HGM] nps */
14397         if(cps->supportsNPS == FALSE)
14398           cps->nps = -1; // don't use if engine explicitly says not supported!
14399         else {
14400           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14401           SendToProgram(buf, cps);
14402         }
14403     }
14404 }
14405
14406 ChessProgramState *WhitePlayer()
14407 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14408 {
14409     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14410        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14411         return &second;
14412     return &first;
14413 }
14414
14415 void
14416 SendTimeRemaining(cps, machineWhite)
14417      ChessProgramState *cps;
14418      int /*boolean*/ machineWhite;
14419 {
14420     char message[MSG_SIZ];
14421     long time, otime;
14422
14423     /* Note: this routine must be called when the clocks are stopped
14424        or when they have *just* been set or switched; otherwise
14425        it will be off by the time since the current tick started.
14426     */
14427     if (machineWhite) {
14428         time = whiteTimeRemaining / 10;
14429         otime = blackTimeRemaining / 10;
14430     } else {
14431         time = blackTimeRemaining / 10;
14432         otime = whiteTimeRemaining / 10;
14433     }
14434     /* [HGM] translate opponent's time by time-odds factor */
14435     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14436     if (appData.debugMode) {
14437         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14438     }
14439
14440     if (time <= 0) time = 1;
14441     if (otime <= 0) otime = 1;
14442
14443     snprintf(message, MSG_SIZ, "time %ld\n", time);
14444     SendToProgram(message, cps);
14445
14446     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14447     SendToProgram(message, cps);
14448 }
14449
14450 int
14451 BoolFeature(p, name, loc, cps)
14452      char **p;
14453      char *name;
14454      int *loc;
14455      ChessProgramState *cps;
14456 {
14457   char buf[MSG_SIZ];
14458   int len = strlen(name);
14459   int val;
14460
14461   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14462     (*p) += len + 1;
14463     sscanf(*p, "%d", &val);
14464     *loc = (val != 0);
14465     while (**p && **p != ' ')
14466       (*p)++;
14467     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14468     SendToProgram(buf, cps);
14469     return TRUE;
14470   }
14471   return FALSE;
14472 }
14473
14474 int
14475 IntFeature(p, name, loc, cps)
14476      char **p;
14477      char *name;
14478      int *loc;
14479      ChessProgramState *cps;
14480 {
14481   char buf[MSG_SIZ];
14482   int len = strlen(name);
14483   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14484     (*p) += len + 1;
14485     sscanf(*p, "%d", loc);
14486     while (**p && **p != ' ') (*p)++;
14487     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14488     SendToProgram(buf, cps);
14489     return TRUE;
14490   }
14491   return FALSE;
14492 }
14493
14494 int
14495 StringFeature(p, name, loc, cps)
14496      char **p;
14497      char *name;
14498      char loc[];
14499      ChessProgramState *cps;
14500 {
14501   char buf[MSG_SIZ];
14502   int len = strlen(name);
14503   if (strncmp((*p), name, len) == 0
14504       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14505     (*p) += len + 2;
14506     sscanf(*p, "%[^\"]", loc);
14507     while (**p && **p != '\"') (*p)++;
14508     if (**p == '\"') (*p)++;
14509     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14510     SendToProgram(buf, cps);
14511     return TRUE;
14512   }
14513   return FALSE;
14514 }
14515
14516 int
14517 ParseOption(Option *opt, ChessProgramState *cps)
14518 // [HGM] options: process the string that defines an engine option, and determine
14519 // name, type, default value, and allowed value range
14520 {
14521         char *p, *q, buf[MSG_SIZ];
14522         int n, min = (-1)<<31, max = 1<<31, def;
14523
14524         if(p = strstr(opt->name, " -spin ")) {
14525             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14526             if(max < min) max = min; // enforce consistency
14527             if(def < min) def = min;
14528             if(def > max) def = max;
14529             opt->value = def;
14530             opt->min = min;
14531             opt->max = max;
14532             opt->type = Spin;
14533         } else if((p = strstr(opt->name, " -slider "))) {
14534             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14535             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14536             if(max < min) max = min; // enforce consistency
14537             if(def < min) def = min;
14538             if(def > max) def = max;
14539             opt->value = def;
14540             opt->min = min;
14541             opt->max = max;
14542             opt->type = Spin; // Slider;
14543         } else if((p = strstr(opt->name, " -string "))) {
14544             opt->textValue = p+9;
14545             opt->type = TextBox;
14546         } else if((p = strstr(opt->name, " -file "))) {
14547             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14548             opt->textValue = p+7;
14549             opt->type = FileName; // FileName;
14550         } else if((p = strstr(opt->name, " -path "))) {
14551             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14552             opt->textValue = p+7;
14553             opt->type = PathName; // PathName;
14554         } else if(p = strstr(opt->name, " -check ")) {
14555             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14556             opt->value = (def != 0);
14557             opt->type = CheckBox;
14558         } else if(p = strstr(opt->name, " -combo ")) {
14559             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14560             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14561             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14562             opt->value = n = 0;
14563             while(q = StrStr(q, " /// ")) {
14564                 n++; *q = 0;    // count choices, and null-terminate each of them
14565                 q += 5;
14566                 if(*q == '*') { // remember default, which is marked with * prefix
14567                     q++;
14568                     opt->value = n;
14569                 }
14570                 cps->comboList[cps->comboCnt++] = q;
14571             }
14572             cps->comboList[cps->comboCnt++] = NULL;
14573             opt->max = n + 1;
14574             opt->type = ComboBox;
14575         } else if(p = strstr(opt->name, " -button")) {
14576             opt->type = Button;
14577         } else if(p = strstr(opt->name, " -save")) {
14578             opt->type = SaveButton;
14579         } else return FALSE;
14580         *p = 0; // terminate option name
14581         // now look if the command-line options define a setting for this engine option.
14582         if(cps->optionSettings && cps->optionSettings[0])
14583             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14584         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14585           snprintf(buf, MSG_SIZ, "option %s", p);
14586                 if(p = strstr(buf, ",")) *p = 0;
14587                 if(q = strchr(buf, '=')) switch(opt->type) {
14588                     case ComboBox:
14589                         for(n=0; n<opt->max; n++)
14590                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14591                         break;
14592                     case TextBox:
14593                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14594                         break;
14595                     case Spin:
14596                     case CheckBox:
14597                         opt->value = atoi(q+1);
14598                     default:
14599                         break;
14600                 }
14601                 strcat(buf, "\n");
14602                 SendToProgram(buf, cps);
14603         }
14604         return TRUE;
14605 }
14606
14607 void
14608 FeatureDone(cps, val)
14609      ChessProgramState* cps;
14610      int val;
14611 {
14612   DelayedEventCallback cb = GetDelayedEvent();
14613   if ((cb == InitBackEnd3 && cps == &first) ||
14614       (cb == SettingsMenuIfReady && cps == &second) ||
14615       (cb == LoadEngine) ||
14616       (cb == TwoMachinesEventIfReady)) {
14617     CancelDelayedEvent();
14618     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14619   }
14620   cps->initDone = val;
14621 }
14622
14623 /* Parse feature command from engine */
14624 void
14625 ParseFeatures(args, cps)
14626      char* args;
14627      ChessProgramState *cps;
14628 {
14629   char *p = args;
14630   char *q;
14631   int val;
14632   char buf[MSG_SIZ];
14633
14634   for (;;) {
14635     while (*p == ' ') p++;
14636     if (*p == NULLCHAR) return;
14637
14638     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14639     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14640     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14641     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14642     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14643     if (BoolFeature(&p, "reuse", &val, cps)) {
14644       /* Engine can disable reuse, but can't enable it if user said no */
14645       if (!val) cps->reuse = FALSE;
14646       continue;
14647     }
14648     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14649     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14650       if (gameMode == TwoMachinesPlay) {
14651         DisplayTwoMachinesTitle();
14652       } else {
14653         DisplayTitle("");
14654       }
14655       continue;
14656     }
14657     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14658     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14659     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14660     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14661     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14662     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14663     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14664     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14665     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14666     if (IntFeature(&p, "done", &val, cps)) {
14667       FeatureDone(cps, val);
14668       continue;
14669     }
14670     /* Added by Tord: */
14671     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14672     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14673     /* End of additions by Tord */
14674
14675     /* [HGM] added features: */
14676     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14677     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14678     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14679     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14680     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14681     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14682     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14683         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14684           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14685             SendToProgram(buf, cps);
14686             continue;
14687         }
14688         if(cps->nrOptions >= MAX_OPTIONS) {
14689             cps->nrOptions--;
14690             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14691             DisplayError(buf, 0);
14692         }
14693         continue;
14694     }
14695     /* End of additions by HGM */
14696
14697     /* unknown feature: complain and skip */
14698     q = p;
14699     while (*q && *q != '=') q++;
14700     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14701     SendToProgram(buf, cps);
14702     p = q;
14703     if (*p == '=') {
14704       p++;
14705       if (*p == '\"') {
14706         p++;
14707         while (*p && *p != '\"') p++;
14708         if (*p == '\"') p++;
14709       } else {
14710         while (*p && *p != ' ') p++;
14711       }
14712     }
14713   }
14714
14715 }
14716
14717 void
14718 PeriodicUpdatesEvent(newState)
14719      int newState;
14720 {
14721     if (newState == appData.periodicUpdates)
14722       return;
14723
14724     appData.periodicUpdates=newState;
14725
14726     /* Display type changes, so update it now */
14727 //    DisplayAnalysis();
14728
14729     /* Get the ball rolling again... */
14730     if (newState) {
14731         AnalysisPeriodicEvent(1);
14732         StartAnalysisClock();
14733     }
14734 }
14735
14736 void
14737 PonderNextMoveEvent(newState)
14738      int newState;
14739 {
14740     if (newState == appData.ponderNextMove) return;
14741     if (gameMode == EditPosition) EditPositionDone(TRUE);
14742     if (newState) {
14743         SendToProgram("hard\n", &first);
14744         if (gameMode == TwoMachinesPlay) {
14745             SendToProgram("hard\n", &second);
14746         }
14747     } else {
14748         SendToProgram("easy\n", &first);
14749         thinkOutput[0] = NULLCHAR;
14750         if (gameMode == TwoMachinesPlay) {
14751             SendToProgram("easy\n", &second);
14752         }
14753     }
14754     appData.ponderNextMove = newState;
14755 }
14756
14757 void
14758 NewSettingEvent(option, feature, command, value)
14759      char *command;
14760      int option, value, *feature;
14761 {
14762     char buf[MSG_SIZ];
14763
14764     if (gameMode == EditPosition) EditPositionDone(TRUE);
14765     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14766     if(feature == NULL || *feature) SendToProgram(buf, &first);
14767     if (gameMode == TwoMachinesPlay) {
14768         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14769     }
14770 }
14771
14772 void
14773 ShowThinkingEvent()
14774 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14775 {
14776     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14777     int newState = appData.showThinking
14778         // [HGM] thinking: other features now need thinking output as well
14779         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14780
14781     if (oldState == newState) return;
14782     oldState = newState;
14783     if (gameMode == EditPosition) EditPositionDone(TRUE);
14784     if (oldState) {
14785         SendToProgram("post\n", &first);
14786         if (gameMode == TwoMachinesPlay) {
14787             SendToProgram("post\n", &second);
14788         }
14789     } else {
14790         SendToProgram("nopost\n", &first);
14791         thinkOutput[0] = NULLCHAR;
14792         if (gameMode == TwoMachinesPlay) {
14793             SendToProgram("nopost\n", &second);
14794         }
14795     }
14796 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14797 }
14798
14799 void
14800 AskQuestionEvent(title, question, replyPrefix, which)
14801      char *title; char *question; char *replyPrefix; char *which;
14802 {
14803   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14804   if (pr == NoProc) return;
14805   AskQuestion(title, question, replyPrefix, pr);
14806 }
14807
14808 void
14809 TypeInEvent(char firstChar)
14810 {
14811     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14812         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14813         gameMode == AnalyzeMode || gameMode == EditGame || \r
14814         gameMode == EditPosition || gameMode == IcsExamining ||\r
14815         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14816         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14817                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14818                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14819         gameMode == Training) PopUpMoveDialog(firstChar);
14820 }
14821
14822 void
14823 TypeInDoneEvent(char *move)
14824 {
14825         Board board;
14826         int n, fromX, fromY, toX, toY;
14827         char promoChar;
14828         ChessMove moveType;\r
14829
14830         // [HGM] FENedit\r
14831         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14832                 EditPositionPasteFEN(move);\r
14833                 return;\r
14834         }\r
14835         // [HGM] movenum: allow move number to be typed in any mode\r
14836         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14837           ToNrEvent(2*n-1);\r
14838           return;\r
14839         }\r
14840
14841       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14842         gameMode != Training) {\r
14843         DisplayMoveError(_("Displayed move is not current"));\r
14844       } else {\r
14845         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14846           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14847         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14848         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14849           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14850           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14851         } else {\r
14852           DisplayMoveError(_("Could not parse move"));\r
14853         }
14854       }\r
14855 }\r
14856
14857 void
14858 DisplayMove(moveNumber)
14859      int moveNumber;
14860 {
14861     char message[MSG_SIZ];
14862     char res[MSG_SIZ];
14863     char cpThinkOutput[MSG_SIZ];
14864
14865     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14866
14867     if (moveNumber == forwardMostMove - 1 ||
14868         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14869
14870         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14871
14872         if (strchr(cpThinkOutput, '\n')) {
14873             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14874         }
14875     } else {
14876         *cpThinkOutput = NULLCHAR;
14877     }
14878
14879     /* [AS] Hide thinking from human user */
14880     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14881         *cpThinkOutput = NULLCHAR;
14882         if( thinkOutput[0] != NULLCHAR ) {
14883             int i;
14884
14885             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14886                 cpThinkOutput[i] = '.';
14887             }
14888             cpThinkOutput[i] = NULLCHAR;
14889             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14890         }
14891     }
14892
14893     if (moveNumber == forwardMostMove - 1 &&
14894         gameInfo.resultDetails != NULL) {
14895         if (gameInfo.resultDetails[0] == NULLCHAR) {
14896           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14897         } else {
14898           snprintf(res, MSG_SIZ, " {%s} %s",
14899                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14900         }
14901     } else {
14902         res[0] = NULLCHAR;
14903     }
14904
14905     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14906         DisplayMessage(res, cpThinkOutput);
14907     } else {
14908       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14909                 WhiteOnMove(moveNumber) ? " " : ".. ",
14910                 parseList[moveNumber], res);
14911         DisplayMessage(message, cpThinkOutput);
14912     }
14913 }
14914
14915 void
14916 DisplayComment(moveNumber, text)
14917      int moveNumber;
14918      char *text;
14919 {
14920     char title[MSG_SIZ];
14921     char buf[8000]; // comment can be long!
14922     int score, depth;
14923
14924     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14925       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14926     } else {
14927       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14928               WhiteOnMove(moveNumber) ? " " : ".. ",
14929               parseList[moveNumber]);
14930     }
14931     // [HGM] PV info: display PV info together with (or as) comment
14932     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14933       if(text == NULL) text = "";
14934       score = pvInfoList[moveNumber].score;
14935       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14936               depth, (pvInfoList[moveNumber].time+50)/100, text);
14937       text = buf;
14938     }
14939     if (text != NULL && (appData.autoDisplayComment || commentUp))
14940         CommentPopUp(title, text);
14941 }
14942
14943 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14944  * might be busy thinking or pondering.  It can be omitted if your
14945  * gnuchess is configured to stop thinking immediately on any user
14946  * input.  However, that gnuchess feature depends on the FIONREAD
14947  * ioctl, which does not work properly on some flavors of Unix.
14948  */
14949 void
14950 Attention(cps)
14951      ChessProgramState *cps;
14952 {
14953 #if ATTENTION
14954     if (!cps->useSigint) return;
14955     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14956     switch (gameMode) {
14957       case MachinePlaysWhite:
14958       case MachinePlaysBlack:
14959       case TwoMachinesPlay:
14960       case IcsPlayingWhite:
14961       case IcsPlayingBlack:
14962       case AnalyzeMode:
14963       case AnalyzeFile:
14964         /* Skip if we know it isn't thinking */
14965         if (!cps->maybeThinking) return;
14966         if (appData.debugMode)
14967           fprintf(debugFP, "Interrupting %s\n", cps->which);
14968         InterruptChildProcess(cps->pr);
14969         cps->maybeThinking = FALSE;
14970         break;
14971       default:
14972         break;
14973     }
14974 #endif /*ATTENTION*/
14975 }
14976
14977 int
14978 CheckFlags()
14979 {
14980     if (whiteTimeRemaining <= 0) {
14981         if (!whiteFlag) {
14982             whiteFlag = TRUE;
14983             if (appData.icsActive) {
14984                 if (appData.autoCallFlag &&
14985                     gameMode == IcsPlayingBlack && !blackFlag) {
14986                   SendToICS(ics_prefix);
14987                   SendToICS("flag\n");
14988                 }
14989             } else {
14990                 if (blackFlag) {
14991                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14992                 } else {
14993                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14994                     if (appData.autoCallFlag) {
14995                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14996                         return TRUE;
14997                     }
14998                 }
14999             }
15000         }
15001     }
15002     if (blackTimeRemaining <= 0) {
15003         if (!blackFlag) {
15004             blackFlag = TRUE;
15005             if (appData.icsActive) {
15006                 if (appData.autoCallFlag &&
15007                     gameMode == IcsPlayingWhite && !whiteFlag) {
15008                   SendToICS(ics_prefix);
15009                   SendToICS("flag\n");
15010                 }
15011             } else {
15012                 if (whiteFlag) {
15013                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15014                 } else {
15015                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15016                     if (appData.autoCallFlag) {
15017                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15018                         return TRUE;
15019                     }
15020                 }
15021             }
15022         }
15023     }
15024     return FALSE;
15025 }
15026
15027 void
15028 CheckTimeControl()
15029 {
15030     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15031         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15032
15033     /*
15034      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15035      */
15036     if ( !WhiteOnMove(forwardMostMove) ) {
15037         /* White made time control */
15038         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15039         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15040         /* [HGM] time odds: correct new time quota for time odds! */
15041                                             / WhitePlayer()->timeOdds;
15042         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15043     } else {
15044         lastBlack -= blackTimeRemaining;
15045         /* Black made time control */
15046         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15047                                             / WhitePlayer()->other->timeOdds;
15048         lastWhite = whiteTimeRemaining;
15049     }
15050 }
15051
15052 void
15053 DisplayBothClocks()
15054 {
15055     int wom = gameMode == EditPosition ?
15056       !blackPlaysFirst : WhiteOnMove(currentMove);
15057     DisplayWhiteClock(whiteTimeRemaining, wom);
15058     DisplayBlackClock(blackTimeRemaining, !wom);
15059 }
15060
15061
15062 /* Timekeeping seems to be a portability nightmare.  I think everyone
15063    has ftime(), but I'm really not sure, so I'm including some ifdefs
15064    to use other calls if you don't.  Clocks will be less accurate if
15065    you have neither ftime nor gettimeofday.
15066 */
15067
15068 /* VS 2008 requires the #include outside of the function */
15069 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15070 #include <sys/timeb.h>
15071 #endif
15072
15073 /* Get the current time as a TimeMark */
15074 void
15075 GetTimeMark(tm)
15076      TimeMark *tm;
15077 {
15078 #if HAVE_GETTIMEOFDAY
15079
15080     struct timeval timeVal;
15081     struct timezone timeZone;
15082
15083     gettimeofday(&timeVal, &timeZone);
15084     tm->sec = (long) timeVal.tv_sec;
15085     tm->ms = (int) (timeVal.tv_usec / 1000L);
15086
15087 #else /*!HAVE_GETTIMEOFDAY*/
15088 #if HAVE_FTIME
15089
15090 // include <sys/timeb.h> / moved to just above start of function
15091     struct timeb timeB;
15092
15093     ftime(&timeB);
15094     tm->sec = (long) timeB.time;
15095     tm->ms = (int) timeB.millitm;
15096
15097 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15098     tm->sec = (long) time(NULL);
15099     tm->ms = 0;
15100 #endif
15101 #endif
15102 }
15103
15104 /* Return the difference in milliseconds between two
15105    time marks.  We assume the difference will fit in a long!
15106 */
15107 long
15108 SubtractTimeMarks(tm2, tm1)
15109      TimeMark *tm2, *tm1;
15110 {
15111     return 1000L*(tm2->sec - tm1->sec) +
15112            (long) (tm2->ms - tm1->ms);
15113 }
15114
15115
15116 /*
15117  * Code to manage the game clocks.
15118  *
15119  * In tournament play, black starts the clock and then white makes a move.
15120  * We give the human user a slight advantage if he is playing white---the
15121  * clocks don't run until he makes his first move, so it takes zero time.
15122  * Also, we don't account for network lag, so we could get out of sync
15123  * with GNU Chess's clock -- but then, referees are always right.
15124  */
15125
15126 static TimeMark tickStartTM;
15127 static long intendedTickLength;
15128
15129 long
15130 NextTickLength(timeRemaining)
15131      long timeRemaining;
15132 {
15133     long nominalTickLength, nextTickLength;
15134
15135     if (timeRemaining > 0L && timeRemaining <= 10000L)
15136       nominalTickLength = 100L;
15137     else
15138       nominalTickLength = 1000L;
15139     nextTickLength = timeRemaining % nominalTickLength;
15140     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15141
15142     return nextTickLength;
15143 }
15144
15145 /* Adjust clock one minute up or down */
15146 void
15147 AdjustClock(Boolean which, int dir)
15148 {
15149     if(which) blackTimeRemaining += 60000*dir;
15150     else      whiteTimeRemaining += 60000*dir;
15151     DisplayBothClocks();
15152 }
15153
15154 /* Stop clocks and reset to a fresh time control */
15155 void
15156 ResetClocks()
15157 {
15158     (void) StopClockTimer();
15159     if (appData.icsActive) {
15160         whiteTimeRemaining = blackTimeRemaining = 0;
15161     } else if (searchTime) {
15162         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15163         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15164     } else { /* [HGM] correct new time quote for time odds */
15165         whiteTC = blackTC = fullTimeControlString;
15166         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15167         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15168     }
15169     if (whiteFlag || blackFlag) {
15170         DisplayTitle("");
15171         whiteFlag = blackFlag = FALSE;
15172     }
15173     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15174     DisplayBothClocks();
15175 }
15176
15177 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15178
15179 /* Decrement running clock by amount of time that has passed */
15180 void
15181 DecrementClocks()
15182 {
15183     long timeRemaining;
15184     long lastTickLength, fudge;
15185     TimeMark now;
15186
15187     if (!appData.clockMode) return;
15188     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15189
15190     GetTimeMark(&now);
15191
15192     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15193
15194     /* Fudge if we woke up a little too soon */
15195     fudge = intendedTickLength - lastTickLength;
15196     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15197
15198     if (WhiteOnMove(forwardMostMove)) {
15199         if(whiteNPS >= 0) lastTickLength = 0;
15200         timeRemaining = whiteTimeRemaining -= lastTickLength;
15201         if(timeRemaining < 0 && !appData.icsActive) {
15202             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15203             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15204                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15205                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15206             }
15207         }
15208         DisplayWhiteClock(whiteTimeRemaining - fudge,
15209                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15210     } else {
15211         if(blackNPS >= 0) lastTickLength = 0;
15212         timeRemaining = blackTimeRemaining -= lastTickLength;
15213         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15214             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15215             if(suddenDeath) {
15216                 blackStartMove = forwardMostMove;
15217                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15218             }
15219         }
15220         DisplayBlackClock(blackTimeRemaining - fudge,
15221                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15222     }
15223     if (CheckFlags()) return;
15224
15225     tickStartTM = now;
15226     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15227     StartClockTimer(intendedTickLength);
15228
15229     /* if the time remaining has fallen below the alarm threshold, sound the
15230      * alarm. if the alarm has sounded and (due to a takeback or time control
15231      * with increment) the time remaining has increased to a level above the
15232      * threshold, reset the alarm so it can sound again.
15233      */
15234
15235     if (appData.icsActive && appData.icsAlarm) {
15236
15237         /* make sure we are dealing with the user's clock */
15238         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15239                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15240            )) return;
15241
15242         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15243             alarmSounded = FALSE;
15244         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15245             PlayAlarmSound();
15246             alarmSounded = TRUE;
15247         }
15248     }
15249 }
15250
15251
15252 /* A player has just moved, so stop the previously running
15253    clock and (if in clock mode) start the other one.
15254    We redisplay both clocks in case we're in ICS mode, because
15255    ICS gives us an update to both clocks after every move.
15256    Note that this routine is called *after* forwardMostMove
15257    is updated, so the last fractional tick must be subtracted
15258    from the color that is *not* on move now.
15259 */
15260 void
15261 SwitchClocks(int newMoveNr)
15262 {
15263     long lastTickLength;
15264     TimeMark now;
15265     int flagged = FALSE;
15266
15267     GetTimeMark(&now);
15268
15269     if (StopClockTimer() && appData.clockMode) {
15270         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15271         if (!WhiteOnMove(forwardMostMove)) {
15272             if(blackNPS >= 0) lastTickLength = 0;
15273             blackTimeRemaining -= lastTickLength;
15274            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15275 //         if(pvInfoList[forwardMostMove].time == -1)
15276                  pvInfoList[forwardMostMove].time =               // use GUI time
15277                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15278         } else {
15279            if(whiteNPS >= 0) lastTickLength = 0;
15280            whiteTimeRemaining -= lastTickLength;
15281            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15282 //         if(pvInfoList[forwardMostMove].time == -1)
15283                  pvInfoList[forwardMostMove].time =
15284                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15285         }
15286         flagged = CheckFlags();
15287     }
15288     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15289     CheckTimeControl();
15290
15291     if (flagged || !appData.clockMode) return;
15292
15293     switch (gameMode) {
15294       case MachinePlaysBlack:
15295       case MachinePlaysWhite:
15296       case BeginningOfGame:
15297         if (pausing) return;
15298         break;
15299
15300       case EditGame:
15301       case PlayFromGameFile:
15302       case IcsExamining:
15303         return;
15304
15305       default:
15306         break;
15307     }
15308
15309     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15310         if(WhiteOnMove(forwardMostMove))
15311              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15312         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15313     }
15314
15315     tickStartTM = now;
15316     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15317       whiteTimeRemaining : blackTimeRemaining);
15318     StartClockTimer(intendedTickLength);
15319 }
15320
15321
15322 /* Stop both clocks */
15323 void
15324 StopClocks()
15325 {
15326     long lastTickLength;
15327     TimeMark now;
15328
15329     if (!StopClockTimer()) return;
15330     if (!appData.clockMode) return;
15331
15332     GetTimeMark(&now);
15333
15334     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15335     if (WhiteOnMove(forwardMostMove)) {
15336         if(whiteNPS >= 0) lastTickLength = 0;
15337         whiteTimeRemaining -= lastTickLength;
15338         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15339     } else {
15340         if(blackNPS >= 0) lastTickLength = 0;
15341         blackTimeRemaining -= lastTickLength;
15342         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15343     }
15344     CheckFlags();
15345 }
15346
15347 /* Start clock of player on move.  Time may have been reset, so
15348    if clock is already running, stop and restart it. */
15349 void
15350 StartClocks()
15351 {
15352     (void) StopClockTimer(); /* in case it was running already */
15353     DisplayBothClocks();
15354     if (CheckFlags()) return;
15355
15356     if (!appData.clockMode) return;
15357     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15358
15359     GetTimeMark(&tickStartTM);
15360     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15361       whiteTimeRemaining : blackTimeRemaining);
15362
15363    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15364     whiteNPS = blackNPS = -1;
15365     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15366        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15367         whiteNPS = first.nps;
15368     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15369        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15370         blackNPS = first.nps;
15371     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15372         whiteNPS = second.nps;
15373     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15374         blackNPS = second.nps;
15375     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15376
15377     StartClockTimer(intendedTickLength);
15378 }
15379
15380 char *
15381 TimeString(ms)
15382      long ms;
15383 {
15384     long second, minute, hour, day;
15385     char *sign = "";
15386     static char buf[32];
15387
15388     if (ms > 0 && ms <= 9900) {
15389       /* convert milliseconds to tenths, rounding up */
15390       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15391
15392       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15393       return buf;
15394     }
15395
15396     /* convert milliseconds to seconds, rounding up */
15397     /* use floating point to avoid strangeness of integer division
15398        with negative dividends on many machines */
15399     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15400
15401     if (second < 0) {
15402         sign = "-";
15403         second = -second;
15404     }
15405
15406     day = second / (60 * 60 * 24);
15407     second = second % (60 * 60 * 24);
15408     hour = second / (60 * 60);
15409     second = second % (60 * 60);
15410     minute = second / 60;
15411     second = second % 60;
15412
15413     if (day > 0)
15414       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15415               sign, day, hour, minute, second);
15416     else if (hour > 0)
15417       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15418     else
15419       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15420
15421     return buf;
15422 }
15423
15424
15425 /*
15426  * This is necessary because some C libraries aren't ANSI C compliant yet.
15427  */
15428 char *
15429 StrStr(string, match)
15430      char *string, *match;
15431 {
15432     int i, length;
15433
15434     length = strlen(match);
15435
15436     for (i = strlen(string) - length; i >= 0; i--, string++)
15437       if (!strncmp(match, string, length))
15438         return string;
15439
15440     return NULL;
15441 }
15442
15443 char *
15444 StrCaseStr(string, match)
15445      char *string, *match;
15446 {
15447     int i, j, length;
15448
15449     length = strlen(match);
15450
15451     for (i = strlen(string) - length; i >= 0; i--, string++) {
15452         for (j = 0; j < length; j++) {
15453             if (ToLower(match[j]) != ToLower(string[j]))
15454               break;
15455         }
15456         if (j == length) return string;
15457     }
15458
15459     return NULL;
15460 }
15461
15462 #ifndef _amigados
15463 int
15464 StrCaseCmp(s1, s2)
15465      char *s1, *s2;
15466 {
15467     char c1, c2;
15468
15469     for (;;) {
15470         c1 = ToLower(*s1++);
15471         c2 = ToLower(*s2++);
15472         if (c1 > c2) return 1;
15473         if (c1 < c2) return -1;
15474         if (c1 == NULLCHAR) return 0;
15475     }
15476 }
15477
15478
15479 int
15480 ToLower(c)
15481      int c;
15482 {
15483     return isupper(c) ? tolower(c) : c;
15484 }
15485
15486
15487 int
15488 ToUpper(c)
15489      int c;
15490 {
15491     return islower(c) ? toupper(c) : c;
15492 }
15493 #endif /* !_amigados    */
15494
15495 char *
15496 StrSave(s)
15497      char *s;
15498 {
15499   char *ret;
15500
15501   if ((ret = (char *) malloc(strlen(s) + 1)))
15502     {
15503       safeStrCpy(ret, s, strlen(s)+1);
15504     }
15505   return ret;
15506 }
15507
15508 char *
15509 StrSavePtr(s, savePtr)
15510      char *s, **savePtr;
15511 {
15512     if (*savePtr) {
15513         free(*savePtr);
15514     }
15515     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15516       safeStrCpy(*savePtr, s, strlen(s)+1);
15517     }
15518     return(*savePtr);
15519 }
15520
15521 char *
15522 PGNDate()
15523 {
15524     time_t clock;
15525     struct tm *tm;
15526     char buf[MSG_SIZ];
15527
15528     clock = time((time_t *)NULL);
15529     tm = localtime(&clock);
15530     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15531             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15532     return StrSave(buf);
15533 }
15534
15535
15536 char *
15537 PositionToFEN(move, overrideCastling)
15538      int move;
15539      char *overrideCastling;
15540 {
15541     int i, j, fromX, fromY, toX, toY;
15542     int whiteToPlay;
15543     char buf[128];
15544     char *p, *q;
15545     int emptycount;
15546     ChessSquare piece;
15547
15548     whiteToPlay = (gameMode == EditPosition) ?
15549       !blackPlaysFirst : (move % 2 == 0);
15550     p = buf;
15551
15552     /* Piece placement data */
15553     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15554         emptycount = 0;
15555         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15556             if (boards[move][i][j] == EmptySquare) {
15557                 emptycount++;
15558             } else { ChessSquare piece = boards[move][i][j];
15559                 if (emptycount > 0) {
15560                     if(emptycount<10) /* [HGM] can be >= 10 */
15561                         *p++ = '0' + emptycount;
15562                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15563                     emptycount = 0;
15564                 }
15565                 if(PieceToChar(piece) == '+') {
15566                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15567                     *p++ = '+';
15568                     piece = (ChessSquare)(DEMOTED piece);
15569                 }
15570                 *p++ = PieceToChar(piece);
15571                 if(p[-1] == '~') {
15572                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15573                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15574                     *p++ = '~';
15575                 }
15576             }
15577         }
15578         if (emptycount > 0) {
15579             if(emptycount<10) /* [HGM] can be >= 10 */
15580                 *p++ = '0' + emptycount;
15581             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15582             emptycount = 0;
15583         }
15584         *p++ = '/';
15585     }
15586     *(p - 1) = ' ';
15587
15588     /* [HGM] print Crazyhouse or Shogi holdings */
15589     if( gameInfo.holdingsWidth ) {
15590         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15591         q = p;
15592         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15593             piece = boards[move][i][BOARD_WIDTH-1];
15594             if( piece != EmptySquare )
15595               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15596                   *p++ = PieceToChar(piece);
15597         }
15598         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15599             piece = boards[move][BOARD_HEIGHT-i-1][0];
15600             if( piece != EmptySquare )
15601               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15602                   *p++ = PieceToChar(piece);
15603         }
15604
15605         if( q == p ) *p++ = '-';
15606         *p++ = ']';
15607         *p++ = ' ';
15608     }
15609
15610     /* Active color */
15611     *p++ = whiteToPlay ? 'w' : 'b';
15612     *p++ = ' ';
15613
15614   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15615     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15616   } else {
15617   if(nrCastlingRights) {
15618      q = p;
15619      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15620        /* [HGM] write directly from rights */
15621            if(boards[move][CASTLING][2] != NoRights &&
15622               boards[move][CASTLING][0] != NoRights   )
15623                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15624            if(boards[move][CASTLING][2] != NoRights &&
15625               boards[move][CASTLING][1] != NoRights   )
15626                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15627            if(boards[move][CASTLING][5] != NoRights &&
15628               boards[move][CASTLING][3] != NoRights   )
15629                 *p++ = boards[move][CASTLING][3] + AAA;
15630            if(boards[move][CASTLING][5] != NoRights &&
15631               boards[move][CASTLING][4] != NoRights   )
15632                 *p++ = boards[move][CASTLING][4] + AAA;
15633      } else {
15634
15635         /* [HGM] write true castling rights */
15636         if( nrCastlingRights == 6 ) {
15637             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15638                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15639             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15640                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15641             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15642                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15643             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15644                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15645         }
15646      }
15647      if (q == p) *p++ = '-'; /* No castling rights */
15648      *p++ = ' ';
15649   }
15650
15651   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15652      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15653     /* En passant target square */
15654     if (move > backwardMostMove) {
15655         fromX = moveList[move - 1][0] - AAA;
15656         fromY = moveList[move - 1][1] - ONE;
15657         toX = moveList[move - 1][2] - AAA;
15658         toY = moveList[move - 1][3] - ONE;
15659         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15660             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15661             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15662             fromX == toX) {
15663             /* 2-square pawn move just happened */
15664             *p++ = toX + AAA;
15665             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15666         } else {
15667             *p++ = '-';
15668         }
15669     } else if(move == backwardMostMove) {
15670         // [HGM] perhaps we should always do it like this, and forget the above?
15671         if((signed char)boards[move][EP_STATUS] >= 0) {
15672             *p++ = boards[move][EP_STATUS] + AAA;
15673             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15674         } else {
15675             *p++ = '-';
15676         }
15677     } else {
15678         *p++ = '-';
15679     }
15680     *p++ = ' ';
15681   }
15682   }
15683
15684     /* [HGM] find reversible plies */
15685     {   int i = 0, j=move;
15686
15687         if (appData.debugMode) { int k;
15688             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15689             for(k=backwardMostMove; k<=forwardMostMove; k++)
15690                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15691
15692         }
15693
15694         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15695         if( j == backwardMostMove ) i += initialRulePlies;
15696         sprintf(p, "%d ", i);
15697         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15698     }
15699     /* Fullmove number */
15700     sprintf(p, "%d", (move / 2) + 1);
15701
15702     return StrSave(buf);
15703 }
15704
15705 Boolean
15706 ParseFEN(board, blackPlaysFirst, fen)
15707     Board board;
15708      int *blackPlaysFirst;
15709      char *fen;
15710 {
15711     int i, j;
15712     char *p, c;
15713     int emptycount;
15714     ChessSquare piece;
15715
15716     p = fen;
15717
15718     /* [HGM] by default clear Crazyhouse holdings, if present */
15719     if(gameInfo.holdingsWidth) {
15720        for(i=0; i<BOARD_HEIGHT; i++) {
15721            board[i][0]             = EmptySquare; /* black holdings */
15722            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15723            board[i][1]             = (ChessSquare) 0; /* black counts */
15724            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15725        }
15726     }
15727
15728     /* Piece placement data */
15729     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15730         j = 0;
15731         for (;;) {
15732             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15733                 if (*p == '/') p++;
15734                 emptycount = gameInfo.boardWidth - j;
15735                 while (emptycount--)
15736                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15737                 break;
15738 #if(BOARD_FILES >= 10)
15739             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15740                 p++; emptycount=10;
15741                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15742                 while (emptycount--)
15743                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15744 #endif
15745             } else if (isdigit(*p)) {
15746                 emptycount = *p++ - '0';
15747                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15748                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15749                 while (emptycount--)
15750                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15751             } else if (*p == '+' || isalpha(*p)) {
15752                 if (j >= gameInfo.boardWidth) return FALSE;
15753                 if(*p=='+') {
15754                     piece = CharToPiece(*++p);
15755                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15756                     piece = (ChessSquare) (PROMOTED piece ); p++;
15757                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15758                 } else piece = CharToPiece(*p++);
15759
15760                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15761                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15762                     piece = (ChessSquare) (PROMOTED piece);
15763                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15764                     p++;
15765                 }
15766                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15767             } else {
15768                 return FALSE;
15769             }
15770         }
15771     }
15772     while (*p == '/' || *p == ' ') p++;
15773
15774     /* [HGM] look for Crazyhouse holdings here */
15775     while(*p==' ') p++;
15776     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15777         if(*p == '[') p++;
15778         if(*p == '-' ) p++; /* empty holdings */ else {
15779             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15780             /* if we would allow FEN reading to set board size, we would   */
15781             /* have to add holdings and shift the board read so far here   */
15782             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15783                 p++;
15784                 if((int) piece >= (int) BlackPawn ) {
15785                     i = (int)piece - (int)BlackPawn;
15786                     i = PieceToNumber((ChessSquare)i);
15787                     if( i >= gameInfo.holdingsSize ) return FALSE;
15788                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15789                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15790                 } else {
15791                     i = (int)piece - (int)WhitePawn;
15792                     i = PieceToNumber((ChessSquare)i);
15793                     if( i >= gameInfo.holdingsSize ) return FALSE;
15794                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15795                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15796                 }
15797             }
15798         }
15799         if(*p == ']') p++;
15800     }
15801
15802     while(*p == ' ') p++;
15803
15804     /* Active color */
15805     c = *p++;
15806     if(appData.colorNickNames) {
15807       if( c == appData.colorNickNames[0] ) c = 'w'; else
15808       if( c == appData.colorNickNames[1] ) c = 'b';
15809     }
15810     switch (c) {
15811       case 'w':
15812         *blackPlaysFirst = FALSE;
15813         break;
15814       case 'b':
15815         *blackPlaysFirst = TRUE;
15816         break;
15817       default:
15818         return FALSE;
15819     }
15820
15821     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15822     /* return the extra info in global variiables             */
15823
15824     /* set defaults in case FEN is incomplete */
15825     board[EP_STATUS] = EP_UNKNOWN;
15826     for(i=0; i<nrCastlingRights; i++ ) {
15827         board[CASTLING][i] =
15828             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15829     }   /* assume possible unless obviously impossible */
15830     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15831     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15832     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15833                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15834     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15835     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15836     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15837                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15838     FENrulePlies = 0;
15839
15840     while(*p==' ') p++;
15841     if(nrCastlingRights) {
15842       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15843           /* castling indicator present, so default becomes no castlings */
15844           for(i=0; i<nrCastlingRights; i++ ) {
15845                  board[CASTLING][i] = NoRights;
15846           }
15847       }
15848       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15849              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15850              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15851              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15852         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15853
15854         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15855             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15856             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15857         }
15858         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15859             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15860         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15861                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15862         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15863                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15864         switch(c) {
15865           case'K':
15866               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15867               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15868               board[CASTLING][2] = whiteKingFile;
15869               break;
15870           case'Q':
15871               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15872               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15873               board[CASTLING][2] = whiteKingFile;
15874               break;
15875           case'k':
15876               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15877               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15878               board[CASTLING][5] = blackKingFile;
15879               break;
15880           case'q':
15881               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15882               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15883               board[CASTLING][5] = blackKingFile;
15884           case '-':
15885               break;
15886           default: /* FRC castlings */
15887               if(c >= 'a') { /* black rights */
15888                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15889                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15890                   if(i == BOARD_RGHT) break;
15891                   board[CASTLING][5] = i;
15892                   c -= AAA;
15893                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15894                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15895                   if(c > i)
15896                       board[CASTLING][3] = c;
15897                   else
15898                       board[CASTLING][4] = c;
15899               } else { /* white rights */
15900                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15901                     if(board[0][i] == WhiteKing) break;
15902                   if(i == BOARD_RGHT) break;
15903                   board[CASTLING][2] = i;
15904                   c -= AAA - 'a' + 'A';
15905                   if(board[0][c] >= WhiteKing) break;
15906                   if(c > i)
15907                       board[CASTLING][0] = c;
15908                   else
15909                       board[CASTLING][1] = c;
15910               }
15911         }
15912       }
15913       for(i=0; i<nrCastlingRights; i++)
15914         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15915     if (appData.debugMode) {
15916         fprintf(debugFP, "FEN castling rights:");
15917         for(i=0; i<nrCastlingRights; i++)
15918         fprintf(debugFP, " %d", board[CASTLING][i]);
15919         fprintf(debugFP, "\n");
15920     }
15921
15922       while(*p==' ') p++;
15923     }
15924
15925     /* read e.p. field in games that know e.p. capture */
15926     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15927        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15928       if(*p=='-') {
15929         p++; board[EP_STATUS] = EP_NONE;
15930       } else {
15931          char c = *p++ - AAA;
15932
15933          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15934          if(*p >= '0' && *p <='9') p++;
15935          board[EP_STATUS] = c;
15936       }
15937     }
15938
15939
15940     if(sscanf(p, "%d", &i) == 1) {
15941         FENrulePlies = i; /* 50-move ply counter */
15942         /* (The move number is still ignored)    */
15943     }
15944
15945     return TRUE;
15946 }
15947
15948 void
15949 EditPositionPasteFEN(char *fen)
15950 {
15951   if (fen != NULL) {
15952     Board initial_position;
15953
15954     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15955       DisplayError(_("Bad FEN position in clipboard"), 0);
15956       return ;
15957     } else {
15958       int savedBlackPlaysFirst = blackPlaysFirst;
15959       EditPositionEvent();
15960       blackPlaysFirst = savedBlackPlaysFirst;
15961       CopyBoard(boards[0], initial_position);
15962       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15963       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15964       DisplayBothClocks();
15965       DrawPosition(FALSE, boards[currentMove]);
15966     }
15967   }
15968 }
15969
15970 static char cseq[12] = "\\   ";
15971
15972 Boolean set_cont_sequence(char *new_seq)
15973 {
15974     int len;
15975     Boolean ret;
15976
15977     // handle bad attempts to set the sequence
15978         if (!new_seq)
15979                 return 0; // acceptable error - no debug
15980
15981     len = strlen(new_seq);
15982     ret = (len > 0) && (len < sizeof(cseq));
15983     if (ret)
15984       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15985     else if (appData.debugMode)
15986       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15987     return ret;
15988 }
15989
15990 /*
15991     reformat a source message so words don't cross the width boundary.  internal
15992     newlines are not removed.  returns the wrapped size (no null character unless
15993     included in source message).  If dest is NULL, only calculate the size required
15994     for the dest buffer.  lp argument indicats line position upon entry, and it's
15995     passed back upon exit.
15996 */
15997 int wrap(char *dest, char *src, int count, int width, int *lp)
15998 {
15999     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16000
16001     cseq_len = strlen(cseq);
16002     old_line = line = *lp;
16003     ansi = len = clen = 0;
16004
16005     for (i=0; i < count; i++)
16006     {
16007         if (src[i] == '\033')
16008             ansi = 1;
16009
16010         // if we hit the width, back up
16011         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16012         {
16013             // store i & len in case the word is too long
16014             old_i = i, old_len = len;
16015
16016             // find the end of the last word
16017             while (i && src[i] != ' ' && src[i] != '\n')
16018             {
16019                 i--;
16020                 len--;
16021             }
16022
16023             // word too long?  restore i & len before splitting it
16024             if ((old_i-i+clen) >= width)
16025             {
16026                 i = old_i;
16027                 len = old_len;
16028             }
16029
16030             // extra space?
16031             if (i && src[i-1] == ' ')
16032                 len--;
16033
16034             if (src[i] != ' ' && src[i] != '\n')
16035             {
16036                 i--;
16037                 if (len)
16038                     len--;
16039             }
16040
16041             // now append the newline and continuation sequence
16042             if (dest)
16043                 dest[len] = '\n';
16044             len++;
16045             if (dest)
16046                 strncpy(dest+len, cseq, cseq_len);
16047             len += cseq_len;
16048             line = cseq_len;
16049             clen = cseq_len;
16050             continue;
16051         }
16052
16053         if (dest)
16054             dest[len] = src[i];
16055         len++;
16056         if (!ansi)
16057             line++;
16058         if (src[i] == '\n')
16059             line = 0;
16060         if (src[i] == 'm')
16061             ansi = 0;
16062     }
16063     if (dest && appData.debugMode)
16064     {
16065         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16066             count, width, line, len, *lp);
16067         show_bytes(debugFP, src, count);
16068         fprintf(debugFP, "\ndest: ");
16069         show_bytes(debugFP, dest, len);
16070         fprintf(debugFP, "\n");
16071     }
16072     *lp = dest ? line : old_line;
16073
16074     return len;
16075 }
16076
16077 // [HGM] vari: routines for shelving variations
16078
16079 void
16080 PushInner(int firstMove, int lastMove)
16081 {
16082         int i, j, nrMoves = lastMove - firstMove;
16083
16084         // push current tail of game on stack
16085         savedResult[storedGames] = gameInfo.result;
16086         savedDetails[storedGames] = gameInfo.resultDetails;
16087         gameInfo.resultDetails = NULL;
16088         savedFirst[storedGames] = firstMove;
16089         savedLast [storedGames] = lastMove;
16090         savedFramePtr[storedGames] = framePtr;
16091         framePtr -= nrMoves; // reserve space for the boards
16092         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16093             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16094             for(j=0; j<MOVE_LEN; j++)
16095                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16096             for(j=0; j<2*MOVE_LEN; j++)
16097                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16098             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16099             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16100             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16101             pvInfoList[firstMove+i-1].depth = 0;
16102             commentList[framePtr+i] = commentList[firstMove+i];
16103             commentList[firstMove+i] = NULL;
16104         }
16105
16106         storedGames++;
16107         forwardMostMove = firstMove; // truncate game so we can start variation
16108 }
16109
16110 void
16111 PushTail(int firstMove, int lastMove)
16112 {
16113         if(appData.icsActive) { // only in local mode
16114                 forwardMostMove = currentMove; // mimic old ICS behavior
16115                 return;
16116         }
16117         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16118
16119         PushInner(firstMove, lastMove);
16120         if(storedGames == 1) GreyRevert(FALSE);
16121 }
16122
16123 void
16124 PopInner(Boolean annotate)
16125 {
16126         int i, j, nrMoves;
16127         char buf[8000], moveBuf[20];
16128
16129         storedGames--;
16130         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16131         nrMoves = savedLast[storedGames] - currentMove;
16132         if(annotate) {
16133                 int cnt = 10;
16134                 if(!WhiteOnMove(currentMove))
16135                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16136                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16137                 for(i=currentMove; i<forwardMostMove; i++) {
16138                         if(WhiteOnMove(i))
16139                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16140                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16141                         strcat(buf, moveBuf);
16142                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16143                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16144                 }
16145                 strcat(buf, ")");
16146         }
16147         for(i=1; i<=nrMoves; i++) { // copy last variation back
16148             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16149             for(j=0; j<MOVE_LEN; j++)
16150                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16151             for(j=0; j<2*MOVE_LEN; j++)
16152                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16153             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16154             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16155             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16156             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16157             commentList[currentMove+i] = commentList[framePtr+i];
16158             commentList[framePtr+i] = NULL;
16159         }
16160         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16161         framePtr = savedFramePtr[storedGames];
16162         gameInfo.result = savedResult[storedGames];
16163         if(gameInfo.resultDetails != NULL) {
16164             free(gameInfo.resultDetails);
16165       }
16166         gameInfo.resultDetails = savedDetails[storedGames];
16167         forwardMostMove = currentMove + nrMoves;
16168 }
16169
16170 Boolean
16171 PopTail(Boolean annotate)
16172 {
16173         if(appData.icsActive) return FALSE; // only in local mode
16174         if(!storedGames) return FALSE; // sanity
16175         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16176
16177         PopInner(annotate);
16178
16179         if(storedGames == 0) GreyRevert(TRUE);
16180         return TRUE;
16181 }
16182
16183 void
16184 CleanupTail()
16185 {       // remove all shelved variations
16186         int i;
16187         for(i=0; i<storedGames; i++) {
16188             if(savedDetails[i])
16189                 free(savedDetails[i]);
16190             savedDetails[i] = NULL;
16191         }
16192         for(i=framePtr; i<MAX_MOVES; i++) {
16193                 if(commentList[i]) free(commentList[i]);
16194                 commentList[i] = NULL;
16195         }
16196         framePtr = MAX_MOVES-1;
16197         storedGames = 0;
16198 }
16199
16200 void
16201 LoadVariation(int index, char *text)
16202 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16203         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16204         int level = 0, move;
16205
16206         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16207         // first find outermost bracketing variation
16208         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16209             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16210                 if(*p == '{') wait = '}'; else
16211                 if(*p == '[') wait = ']'; else
16212                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16213                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16214             }
16215             if(*p == wait) wait = NULLCHAR; // closing ]} found
16216             p++;
16217         }
16218         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16219         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16220         end[1] = NULLCHAR; // clip off comment beyond variation
16221         ToNrEvent(currentMove-1);
16222         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16223         // kludge: use ParsePV() to append variation to game
16224         move = currentMove;
16225         ParsePV(start, TRUE);
16226         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16227         ClearPremoveHighlights();
16228         CommentPopDown();
16229         ToNrEvent(currentMove+1);
16230 }
16231