Fix stopping of match in tourney mode
[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 NULL; // 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) DisplayFatalError(ranking ? ranking : buf, 0, 0); else {
10068         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10069         DisplayNote(ranking ? ranking : buf);
10070       }
10071       if(ranking) free(ranking);
10072     }
10073 }
10074
10075 /* Assumes program was just initialized (initString sent).
10076    Leaves program in force mode. */
10077 void
10078 FeedMovesToProgram(cps, upto)
10079      ChessProgramState *cps;
10080      int upto;
10081 {
10082     int i;
10083
10084     if (appData.debugMode)
10085       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10086               startedFromSetupPosition ? "position and " : "",
10087               backwardMostMove, upto, cps->which);
10088     if(currentlyInitializedVariant != gameInfo.variant) {
10089       char buf[MSG_SIZ];
10090         // [HGM] variantswitch: make engine aware of new variant
10091         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10092                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10093         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10094         SendToProgram(buf, cps);
10095         currentlyInitializedVariant = gameInfo.variant;
10096     }
10097     SendToProgram("force\n", cps);
10098     if (startedFromSetupPosition) {
10099         SendBoard(cps, backwardMostMove);
10100     if (appData.debugMode) {
10101         fprintf(debugFP, "feedMoves\n");
10102     }
10103     }
10104     for (i = backwardMostMove; i < upto; i++) {
10105         SendMoveToProgram(i, cps);
10106     }
10107 }
10108
10109
10110 int
10111 ResurrectChessProgram()
10112 {
10113      /* The chess program may have exited.
10114         If so, restart it and feed it all the moves made so far. */
10115     static int doInit = 0;
10116
10117     if (appData.noChessProgram) return 1;
10118
10119     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10120         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10121         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10122         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10123     } else {
10124         if (first.pr != NoProc) return 1;
10125         StartChessProgram(&first);
10126     }
10127     InitChessProgram(&first, FALSE);
10128     FeedMovesToProgram(&first, currentMove);
10129
10130     if (!first.sendTime) {
10131         /* can't tell gnuchess what its clock should read,
10132            so we bow to its notion. */
10133         ResetClocks();
10134         timeRemaining[0][currentMove] = whiteTimeRemaining;
10135         timeRemaining[1][currentMove] = blackTimeRemaining;
10136     }
10137
10138     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10139                 appData.icsEngineAnalyze) && first.analysisSupport) {
10140       SendToProgram("analyze\n", &first);
10141       first.analyzing = TRUE;
10142     }
10143     return 1;
10144 }
10145
10146 /*
10147  * Button procedures
10148  */
10149 void
10150 Reset(redraw, init)
10151      int redraw, init;
10152 {
10153     int i;
10154
10155     if (appData.debugMode) {
10156         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10157                 redraw, init, gameMode);
10158     }
10159     CleanupTail(); // [HGM] vari: delete any stored variations
10160     pausing = pauseExamInvalid = FALSE;
10161     startedFromSetupPosition = blackPlaysFirst = FALSE;
10162     firstMove = TRUE;
10163     whiteFlag = blackFlag = FALSE;
10164     userOfferedDraw = FALSE;
10165     hintRequested = bookRequested = FALSE;
10166     first.maybeThinking = FALSE;
10167     second.maybeThinking = FALSE;
10168     first.bookSuspend = FALSE; // [HGM] book
10169     second.bookSuspend = FALSE;
10170     thinkOutput[0] = NULLCHAR;
10171     lastHint[0] = NULLCHAR;
10172     ClearGameInfo(&gameInfo);
10173     gameInfo.variant = StringToVariant(appData.variant);
10174     ics_user_moved = ics_clock_paused = FALSE;
10175     ics_getting_history = H_FALSE;
10176     ics_gamenum = -1;
10177     white_holding[0] = black_holding[0] = NULLCHAR;
10178     ClearProgramStats();
10179     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10180
10181     ResetFrontEnd();
10182     ClearHighlights();
10183     flipView = appData.flipView;
10184     ClearPremoveHighlights();
10185     gotPremove = FALSE;
10186     alarmSounded = FALSE;
10187
10188     GameEnds(EndOfFile, NULL, GE_PLAYER);
10189     if(appData.serverMovesName != NULL) {
10190         /* [HGM] prepare to make moves file for broadcasting */
10191         clock_t t = clock();
10192         if(serverMoves != NULL) fclose(serverMoves);
10193         serverMoves = fopen(appData.serverMovesName, "r");
10194         if(serverMoves != NULL) {
10195             fclose(serverMoves);
10196             /* delay 15 sec before overwriting, so all clients can see end */
10197             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10198         }
10199         serverMoves = fopen(appData.serverMovesName, "w");
10200     }
10201
10202     ExitAnalyzeMode();
10203     gameMode = BeginningOfGame;
10204     ModeHighlight();
10205     if(appData.icsActive) gameInfo.variant = VariantNormal;
10206     currentMove = forwardMostMove = backwardMostMove = 0;
10207     InitPosition(redraw);
10208     for (i = 0; i < MAX_MOVES; i++) {
10209         if (commentList[i] != NULL) {
10210             free(commentList[i]);
10211             commentList[i] = NULL;
10212         }
10213     }
10214     ResetClocks();
10215     timeRemaining[0][0] = whiteTimeRemaining;
10216     timeRemaining[1][0] = blackTimeRemaining;
10217
10218     if (first.pr == NULL) {
10219         StartChessProgram(&first);
10220     }
10221     if (init) {
10222             InitChessProgram(&first, startedFromSetupPosition);
10223     }
10224     DisplayTitle("");
10225     DisplayMessage("", "");
10226     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10227     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10228 }
10229
10230 void
10231 AutoPlayGameLoop()
10232 {
10233     for (;;) {
10234         if (!AutoPlayOneMove())
10235           return;
10236         if (matchMode || appData.timeDelay == 0)
10237           continue;
10238         if (appData.timeDelay < 0)
10239           return;
10240         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10241         break;
10242     }
10243 }
10244
10245
10246 int
10247 AutoPlayOneMove()
10248 {
10249     int fromX, fromY, toX, toY;
10250
10251     if (appData.debugMode) {
10252       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10253     }
10254
10255     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10256       return FALSE;
10257
10258     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10259       pvInfoList[currentMove].depth = programStats.depth;
10260       pvInfoList[currentMove].score = programStats.score;
10261       pvInfoList[currentMove].time  = 0;
10262       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10263     }
10264
10265     if (currentMove >= forwardMostMove) {
10266       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10267       gameMode = EditGame;
10268       ModeHighlight();
10269
10270       /* [AS] Clear current move marker at the end of a game */
10271       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10272
10273       return FALSE;
10274     }
10275
10276     toX = moveList[currentMove][2] - AAA;
10277     toY = moveList[currentMove][3] - ONE;
10278
10279     if (moveList[currentMove][1] == '@') {
10280         if (appData.highlightLastMove) {
10281             SetHighlights(-1, -1, toX, toY);
10282         }
10283     } else {
10284         fromX = moveList[currentMove][0] - AAA;
10285         fromY = moveList[currentMove][1] - ONE;
10286
10287         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10288
10289         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10290
10291         if (appData.highlightLastMove) {
10292             SetHighlights(fromX, fromY, toX, toY);
10293         }
10294     }
10295     DisplayMove(currentMove);
10296     SendMoveToProgram(currentMove++, &first);
10297     DisplayBothClocks();
10298     DrawPosition(FALSE, boards[currentMove]);
10299     // [HGM] PV info: always display, routine tests if empty
10300     DisplayComment(currentMove - 1, commentList[currentMove]);
10301     return TRUE;
10302 }
10303
10304
10305 int
10306 LoadGameOneMove(readAhead)
10307      ChessMove readAhead;
10308 {
10309     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10310     char promoChar = NULLCHAR;
10311     ChessMove moveType;
10312     char move[MSG_SIZ];
10313     char *p, *q;
10314
10315     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10316         gameMode != AnalyzeMode && gameMode != Training) {
10317         gameFileFP = NULL;
10318         return FALSE;
10319     }
10320
10321     yyboardindex = forwardMostMove;
10322     if (readAhead != EndOfFile) {
10323       moveType = readAhead;
10324     } else {
10325       if (gameFileFP == NULL)
10326           return FALSE;
10327       moveType = (ChessMove) Myylex();
10328     }
10329
10330     done = FALSE;
10331     switch (moveType) {
10332       case Comment:
10333         if (appData.debugMode)
10334           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10335         p = yy_text;
10336
10337         /* append the comment but don't display it */
10338         AppendComment(currentMove, p, FALSE);
10339         return TRUE;
10340
10341       case WhiteCapturesEnPassant:
10342       case BlackCapturesEnPassant:
10343       case WhitePromotion:
10344       case BlackPromotion:
10345       case WhiteNonPromotion:
10346       case BlackNonPromotion:
10347       case NormalMove:
10348       case WhiteKingSideCastle:
10349       case WhiteQueenSideCastle:
10350       case BlackKingSideCastle:
10351       case BlackQueenSideCastle:
10352       case WhiteKingSideCastleWild:
10353       case WhiteQueenSideCastleWild:
10354       case BlackKingSideCastleWild:
10355       case BlackQueenSideCastleWild:
10356       /* PUSH Fabien */
10357       case WhiteHSideCastleFR:
10358       case WhiteASideCastleFR:
10359       case BlackHSideCastleFR:
10360       case BlackASideCastleFR:
10361       /* POP Fabien */
10362         if (appData.debugMode)
10363           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10364         fromX = currentMoveString[0] - AAA;
10365         fromY = currentMoveString[1] - ONE;
10366         toX = currentMoveString[2] - AAA;
10367         toY = currentMoveString[3] - ONE;
10368         promoChar = currentMoveString[4];
10369         break;
10370
10371       case WhiteDrop:
10372       case BlackDrop:
10373         if (appData.debugMode)
10374           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10375         fromX = moveType == WhiteDrop ?
10376           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10377         (int) CharToPiece(ToLower(currentMoveString[0]));
10378         fromY = DROP_RANK;
10379         toX = currentMoveString[2] - AAA;
10380         toY = currentMoveString[3] - ONE;
10381         break;
10382
10383       case WhiteWins:
10384       case BlackWins:
10385       case GameIsDrawn:
10386       case GameUnfinished:
10387         if (appData.debugMode)
10388           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10389         p = strchr(yy_text, '{');
10390         if (p == NULL) p = strchr(yy_text, '(');
10391         if (p == NULL) {
10392             p = yy_text;
10393             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10394         } else {
10395             q = strchr(p, *p == '{' ? '}' : ')');
10396             if (q != NULL) *q = NULLCHAR;
10397             p++;
10398         }
10399         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10400         GameEnds(moveType, p, GE_FILE);
10401         done = TRUE;
10402         if (cmailMsgLoaded) {
10403             ClearHighlights();
10404             flipView = WhiteOnMove(currentMove);
10405             if (moveType == GameUnfinished) flipView = !flipView;
10406             if (appData.debugMode)
10407               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10408         }
10409         break;
10410
10411       case EndOfFile:
10412         if (appData.debugMode)
10413           fprintf(debugFP, "Parser hit end of file\n");
10414         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10415           case MT_NONE:
10416           case MT_CHECK:
10417             break;
10418           case MT_CHECKMATE:
10419           case MT_STAINMATE:
10420             if (WhiteOnMove(currentMove)) {
10421                 GameEnds(BlackWins, "Black mates", GE_FILE);
10422             } else {
10423                 GameEnds(WhiteWins, "White mates", GE_FILE);
10424             }
10425             break;
10426           case MT_STALEMATE:
10427             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10428             break;
10429         }
10430         done = TRUE;
10431         break;
10432
10433       case MoveNumberOne:
10434         if (lastLoadGameStart == GNUChessGame) {
10435             /* GNUChessGames have numbers, but they aren't move numbers */
10436             if (appData.debugMode)
10437               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10438                       yy_text, (int) moveType);
10439             return LoadGameOneMove(EndOfFile); /* tail recursion */
10440         }
10441         /* else fall thru */
10442
10443       case XBoardGame:
10444       case GNUChessGame:
10445       case PGNTag:
10446         /* Reached start of next game in file */
10447         if (appData.debugMode)
10448           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10449         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10450           case MT_NONE:
10451           case MT_CHECK:
10452             break;
10453           case MT_CHECKMATE:
10454           case MT_STAINMATE:
10455             if (WhiteOnMove(currentMove)) {
10456                 GameEnds(BlackWins, "Black mates", GE_FILE);
10457             } else {
10458                 GameEnds(WhiteWins, "White mates", GE_FILE);
10459             }
10460             break;
10461           case MT_STALEMATE:
10462             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10463             break;
10464         }
10465         done = TRUE;
10466         break;
10467
10468       case PositionDiagram:     /* should not happen; ignore */
10469       case ElapsedTime:         /* ignore */
10470       case NAG:                 /* ignore */
10471         if (appData.debugMode)
10472           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10473                   yy_text, (int) moveType);
10474         return LoadGameOneMove(EndOfFile); /* tail recursion */
10475
10476       case IllegalMove:
10477         if (appData.testLegality) {
10478             if (appData.debugMode)
10479               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10480             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10481                     (forwardMostMove / 2) + 1,
10482                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10483             DisplayError(move, 0);
10484             done = TRUE;
10485         } else {
10486             if (appData.debugMode)
10487               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10488                       yy_text, currentMoveString);
10489             fromX = currentMoveString[0] - AAA;
10490             fromY = currentMoveString[1] - ONE;
10491             toX = currentMoveString[2] - AAA;
10492             toY = currentMoveString[3] - ONE;
10493             promoChar = currentMoveString[4];
10494         }
10495         break;
10496
10497       case AmbiguousMove:
10498         if (appData.debugMode)
10499           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10500         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10501                 (forwardMostMove / 2) + 1,
10502                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10503         DisplayError(move, 0);
10504         done = TRUE;
10505         break;
10506
10507       default:
10508       case ImpossibleMove:
10509         if (appData.debugMode)
10510           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10511         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10512                 (forwardMostMove / 2) + 1,
10513                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10514         DisplayError(move, 0);
10515         done = TRUE;
10516         break;
10517     }
10518
10519     if (done) {
10520         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10521             DrawPosition(FALSE, boards[currentMove]);
10522             DisplayBothClocks();
10523             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10524               DisplayComment(currentMove - 1, commentList[currentMove]);
10525         }
10526         (void) StopLoadGameTimer();
10527         gameFileFP = NULL;
10528         cmailOldMove = forwardMostMove;
10529         return FALSE;
10530     } else {
10531         /* currentMoveString is set as a side-effect of yylex */
10532
10533         thinkOutput[0] = NULLCHAR;
10534         MakeMove(fromX, fromY, toX, toY, promoChar);
10535         currentMove = forwardMostMove;
10536         return TRUE;
10537     }
10538 }
10539
10540 /* Load the nth game from the given file */
10541 int
10542 LoadGameFromFile(filename, n, title, useList)
10543      char *filename;
10544      int n;
10545      char *title;
10546      /*Boolean*/ int useList;
10547 {
10548     FILE *f;
10549     char buf[MSG_SIZ];
10550
10551     if (strcmp(filename, "-") == 0) {
10552         f = stdin;
10553         title = "stdin";
10554     } else {
10555         f = fopen(filename, "rb");
10556         if (f == NULL) {
10557           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10558             DisplayError(buf, errno);
10559             return FALSE;
10560         }
10561     }
10562     if (fseek(f, 0, 0) == -1) {
10563         /* f is not seekable; probably a pipe */
10564         useList = FALSE;
10565     }
10566     if (useList && n == 0) {
10567         int error = GameListBuild(f);
10568         if (error) {
10569             DisplayError(_("Cannot build game list"), error);
10570         } else if (!ListEmpty(&gameList) &&
10571                    ((ListGame *) gameList.tailPred)->number > 1) {
10572             GameListPopUp(f, title);
10573             return TRUE;
10574         }
10575         GameListDestroy();
10576         n = 1;
10577     }
10578     if (n == 0) n = 1;
10579     return LoadGame(f, n, title, FALSE);
10580 }
10581
10582
10583 void
10584 MakeRegisteredMove()
10585 {
10586     int fromX, fromY, toX, toY;
10587     char promoChar;
10588     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10589         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10590           case CMAIL_MOVE:
10591           case CMAIL_DRAW:
10592             if (appData.debugMode)
10593               fprintf(debugFP, "Restoring %s for game %d\n",
10594                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10595
10596             thinkOutput[0] = NULLCHAR;
10597             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10598             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10599             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10600             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10601             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10602             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10603             MakeMove(fromX, fromY, toX, toY, promoChar);
10604             ShowMove(fromX, fromY, toX, toY);
10605
10606             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10607               case MT_NONE:
10608               case MT_CHECK:
10609                 break;
10610
10611               case MT_CHECKMATE:
10612               case MT_STAINMATE:
10613                 if (WhiteOnMove(currentMove)) {
10614                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10615                 } else {
10616                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10617                 }
10618                 break;
10619
10620               case MT_STALEMATE:
10621                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10622                 break;
10623             }
10624
10625             break;
10626
10627           case CMAIL_RESIGN:
10628             if (WhiteOnMove(currentMove)) {
10629                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10630             } else {
10631                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10632             }
10633             break;
10634
10635           case CMAIL_ACCEPT:
10636             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10637             break;
10638
10639           default:
10640             break;
10641         }
10642     }
10643
10644     return;
10645 }
10646
10647 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10648 int
10649 CmailLoadGame(f, gameNumber, title, useList)
10650      FILE *f;
10651      int gameNumber;
10652      char *title;
10653      int useList;
10654 {
10655     int retVal;
10656
10657     if (gameNumber > nCmailGames) {
10658         DisplayError(_("No more games in this message"), 0);
10659         return FALSE;
10660     }
10661     if (f == lastLoadGameFP) {
10662         int offset = gameNumber - lastLoadGameNumber;
10663         if (offset == 0) {
10664             cmailMsg[0] = NULLCHAR;
10665             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10666                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10667                 nCmailMovesRegistered--;
10668             }
10669             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10670             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10671                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10672             }
10673         } else {
10674             if (! RegisterMove()) return FALSE;
10675         }
10676     }
10677
10678     retVal = LoadGame(f, gameNumber, title, useList);
10679
10680     /* Make move registered during previous look at this game, if any */
10681     MakeRegisteredMove();
10682
10683     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10684         commentList[currentMove]
10685           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10686         DisplayComment(currentMove - 1, commentList[currentMove]);
10687     }
10688
10689     return retVal;
10690 }
10691
10692 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10693 int
10694 ReloadGame(offset)
10695      int offset;
10696 {
10697     int gameNumber = lastLoadGameNumber + offset;
10698     if (lastLoadGameFP == NULL) {
10699         DisplayError(_("No game has been loaded yet"), 0);
10700         return FALSE;
10701     }
10702     if (gameNumber <= 0) {
10703         DisplayError(_("Can't back up any further"), 0);
10704         return FALSE;
10705     }
10706     if (cmailMsgLoaded) {
10707         return CmailLoadGame(lastLoadGameFP, gameNumber,
10708                              lastLoadGameTitle, lastLoadGameUseList);
10709     } else {
10710         return LoadGame(lastLoadGameFP, gameNumber,
10711                         lastLoadGameTitle, lastLoadGameUseList);
10712     }
10713 }
10714
10715
10716
10717 /* Load the nth game from open file f */
10718 int
10719 LoadGame(f, gameNumber, title, useList)
10720      FILE *f;
10721      int gameNumber;
10722      char *title;
10723      int useList;
10724 {
10725     ChessMove cm;
10726     char buf[MSG_SIZ];
10727     int gn = gameNumber;
10728     ListGame *lg = NULL;
10729     int numPGNTags = 0;
10730     int err;
10731     GameMode oldGameMode;
10732     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10733
10734     if (appData.debugMode)
10735         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10736
10737     if (gameMode == Training )
10738         SetTrainingModeOff();
10739
10740     oldGameMode = gameMode;
10741     if (gameMode != BeginningOfGame) {
10742       Reset(FALSE, TRUE);
10743     }
10744
10745     gameFileFP = f;
10746     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10747         fclose(lastLoadGameFP);
10748     }
10749
10750     if (useList) {
10751         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10752
10753         if (lg) {
10754             fseek(f, lg->offset, 0);
10755             GameListHighlight(gameNumber);
10756             gn = 1;
10757         }
10758         else {
10759             DisplayError(_("Game number out of range"), 0);
10760             return FALSE;
10761         }
10762     } else {
10763         GameListDestroy();
10764         if (fseek(f, 0, 0) == -1) {
10765             if (f == lastLoadGameFP ?
10766                 gameNumber == lastLoadGameNumber + 1 :
10767                 gameNumber == 1) {
10768                 gn = 1;
10769             } else {
10770                 DisplayError(_("Can't seek on game file"), 0);
10771                 return FALSE;
10772             }
10773         }
10774     }
10775     lastLoadGameFP = f;
10776     lastLoadGameNumber = gameNumber;
10777     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10778     lastLoadGameUseList = useList;
10779
10780     yynewfile(f);
10781
10782     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10783       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10784                 lg->gameInfo.black);
10785             DisplayTitle(buf);
10786     } else if (*title != NULLCHAR) {
10787         if (gameNumber > 1) {
10788           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10789             DisplayTitle(buf);
10790         } else {
10791             DisplayTitle(title);
10792         }
10793     }
10794
10795     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10796         gameMode = PlayFromGameFile;
10797         ModeHighlight();
10798     }
10799
10800     currentMove = forwardMostMove = backwardMostMove = 0;
10801     CopyBoard(boards[0], initialPosition);
10802     StopClocks();
10803
10804     /*
10805      * Skip the first gn-1 games in the file.
10806      * Also skip over anything that precedes an identifiable
10807      * start of game marker, to avoid being confused by
10808      * garbage at the start of the file.  Currently
10809      * recognized start of game markers are the move number "1",
10810      * the pattern "gnuchess .* game", the pattern
10811      * "^[#;%] [^ ]* game file", and a PGN tag block.
10812      * A game that starts with one of the latter two patterns
10813      * will also have a move number 1, possibly
10814      * following a position diagram.
10815      * 5-4-02: Let's try being more lenient and allowing a game to
10816      * start with an unnumbered move.  Does that break anything?
10817      */
10818     cm = lastLoadGameStart = EndOfFile;
10819     while (gn > 0) {
10820         yyboardindex = forwardMostMove;
10821         cm = (ChessMove) Myylex();
10822         switch (cm) {
10823           case EndOfFile:
10824             if (cmailMsgLoaded) {
10825                 nCmailGames = CMAIL_MAX_GAMES - gn;
10826             } else {
10827                 Reset(TRUE, TRUE);
10828                 DisplayError(_("Game not found in file"), 0);
10829             }
10830             return FALSE;
10831
10832           case GNUChessGame:
10833           case XBoardGame:
10834             gn--;
10835             lastLoadGameStart = cm;
10836             break;
10837
10838           case MoveNumberOne:
10839             switch (lastLoadGameStart) {
10840               case GNUChessGame:
10841               case XBoardGame:
10842               case PGNTag:
10843                 break;
10844               case MoveNumberOne:
10845               case EndOfFile:
10846                 gn--;           /* count this game */
10847                 lastLoadGameStart = cm;
10848                 break;
10849               default:
10850                 /* impossible */
10851                 break;
10852             }
10853             break;
10854
10855           case PGNTag:
10856             switch (lastLoadGameStart) {
10857               case GNUChessGame:
10858               case PGNTag:
10859               case MoveNumberOne:
10860               case EndOfFile:
10861                 gn--;           /* count this game */
10862                 lastLoadGameStart = cm;
10863                 break;
10864               case XBoardGame:
10865                 lastLoadGameStart = cm; /* game counted already */
10866                 break;
10867               default:
10868                 /* impossible */
10869                 break;
10870             }
10871             if (gn > 0) {
10872                 do {
10873                     yyboardindex = forwardMostMove;
10874                     cm = (ChessMove) Myylex();
10875                 } while (cm == PGNTag || cm == Comment);
10876             }
10877             break;
10878
10879           case WhiteWins:
10880           case BlackWins:
10881           case GameIsDrawn:
10882             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10883                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10884                     != CMAIL_OLD_RESULT) {
10885                     nCmailResults ++ ;
10886                     cmailResult[  CMAIL_MAX_GAMES
10887                                 - gn - 1] = CMAIL_OLD_RESULT;
10888                 }
10889             }
10890             break;
10891
10892           case NormalMove:
10893             /* Only a NormalMove can be at the start of a game
10894              * without a position diagram. */
10895             if (lastLoadGameStart == EndOfFile ) {
10896               gn--;
10897               lastLoadGameStart = MoveNumberOne;
10898             }
10899             break;
10900
10901           default:
10902             break;
10903         }
10904     }
10905
10906     if (appData.debugMode)
10907       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10908
10909     if (cm == XBoardGame) {
10910         /* Skip any header junk before position diagram and/or move 1 */
10911         for (;;) {
10912             yyboardindex = forwardMostMove;
10913             cm = (ChessMove) Myylex();
10914
10915             if (cm == EndOfFile ||
10916                 cm == GNUChessGame || cm == XBoardGame) {
10917                 /* Empty game; pretend end-of-file and handle later */
10918                 cm = EndOfFile;
10919                 break;
10920             }
10921
10922             if (cm == MoveNumberOne || cm == PositionDiagram ||
10923                 cm == PGNTag || cm == Comment)
10924               break;
10925         }
10926     } else if (cm == GNUChessGame) {
10927         if (gameInfo.event != NULL) {
10928             free(gameInfo.event);
10929         }
10930         gameInfo.event = StrSave(yy_text);
10931     }
10932
10933     startedFromSetupPosition = FALSE;
10934     while (cm == PGNTag) {
10935         if (appData.debugMode)
10936           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10937         err = ParsePGNTag(yy_text, &gameInfo);
10938         if (!err) numPGNTags++;
10939
10940         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10941         if(gameInfo.variant != oldVariant) {
10942             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10943             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10944             InitPosition(TRUE);
10945             oldVariant = gameInfo.variant;
10946             if (appData.debugMode)
10947               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10948         }
10949
10950
10951         if (gameInfo.fen != NULL) {
10952           Board initial_position;
10953           startedFromSetupPosition = TRUE;
10954           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10955             Reset(TRUE, TRUE);
10956             DisplayError(_("Bad FEN position in file"), 0);
10957             return FALSE;
10958           }
10959           CopyBoard(boards[0], initial_position);
10960           if (blackPlaysFirst) {
10961             currentMove = forwardMostMove = backwardMostMove = 1;
10962             CopyBoard(boards[1], initial_position);
10963             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10964             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10965             timeRemaining[0][1] = whiteTimeRemaining;
10966             timeRemaining[1][1] = blackTimeRemaining;
10967             if (commentList[0] != NULL) {
10968               commentList[1] = commentList[0];
10969               commentList[0] = NULL;
10970             }
10971           } else {
10972             currentMove = forwardMostMove = backwardMostMove = 0;
10973           }
10974           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10975           {   int i;
10976               initialRulePlies = FENrulePlies;
10977               for( i=0; i< nrCastlingRights; i++ )
10978                   initialRights[i] = initial_position[CASTLING][i];
10979           }
10980           yyboardindex = forwardMostMove;
10981           free(gameInfo.fen);
10982           gameInfo.fen = NULL;
10983         }
10984
10985         yyboardindex = forwardMostMove;
10986         cm = (ChessMove) Myylex();
10987
10988         /* Handle comments interspersed among the tags */
10989         while (cm == Comment) {
10990             char *p;
10991             if (appData.debugMode)
10992               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10993             p = yy_text;
10994             AppendComment(currentMove, p, FALSE);
10995             yyboardindex = forwardMostMove;
10996             cm = (ChessMove) Myylex();
10997         }
10998     }
10999
11000     /* don't rely on existence of Event tag since if game was
11001      * pasted from clipboard the Event tag may not exist
11002      */
11003     if (numPGNTags > 0){
11004         char *tags;
11005         if (gameInfo.variant == VariantNormal) {
11006           VariantClass v = StringToVariant(gameInfo.event);
11007           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11008           if(v < VariantShogi) gameInfo.variant = v;
11009         }
11010         if (!matchMode) {
11011           if( appData.autoDisplayTags ) {
11012             tags = PGNTags(&gameInfo);
11013             TagsPopUp(tags, CmailMsg());
11014             free(tags);
11015           }
11016         }
11017     } else {
11018         /* Make something up, but don't display it now */
11019         SetGameInfo();
11020         TagsPopDown();
11021     }
11022
11023     if (cm == PositionDiagram) {
11024         int i, j;
11025         char *p;
11026         Board initial_position;
11027
11028         if (appData.debugMode)
11029           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11030
11031         if (!startedFromSetupPosition) {
11032             p = yy_text;
11033             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11034               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11035                 switch (*p) {
11036                   case '{':
11037                   case '[':
11038                   case '-':
11039                   case ' ':
11040                   case '\t':
11041                   case '\n':
11042                   case '\r':
11043                     break;
11044                   default:
11045                     initial_position[i][j++] = CharToPiece(*p);
11046                     break;
11047                 }
11048             while (*p == ' ' || *p == '\t' ||
11049                    *p == '\n' || *p == '\r') p++;
11050
11051             if (strncmp(p, "black", strlen("black"))==0)
11052               blackPlaysFirst = TRUE;
11053             else
11054               blackPlaysFirst = FALSE;
11055             startedFromSetupPosition = TRUE;
11056
11057             CopyBoard(boards[0], initial_position);
11058             if (blackPlaysFirst) {
11059                 currentMove = forwardMostMove = backwardMostMove = 1;
11060                 CopyBoard(boards[1], initial_position);
11061                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11062                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11063                 timeRemaining[0][1] = whiteTimeRemaining;
11064                 timeRemaining[1][1] = blackTimeRemaining;
11065                 if (commentList[0] != NULL) {
11066                     commentList[1] = commentList[0];
11067                     commentList[0] = NULL;
11068                 }
11069             } else {
11070                 currentMove = forwardMostMove = backwardMostMove = 0;
11071             }
11072         }
11073         yyboardindex = forwardMostMove;
11074         cm = (ChessMove) Myylex();
11075     }
11076
11077     if (first.pr == NoProc) {
11078         StartChessProgram(&first);
11079     }
11080     InitChessProgram(&first, FALSE);
11081     SendToProgram("force\n", &first);
11082     if (startedFromSetupPosition) {
11083         SendBoard(&first, forwardMostMove);
11084     if (appData.debugMode) {
11085         fprintf(debugFP, "Load Game\n");
11086     }
11087         DisplayBothClocks();
11088     }
11089
11090     /* [HGM] server: flag to write setup moves in broadcast file as one */
11091     loadFlag = appData.suppressLoadMoves;
11092
11093     while (cm == Comment) {
11094         char *p;
11095         if (appData.debugMode)
11096           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11097         p = yy_text;
11098         AppendComment(currentMove, p, FALSE);
11099         yyboardindex = forwardMostMove;
11100         cm = (ChessMove) Myylex();
11101     }
11102
11103     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11104         cm == WhiteWins || cm == BlackWins ||
11105         cm == GameIsDrawn || cm == GameUnfinished) {
11106         DisplayMessage("", _("No moves in game"));
11107         if (cmailMsgLoaded) {
11108             if (appData.debugMode)
11109               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11110             ClearHighlights();
11111             flipView = FALSE;
11112         }
11113         DrawPosition(FALSE, boards[currentMove]);
11114         DisplayBothClocks();
11115         gameMode = EditGame;
11116         ModeHighlight();
11117         gameFileFP = NULL;
11118         cmailOldMove = 0;
11119         return TRUE;
11120     }
11121
11122     // [HGM] PV info: routine tests if comment empty
11123     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11124         DisplayComment(currentMove - 1, commentList[currentMove]);
11125     }
11126     if (!matchMode && appData.timeDelay != 0)
11127       DrawPosition(FALSE, boards[currentMove]);
11128
11129     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11130       programStats.ok_to_send = 1;
11131     }
11132
11133     /* if the first token after the PGN tags is a move
11134      * and not move number 1, retrieve it from the parser
11135      */
11136     if (cm != MoveNumberOne)
11137         LoadGameOneMove(cm);
11138
11139     /* load the remaining moves from the file */
11140     while (LoadGameOneMove(EndOfFile)) {
11141       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11142       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11143     }
11144
11145     /* rewind to the start of the game */
11146     currentMove = backwardMostMove;
11147
11148     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11149
11150     if (oldGameMode == AnalyzeFile ||
11151         oldGameMode == AnalyzeMode) {
11152       AnalyzeFileEvent();
11153     }
11154
11155     if (matchMode || appData.timeDelay == 0) {
11156       ToEndEvent();
11157       gameMode = EditGame;
11158       ModeHighlight();
11159     } else if (appData.timeDelay > 0) {
11160       AutoPlayGameLoop();
11161     }
11162
11163     if (appData.debugMode)
11164         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11165
11166     loadFlag = 0; /* [HGM] true game starts */
11167     return TRUE;
11168 }
11169
11170 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11171 int
11172 ReloadPosition(offset)
11173      int offset;
11174 {
11175     int positionNumber = lastLoadPositionNumber + offset;
11176     if (lastLoadPositionFP == NULL) {
11177         DisplayError(_("No position has been loaded yet"), 0);
11178         return FALSE;
11179     }
11180     if (positionNumber <= 0) {
11181         DisplayError(_("Can't back up any further"), 0);
11182         return FALSE;
11183     }
11184     return LoadPosition(lastLoadPositionFP, positionNumber,
11185                         lastLoadPositionTitle);
11186 }
11187
11188 /* Load the nth position from the given file */
11189 int
11190 LoadPositionFromFile(filename, n, title)
11191      char *filename;
11192      int n;
11193      char *title;
11194 {
11195     FILE *f;
11196     char buf[MSG_SIZ];
11197
11198     if (strcmp(filename, "-") == 0) {
11199         return LoadPosition(stdin, n, "stdin");
11200     } else {
11201         f = fopen(filename, "rb");
11202         if (f == NULL) {
11203             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11204             DisplayError(buf, errno);
11205             return FALSE;
11206         } else {
11207             return LoadPosition(f, n, title);
11208         }
11209     }
11210 }
11211
11212 /* Load the nth position from the given open file, and close it */
11213 int
11214 LoadPosition(f, positionNumber, title)
11215      FILE *f;
11216      int positionNumber;
11217      char *title;
11218 {
11219     char *p, line[MSG_SIZ];
11220     Board initial_position;
11221     int i, j, fenMode, pn;
11222
11223     if (gameMode == Training )
11224         SetTrainingModeOff();
11225
11226     if (gameMode != BeginningOfGame) {
11227         Reset(FALSE, TRUE);
11228     }
11229     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11230         fclose(lastLoadPositionFP);
11231     }
11232     if (positionNumber == 0) positionNumber = 1;
11233     lastLoadPositionFP = f;
11234     lastLoadPositionNumber = positionNumber;
11235     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11236     if (first.pr == NoProc) {
11237       StartChessProgram(&first);
11238       InitChessProgram(&first, FALSE);
11239     }
11240     pn = positionNumber;
11241     if (positionNumber < 0) {
11242         /* Negative position number means to seek to that byte offset */
11243         if (fseek(f, -positionNumber, 0) == -1) {
11244             DisplayError(_("Can't seek on position file"), 0);
11245             return FALSE;
11246         };
11247         pn = 1;
11248     } else {
11249         if (fseek(f, 0, 0) == -1) {
11250             if (f == lastLoadPositionFP ?
11251                 positionNumber == lastLoadPositionNumber + 1 :
11252                 positionNumber == 1) {
11253                 pn = 1;
11254             } else {
11255                 DisplayError(_("Can't seek on position file"), 0);
11256                 return FALSE;
11257             }
11258         }
11259     }
11260     /* See if this file is FEN or old-style xboard */
11261     if (fgets(line, MSG_SIZ, f) == NULL) {
11262         DisplayError(_("Position not found in file"), 0);
11263         return FALSE;
11264     }
11265     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11266     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11267
11268     if (pn >= 2) {
11269         if (fenMode || line[0] == '#') pn--;
11270         while (pn > 0) {
11271             /* skip positions before number pn */
11272             if (fgets(line, MSG_SIZ, f) == NULL) {
11273                 Reset(TRUE, TRUE);
11274                 DisplayError(_("Position not found in file"), 0);
11275                 return FALSE;
11276             }
11277             if (fenMode || line[0] == '#') pn--;
11278         }
11279     }
11280
11281     if (fenMode) {
11282         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11283             DisplayError(_("Bad FEN position in file"), 0);
11284             return FALSE;
11285         }
11286     } else {
11287         (void) fgets(line, MSG_SIZ, f);
11288         (void) fgets(line, MSG_SIZ, f);
11289
11290         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11291             (void) fgets(line, MSG_SIZ, f);
11292             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11293                 if (*p == ' ')
11294                   continue;
11295                 initial_position[i][j++] = CharToPiece(*p);
11296             }
11297         }
11298
11299         blackPlaysFirst = FALSE;
11300         if (!feof(f)) {
11301             (void) fgets(line, MSG_SIZ, f);
11302             if (strncmp(line, "black", strlen("black"))==0)
11303               blackPlaysFirst = TRUE;
11304         }
11305     }
11306     startedFromSetupPosition = TRUE;
11307
11308     SendToProgram("force\n", &first);
11309     CopyBoard(boards[0], initial_position);
11310     if (blackPlaysFirst) {
11311         currentMove = forwardMostMove = backwardMostMove = 1;
11312         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11313         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11314         CopyBoard(boards[1], initial_position);
11315         DisplayMessage("", _("Black to play"));
11316     } else {
11317         currentMove = forwardMostMove = backwardMostMove = 0;
11318         DisplayMessage("", _("White to play"));
11319     }
11320     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11321     SendBoard(&first, forwardMostMove);
11322     if (appData.debugMode) {
11323 int i, j;
11324   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11325   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11326         fprintf(debugFP, "Load Position\n");
11327     }
11328
11329     if (positionNumber > 1) {
11330       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11331         DisplayTitle(line);
11332     } else {
11333         DisplayTitle(title);
11334     }
11335     gameMode = EditGame;
11336     ModeHighlight();
11337     ResetClocks();
11338     timeRemaining[0][1] = whiteTimeRemaining;
11339     timeRemaining[1][1] = blackTimeRemaining;
11340     DrawPosition(FALSE, boards[currentMove]);
11341
11342     return TRUE;
11343 }
11344
11345
11346 void
11347 CopyPlayerNameIntoFileName(dest, src)
11348      char **dest, *src;
11349 {
11350     while (*src != NULLCHAR && *src != ',') {
11351         if (*src == ' ') {
11352             *(*dest)++ = '_';
11353             src++;
11354         } else {
11355             *(*dest)++ = *src++;
11356         }
11357     }
11358 }
11359
11360 char *DefaultFileName(ext)
11361      char *ext;
11362 {
11363     static char def[MSG_SIZ];
11364     char *p;
11365
11366     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11367         p = def;
11368         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11369         *p++ = '-';
11370         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11371         *p++ = '.';
11372         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11373     } else {
11374         def[0] = NULLCHAR;
11375     }
11376     return def;
11377 }
11378
11379 /* Save the current game to the given file */
11380 int
11381 SaveGameToFile(filename, append)
11382      char *filename;
11383      int append;
11384 {
11385     FILE *f;
11386     char buf[MSG_SIZ];
11387     int result;
11388
11389     if (strcmp(filename, "-") == 0) {
11390         return SaveGame(stdout, 0, NULL);
11391     } else {
11392         f = fopen(filename, append ? "a" : "w");
11393         if (f == NULL) {
11394             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11395             DisplayError(buf, errno);
11396             return FALSE;
11397         } else {
11398             safeStrCpy(buf, lastMsg, MSG_SIZ);
11399             DisplayMessage(_("Waiting for access to save file"), "");
11400             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11401             DisplayMessage(_("Saving game"), "");
11402             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11403             result = SaveGame(f, 0, NULL);
11404             DisplayMessage(buf, "");
11405             return result;
11406         }
11407     }
11408 }
11409
11410 char *
11411 SavePart(str)
11412      char *str;
11413 {
11414     static char buf[MSG_SIZ];
11415     char *p;
11416
11417     p = strchr(str, ' ');
11418     if (p == NULL) return str;
11419     strncpy(buf, str, p - str);
11420     buf[p - str] = NULLCHAR;
11421     return buf;
11422 }
11423
11424 #define PGN_MAX_LINE 75
11425
11426 #define PGN_SIDE_WHITE  0
11427 #define PGN_SIDE_BLACK  1
11428
11429 /* [AS] */
11430 static int FindFirstMoveOutOfBook( int side )
11431 {
11432     int result = -1;
11433
11434     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11435         int index = backwardMostMove;
11436         int has_book_hit = 0;
11437
11438         if( (index % 2) != side ) {
11439             index++;
11440         }
11441
11442         while( index < forwardMostMove ) {
11443             /* Check to see if engine is in book */
11444             int depth = pvInfoList[index].depth;
11445             int score = pvInfoList[index].score;
11446             int in_book = 0;
11447
11448             if( depth <= 2 ) {
11449                 in_book = 1;
11450             }
11451             else if( score == 0 && depth == 63 ) {
11452                 in_book = 1; /* Zappa */
11453             }
11454             else if( score == 2 && depth == 99 ) {
11455                 in_book = 1; /* Abrok */
11456             }
11457
11458             has_book_hit += in_book;
11459
11460             if( ! in_book ) {
11461                 result = index;
11462
11463                 break;
11464             }
11465
11466             index += 2;
11467         }
11468     }
11469
11470     return result;
11471 }
11472
11473 /* [AS] */
11474 void GetOutOfBookInfo( char * buf )
11475 {
11476     int oob[2];
11477     int i;
11478     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11479
11480     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11481     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11482
11483     *buf = '\0';
11484
11485     if( oob[0] >= 0 || oob[1] >= 0 ) {
11486         for( i=0; i<2; i++ ) {
11487             int idx = oob[i];
11488
11489             if( idx >= 0 ) {
11490                 if( i > 0 && oob[0] >= 0 ) {
11491                     strcat( buf, "   " );
11492                 }
11493
11494                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11495                 sprintf( buf+strlen(buf), "%s%.2f",
11496                     pvInfoList[idx].score >= 0 ? "+" : "",
11497                     pvInfoList[idx].score / 100.0 );
11498             }
11499         }
11500     }
11501 }
11502
11503 /* Save game in PGN style and close the file */
11504 int
11505 SaveGamePGN(f)
11506      FILE *f;
11507 {
11508     int i, offset, linelen, newblock;
11509     time_t tm;
11510 //    char *movetext;
11511     char numtext[32];
11512     int movelen, numlen, blank;
11513     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11514
11515     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11516
11517     tm = time((time_t *) NULL);
11518
11519     PrintPGNTags(f, &gameInfo);
11520
11521     if (backwardMostMove > 0 || startedFromSetupPosition) {
11522         char *fen = PositionToFEN(backwardMostMove, NULL);
11523         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11524         fprintf(f, "\n{--------------\n");
11525         PrintPosition(f, backwardMostMove);
11526         fprintf(f, "--------------}\n");
11527         free(fen);
11528     }
11529     else {
11530         /* [AS] Out of book annotation */
11531         if( appData.saveOutOfBookInfo ) {
11532             char buf[64];
11533
11534             GetOutOfBookInfo( buf );
11535
11536             if( buf[0] != '\0' ) {
11537                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11538             }
11539         }
11540
11541         fprintf(f, "\n");
11542     }
11543
11544     i = backwardMostMove;
11545     linelen = 0;
11546     newblock = TRUE;
11547
11548     while (i < forwardMostMove) {
11549         /* Print comments preceding this move */
11550         if (commentList[i] != NULL) {
11551             if (linelen > 0) fprintf(f, "\n");
11552             fprintf(f, "%s", commentList[i]);
11553             linelen = 0;
11554             newblock = TRUE;
11555         }
11556
11557         /* Format move number */
11558         if ((i % 2) == 0)
11559           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11560         else
11561           if (newblock)
11562             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11563           else
11564             numtext[0] = NULLCHAR;
11565
11566         numlen = strlen(numtext);
11567         newblock = FALSE;
11568
11569         /* Print move number */
11570         blank = linelen > 0 && numlen > 0;
11571         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11572             fprintf(f, "\n");
11573             linelen = 0;
11574             blank = 0;
11575         }
11576         if (blank) {
11577             fprintf(f, " ");
11578             linelen++;
11579         }
11580         fprintf(f, "%s", numtext);
11581         linelen += numlen;
11582
11583         /* Get move */
11584         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11585         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11586
11587         /* Print move */
11588         blank = linelen > 0 && movelen > 0;
11589         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11590             fprintf(f, "\n");
11591             linelen = 0;
11592             blank = 0;
11593         }
11594         if (blank) {
11595             fprintf(f, " ");
11596             linelen++;
11597         }
11598         fprintf(f, "%s", move_buffer);
11599         linelen += movelen;
11600
11601         /* [AS] Add PV info if present */
11602         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11603             /* [HGM] add time */
11604             char buf[MSG_SIZ]; int seconds;
11605
11606             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11607
11608             if( seconds <= 0)
11609               buf[0] = 0;
11610             else
11611               if( seconds < 30 )
11612                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11613               else
11614                 {
11615                   seconds = (seconds + 4)/10; // round to full seconds
11616                   if( seconds < 60 )
11617                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11618                   else
11619                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11620                 }
11621
11622             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11623                       pvInfoList[i].score >= 0 ? "+" : "",
11624                       pvInfoList[i].score / 100.0,
11625                       pvInfoList[i].depth,
11626                       buf );
11627
11628             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11629
11630             /* Print score/depth */
11631             blank = linelen > 0 && movelen > 0;
11632             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11633                 fprintf(f, "\n");
11634                 linelen = 0;
11635                 blank = 0;
11636             }
11637             if (blank) {
11638                 fprintf(f, " ");
11639                 linelen++;
11640             }
11641             fprintf(f, "%s", move_buffer);
11642             linelen += movelen;
11643         }
11644
11645         i++;
11646     }
11647
11648     /* Start a new line */
11649     if (linelen > 0) fprintf(f, "\n");
11650
11651     /* Print comments after last move */
11652     if (commentList[i] != NULL) {
11653         fprintf(f, "%s\n", commentList[i]);
11654     }
11655
11656     /* Print result */
11657     if (gameInfo.resultDetails != NULL &&
11658         gameInfo.resultDetails[0] != NULLCHAR) {
11659         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11660                 PGNResult(gameInfo.result));
11661     } else {
11662         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11663     }
11664
11665     fclose(f);
11666     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11667     return TRUE;
11668 }
11669
11670 /* Save game in old style and close the file */
11671 int
11672 SaveGameOldStyle(f)
11673      FILE *f;
11674 {
11675     int i, offset;
11676     time_t tm;
11677
11678     tm = time((time_t *) NULL);
11679
11680     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11681     PrintOpponents(f);
11682
11683     if (backwardMostMove > 0 || startedFromSetupPosition) {
11684         fprintf(f, "\n[--------------\n");
11685         PrintPosition(f, backwardMostMove);
11686         fprintf(f, "--------------]\n");
11687     } else {
11688         fprintf(f, "\n");
11689     }
11690
11691     i = backwardMostMove;
11692     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11693
11694     while (i < forwardMostMove) {
11695         if (commentList[i] != NULL) {
11696             fprintf(f, "[%s]\n", commentList[i]);
11697         }
11698
11699         if ((i % 2) == 1) {
11700             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11701             i++;
11702         } else {
11703             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11704             i++;
11705             if (commentList[i] != NULL) {
11706                 fprintf(f, "\n");
11707                 continue;
11708             }
11709             if (i >= forwardMostMove) {
11710                 fprintf(f, "\n");
11711                 break;
11712             }
11713             fprintf(f, "%s\n", parseList[i]);
11714             i++;
11715         }
11716     }
11717
11718     if (commentList[i] != NULL) {
11719         fprintf(f, "[%s]\n", commentList[i]);
11720     }
11721
11722     /* This isn't really the old style, but it's close enough */
11723     if (gameInfo.resultDetails != NULL &&
11724         gameInfo.resultDetails[0] != NULLCHAR) {
11725         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11726                 gameInfo.resultDetails);
11727     } else {
11728         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11729     }
11730
11731     fclose(f);
11732     return TRUE;
11733 }
11734
11735 /* Save the current game to open file f and close the file */
11736 int
11737 SaveGame(f, dummy, dummy2)
11738      FILE *f;
11739      int dummy;
11740      char *dummy2;
11741 {
11742     if (gameMode == EditPosition) EditPositionDone(TRUE);
11743     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11744     if (appData.oldSaveStyle)
11745       return SaveGameOldStyle(f);
11746     else
11747       return SaveGamePGN(f);
11748 }
11749
11750 /* Save the current position to the given file */
11751 int
11752 SavePositionToFile(filename)
11753      char *filename;
11754 {
11755     FILE *f;
11756     char buf[MSG_SIZ];
11757
11758     if (strcmp(filename, "-") == 0) {
11759         return SavePosition(stdout, 0, NULL);
11760     } else {
11761         f = fopen(filename, "a");
11762         if (f == NULL) {
11763             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11764             DisplayError(buf, errno);
11765             return FALSE;
11766         } else {
11767             safeStrCpy(buf, lastMsg, MSG_SIZ);
11768             DisplayMessage(_("Waiting for access to save file"), "");
11769             flock(fileno(f), LOCK_EX); // [HGM] lock
11770             DisplayMessage(_("Saving position"), "");
11771             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11772             SavePosition(f, 0, NULL);
11773             DisplayMessage(buf, "");
11774             return TRUE;
11775         }
11776     }
11777 }
11778
11779 /* Save the current position to the given open file and close the file */
11780 int
11781 SavePosition(f, dummy, dummy2)
11782      FILE *f;
11783      int dummy;
11784      char *dummy2;
11785 {
11786     time_t tm;
11787     char *fen;
11788
11789     if (gameMode == EditPosition) EditPositionDone(TRUE);
11790     if (appData.oldSaveStyle) {
11791         tm = time((time_t *) NULL);
11792
11793         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11794         PrintOpponents(f);
11795         fprintf(f, "[--------------\n");
11796         PrintPosition(f, currentMove);
11797         fprintf(f, "--------------]\n");
11798     } else {
11799         fen = PositionToFEN(currentMove, NULL);
11800         fprintf(f, "%s\n", fen);
11801         free(fen);
11802     }
11803     fclose(f);
11804     return TRUE;
11805 }
11806
11807 void
11808 ReloadCmailMsgEvent(unregister)
11809      int unregister;
11810 {
11811 #if !WIN32
11812     static char *inFilename = NULL;
11813     static char *outFilename;
11814     int i;
11815     struct stat inbuf, outbuf;
11816     int status;
11817
11818     /* Any registered moves are unregistered if unregister is set, */
11819     /* i.e. invoked by the signal handler */
11820     if (unregister) {
11821         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11822             cmailMoveRegistered[i] = FALSE;
11823             if (cmailCommentList[i] != NULL) {
11824                 free(cmailCommentList[i]);
11825                 cmailCommentList[i] = NULL;
11826             }
11827         }
11828         nCmailMovesRegistered = 0;
11829     }
11830
11831     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11832         cmailResult[i] = CMAIL_NOT_RESULT;
11833     }
11834     nCmailResults = 0;
11835
11836     if (inFilename == NULL) {
11837         /* Because the filenames are static they only get malloced once  */
11838         /* and they never get freed                                      */
11839         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11840         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11841
11842         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11843         sprintf(outFilename, "%s.out", appData.cmailGameName);
11844     }
11845
11846     status = stat(outFilename, &outbuf);
11847     if (status < 0) {
11848         cmailMailedMove = FALSE;
11849     } else {
11850         status = stat(inFilename, &inbuf);
11851         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11852     }
11853
11854     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11855        counts the games, notes how each one terminated, etc.
11856
11857        It would be nice to remove this kludge and instead gather all
11858        the information while building the game list.  (And to keep it
11859        in the game list nodes instead of having a bunch of fixed-size
11860        parallel arrays.)  Note this will require getting each game's
11861        termination from the PGN tags, as the game list builder does
11862        not process the game moves.  --mann
11863        */
11864     cmailMsgLoaded = TRUE;
11865     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11866
11867     /* Load first game in the file or popup game menu */
11868     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11869
11870 #endif /* !WIN32 */
11871     return;
11872 }
11873
11874 int
11875 RegisterMove()
11876 {
11877     FILE *f;
11878     char string[MSG_SIZ];
11879
11880     if (   cmailMailedMove
11881         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11882         return TRUE;            /* Allow free viewing  */
11883     }
11884
11885     /* Unregister move to ensure that we don't leave RegisterMove        */
11886     /* with the move registered when the conditions for registering no   */
11887     /* longer hold                                                       */
11888     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11889         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11890         nCmailMovesRegistered --;
11891
11892         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11893           {
11894               free(cmailCommentList[lastLoadGameNumber - 1]);
11895               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11896           }
11897     }
11898
11899     if (cmailOldMove == -1) {
11900         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11901         return FALSE;
11902     }
11903
11904     if (currentMove > cmailOldMove + 1) {
11905         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11906         return FALSE;
11907     }
11908
11909     if (currentMove < cmailOldMove) {
11910         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11911         return FALSE;
11912     }
11913
11914     if (forwardMostMove > currentMove) {
11915         /* Silently truncate extra moves */
11916         TruncateGame();
11917     }
11918
11919     if (   (currentMove == cmailOldMove + 1)
11920         || (   (currentMove == cmailOldMove)
11921             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11922                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11923         if (gameInfo.result != GameUnfinished) {
11924             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11925         }
11926
11927         if (commentList[currentMove] != NULL) {
11928             cmailCommentList[lastLoadGameNumber - 1]
11929               = StrSave(commentList[currentMove]);
11930         }
11931         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11932
11933         if (appData.debugMode)
11934           fprintf(debugFP, "Saving %s for game %d\n",
11935                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11936
11937         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11938
11939         f = fopen(string, "w");
11940         if (appData.oldSaveStyle) {
11941             SaveGameOldStyle(f); /* also closes the file */
11942
11943             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11944             f = fopen(string, "w");
11945             SavePosition(f, 0, NULL); /* also closes the file */
11946         } else {
11947             fprintf(f, "{--------------\n");
11948             PrintPosition(f, currentMove);
11949             fprintf(f, "--------------}\n\n");
11950
11951             SaveGame(f, 0, NULL); /* also closes the file*/
11952         }
11953
11954         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11955         nCmailMovesRegistered ++;
11956     } else if (nCmailGames == 1) {
11957         DisplayError(_("You have not made a move yet"), 0);
11958         return FALSE;
11959     }
11960
11961     return TRUE;
11962 }
11963
11964 void
11965 MailMoveEvent()
11966 {
11967 #if !WIN32
11968     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11969     FILE *commandOutput;
11970     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11971     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11972     int nBuffers;
11973     int i;
11974     int archived;
11975     char *arcDir;
11976
11977     if (! cmailMsgLoaded) {
11978         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11979         return;
11980     }
11981
11982     if (nCmailGames == nCmailResults) {
11983         DisplayError(_("No unfinished games"), 0);
11984         return;
11985     }
11986
11987 #if CMAIL_PROHIBIT_REMAIL
11988     if (cmailMailedMove) {
11989       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);
11990         DisplayError(msg, 0);
11991         return;
11992     }
11993 #endif
11994
11995     if (! (cmailMailedMove || RegisterMove())) return;
11996
11997     if (   cmailMailedMove
11998         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11999       snprintf(string, MSG_SIZ, partCommandString,
12000                appData.debugMode ? " -v" : "", appData.cmailGameName);
12001         commandOutput = popen(string, "r");
12002
12003         if (commandOutput == NULL) {
12004             DisplayError(_("Failed to invoke cmail"), 0);
12005         } else {
12006             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12007                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12008             }
12009             if (nBuffers > 1) {
12010                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12011                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12012                 nBytes = MSG_SIZ - 1;
12013             } else {
12014                 (void) memcpy(msg, buffer, nBytes);
12015             }
12016             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12017
12018             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12019                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12020
12021                 archived = TRUE;
12022                 for (i = 0; i < nCmailGames; i ++) {
12023                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12024                         archived = FALSE;
12025                     }
12026                 }
12027                 if (   archived
12028                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12029                         != NULL)) {
12030                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12031                            arcDir,
12032                            appData.cmailGameName,
12033                            gameInfo.date);
12034                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12035                     cmailMsgLoaded = FALSE;
12036                 }
12037             }
12038
12039             DisplayInformation(msg);
12040             pclose(commandOutput);
12041         }
12042     } else {
12043         if ((*cmailMsg) != '\0') {
12044             DisplayInformation(cmailMsg);
12045         }
12046     }
12047
12048     return;
12049 #endif /* !WIN32 */
12050 }
12051
12052 char *
12053 CmailMsg()
12054 {
12055 #if WIN32
12056     return NULL;
12057 #else
12058     int  prependComma = 0;
12059     char number[5];
12060     char string[MSG_SIZ];       /* Space for game-list */
12061     int  i;
12062
12063     if (!cmailMsgLoaded) return "";
12064
12065     if (cmailMailedMove) {
12066       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12067     } else {
12068         /* Create a list of games left */
12069       snprintf(string, MSG_SIZ, "[");
12070         for (i = 0; i < nCmailGames; i ++) {
12071             if (! (   cmailMoveRegistered[i]
12072                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12073                 if (prependComma) {
12074                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12075                 } else {
12076                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12077                     prependComma = 1;
12078                 }
12079
12080                 strcat(string, number);
12081             }
12082         }
12083         strcat(string, "]");
12084
12085         if (nCmailMovesRegistered + nCmailResults == 0) {
12086             switch (nCmailGames) {
12087               case 1:
12088                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12089                 break;
12090
12091               case 2:
12092                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12093                 break;
12094
12095               default:
12096                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12097                          nCmailGames);
12098                 break;
12099             }
12100         } else {
12101             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12102               case 1:
12103                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12104                          string);
12105                 break;
12106
12107               case 0:
12108                 if (nCmailResults == nCmailGames) {
12109                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12110                 } else {
12111                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12112                 }
12113                 break;
12114
12115               default:
12116                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12117                          string);
12118             }
12119         }
12120     }
12121     return cmailMsg;
12122 #endif /* WIN32 */
12123 }
12124
12125 void
12126 ResetGameEvent()
12127 {
12128     if (gameMode == Training)
12129       SetTrainingModeOff();
12130
12131     Reset(TRUE, TRUE);
12132     cmailMsgLoaded = FALSE;
12133     if (appData.icsActive) {
12134       SendToICS(ics_prefix);
12135       SendToICS("refresh\n");
12136     }
12137 }
12138
12139 void
12140 ExitEvent(status)
12141      int status;
12142 {
12143     exiting++;
12144     if (exiting > 2) {
12145       /* Give up on clean exit */
12146       exit(status);
12147     }
12148     if (exiting > 1) {
12149       /* Keep trying for clean exit */
12150       return;
12151     }
12152
12153     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12154
12155     if (telnetISR != NULL) {
12156       RemoveInputSource(telnetISR);
12157     }
12158     if (icsPR != NoProc) {
12159       DestroyChildProcess(icsPR, TRUE);
12160     }
12161
12162     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12163     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12164
12165     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12166     /* make sure this other one finishes before killing it!                  */
12167     if(endingGame) { int count = 0;
12168         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12169         while(endingGame && count++ < 10) DoSleep(1);
12170         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12171     }
12172
12173     /* Kill off chess programs */
12174     if (first.pr != NoProc) {
12175         ExitAnalyzeMode();
12176
12177         DoSleep( appData.delayBeforeQuit );
12178         SendToProgram("quit\n", &first);
12179         DoSleep( appData.delayAfterQuit );
12180         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12181     }
12182     if (second.pr != NoProc) {
12183         DoSleep( appData.delayBeforeQuit );
12184         SendToProgram("quit\n", &second);
12185         DoSleep( appData.delayAfterQuit );
12186         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12187     }
12188     if (first.isr != NULL) {
12189         RemoveInputSource(first.isr);
12190     }
12191     if (second.isr != NULL) {
12192         RemoveInputSource(second.isr);
12193     }
12194
12195     ShutDownFrontEnd();
12196     exit(status);
12197 }
12198
12199 void
12200 PauseEvent()
12201 {
12202     if (appData.debugMode)
12203         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12204     if (pausing) {
12205         pausing = FALSE;
12206         ModeHighlight();
12207         if (gameMode == MachinePlaysWhite ||
12208             gameMode == MachinePlaysBlack) {
12209             StartClocks();
12210         } else {
12211             DisplayBothClocks();
12212         }
12213         if (gameMode == PlayFromGameFile) {
12214             if (appData.timeDelay >= 0)
12215                 AutoPlayGameLoop();
12216         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12217             Reset(FALSE, TRUE);
12218             SendToICS(ics_prefix);
12219             SendToICS("refresh\n");
12220         } else if (currentMove < forwardMostMove) {
12221             ForwardInner(forwardMostMove);
12222         }
12223         pauseExamInvalid = FALSE;
12224     } else {
12225         switch (gameMode) {
12226           default:
12227             return;
12228           case IcsExamining:
12229             pauseExamForwardMostMove = forwardMostMove;
12230             pauseExamInvalid = FALSE;
12231             /* fall through */
12232           case IcsObserving:
12233           case IcsPlayingWhite:
12234           case IcsPlayingBlack:
12235             pausing = TRUE;
12236             ModeHighlight();
12237             return;
12238           case PlayFromGameFile:
12239             (void) StopLoadGameTimer();
12240             pausing = TRUE;
12241             ModeHighlight();
12242             break;
12243           case BeginningOfGame:
12244             if (appData.icsActive) return;
12245             /* else fall through */
12246           case MachinePlaysWhite:
12247           case MachinePlaysBlack:
12248           case TwoMachinesPlay:
12249             if (forwardMostMove == 0)
12250               return;           /* don't pause if no one has moved */
12251             if ((gameMode == MachinePlaysWhite &&
12252                  !WhiteOnMove(forwardMostMove)) ||
12253                 (gameMode == MachinePlaysBlack &&
12254                  WhiteOnMove(forwardMostMove))) {
12255                 StopClocks();
12256             }
12257             pausing = TRUE;
12258             ModeHighlight();
12259             break;
12260         }
12261     }
12262 }
12263
12264 void
12265 EditCommentEvent()
12266 {
12267     char title[MSG_SIZ];
12268
12269     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12270       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12271     } else {
12272       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12273                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12274                parseList[currentMove - 1]);
12275     }
12276
12277     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12278 }
12279
12280
12281 void
12282 EditTagsEvent()
12283 {
12284     char *tags = PGNTags(&gameInfo);
12285     EditTagsPopUp(tags, NULL);
12286     free(tags);
12287 }
12288
12289 void
12290 AnalyzeModeEvent()
12291 {
12292     if (appData.noChessProgram || gameMode == AnalyzeMode)
12293       return;
12294
12295     if (gameMode != AnalyzeFile) {
12296         if (!appData.icsEngineAnalyze) {
12297                EditGameEvent();
12298                if (gameMode != EditGame) return;
12299         }
12300         ResurrectChessProgram();
12301         SendToProgram("analyze\n", &first);
12302         first.analyzing = TRUE;
12303         /*first.maybeThinking = TRUE;*/
12304         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12305         EngineOutputPopUp();
12306     }
12307     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12308     pausing = FALSE;
12309     ModeHighlight();
12310     SetGameInfo();
12311
12312     StartAnalysisClock();
12313     GetTimeMark(&lastNodeCountTime);
12314     lastNodeCount = 0;
12315 }
12316
12317 void
12318 AnalyzeFileEvent()
12319 {
12320     if (appData.noChessProgram || gameMode == AnalyzeFile)
12321       return;
12322
12323     if (gameMode != AnalyzeMode) {
12324         EditGameEvent();
12325         if (gameMode != EditGame) return;
12326         ResurrectChessProgram();
12327         SendToProgram("analyze\n", &first);
12328         first.analyzing = TRUE;
12329         /*first.maybeThinking = TRUE;*/
12330         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12331         EngineOutputPopUp();
12332     }
12333     gameMode = AnalyzeFile;
12334     pausing = FALSE;
12335     ModeHighlight();
12336     SetGameInfo();
12337
12338     StartAnalysisClock();
12339     GetTimeMark(&lastNodeCountTime);
12340     lastNodeCount = 0;
12341 }
12342
12343 void
12344 MachineWhiteEvent()
12345 {
12346     char buf[MSG_SIZ];
12347     char *bookHit = NULL;
12348
12349     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12350       return;
12351
12352
12353     if (gameMode == PlayFromGameFile ||
12354         gameMode == TwoMachinesPlay  ||
12355         gameMode == Training         ||
12356         gameMode == AnalyzeMode      ||
12357         gameMode == EndOfGame)
12358         EditGameEvent();
12359
12360     if (gameMode == EditPosition)
12361         EditPositionDone(TRUE);
12362
12363     if (!WhiteOnMove(currentMove)) {
12364         DisplayError(_("It is not White's turn"), 0);
12365         return;
12366     }
12367
12368     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12369       ExitAnalyzeMode();
12370
12371     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12372         gameMode == AnalyzeFile)
12373         TruncateGame();
12374
12375     ResurrectChessProgram();    /* in case it isn't running */
12376     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12377         gameMode = MachinePlaysWhite;
12378         ResetClocks();
12379     } else
12380     gameMode = MachinePlaysWhite;
12381     pausing = FALSE;
12382     ModeHighlight();
12383     SetGameInfo();
12384     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12385     DisplayTitle(buf);
12386     if (first.sendName) {
12387       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12388       SendToProgram(buf, &first);
12389     }
12390     if (first.sendTime) {
12391       if (first.useColors) {
12392         SendToProgram("black\n", &first); /*gnu kludge*/
12393       }
12394       SendTimeRemaining(&first, TRUE);
12395     }
12396     if (first.useColors) {
12397       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12398     }
12399     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12400     SetMachineThinkingEnables();
12401     first.maybeThinking = TRUE;
12402     StartClocks();
12403     firstMove = FALSE;
12404
12405     if (appData.autoFlipView && !flipView) {
12406       flipView = !flipView;
12407       DrawPosition(FALSE, NULL);
12408       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12409     }
12410
12411     if(bookHit) { // [HGM] book: simulate book reply
12412         static char bookMove[MSG_SIZ]; // a bit generous?
12413
12414         programStats.nodes = programStats.depth = programStats.time =
12415         programStats.score = programStats.got_only_move = 0;
12416         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12417
12418         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12419         strcat(bookMove, bookHit);
12420         HandleMachineMove(bookMove, &first);
12421     }
12422 }
12423
12424 void
12425 MachineBlackEvent()
12426 {
12427   char buf[MSG_SIZ];
12428   char *bookHit = NULL;
12429
12430     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12431         return;
12432
12433
12434     if (gameMode == PlayFromGameFile ||
12435         gameMode == TwoMachinesPlay  ||
12436         gameMode == Training         ||
12437         gameMode == AnalyzeMode      ||
12438         gameMode == EndOfGame)
12439         EditGameEvent();
12440
12441     if (gameMode == EditPosition)
12442         EditPositionDone(TRUE);
12443
12444     if (WhiteOnMove(currentMove)) {
12445         DisplayError(_("It is not Black's turn"), 0);
12446         return;
12447     }
12448
12449     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12450       ExitAnalyzeMode();
12451
12452     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12453         gameMode == AnalyzeFile)
12454         TruncateGame();
12455
12456     ResurrectChessProgram();    /* in case it isn't running */
12457     gameMode = MachinePlaysBlack;
12458     pausing = FALSE;
12459     ModeHighlight();
12460     SetGameInfo();
12461     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12462     DisplayTitle(buf);
12463     if (first.sendName) {
12464       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12465       SendToProgram(buf, &first);
12466     }
12467     if (first.sendTime) {
12468       if (first.useColors) {
12469         SendToProgram("white\n", &first); /*gnu kludge*/
12470       }
12471       SendTimeRemaining(&first, FALSE);
12472     }
12473     if (first.useColors) {
12474       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12475     }
12476     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12477     SetMachineThinkingEnables();
12478     first.maybeThinking = TRUE;
12479     StartClocks();
12480
12481     if (appData.autoFlipView && flipView) {
12482       flipView = !flipView;
12483       DrawPosition(FALSE, NULL);
12484       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12485     }
12486     if(bookHit) { // [HGM] book: simulate book reply
12487         static char bookMove[MSG_SIZ]; // a bit generous?
12488
12489         programStats.nodes = programStats.depth = programStats.time =
12490         programStats.score = programStats.got_only_move = 0;
12491         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12492
12493         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12494         strcat(bookMove, bookHit);
12495         HandleMachineMove(bookMove, &first);
12496     }
12497 }
12498
12499
12500 void
12501 DisplayTwoMachinesTitle()
12502 {
12503     char buf[MSG_SIZ];
12504     if (appData.matchGames > 0) {
12505         if (first.twoMachinesColor[0] == 'w') {
12506           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12507                    gameInfo.white, gameInfo.black,
12508                    first.matchWins, second.matchWins,
12509                    matchGame - 1 - (first.matchWins + second.matchWins));
12510         } else {
12511           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12512                    gameInfo.white, gameInfo.black,
12513                    second.matchWins, first.matchWins,
12514                    matchGame - 1 - (first.matchWins + second.matchWins));
12515         }
12516     } else {
12517       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12518     }
12519     DisplayTitle(buf);
12520 }
12521
12522 void
12523 SettingsMenuIfReady()
12524 {
12525   if (second.lastPing != second.lastPong) {
12526     DisplayMessage("", _("Waiting for second chess program"));
12527     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12528     return;
12529   }
12530   ThawUI();
12531   DisplayMessage("", "");
12532   SettingsPopUp(&second);
12533 }
12534
12535 int
12536 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12537 {
12538     char buf[MSG_SIZ];
12539     if (cps->pr == NULL) {
12540         StartChessProgram(cps);
12541         if (cps->protocolVersion == 1) {
12542           retry();
12543         } else {
12544           /* kludge: allow timeout for initial "feature" command */
12545           FreezeUI();
12546           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12547           DisplayMessage("", buf);
12548           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12549         }
12550         return 1;
12551     }
12552     return 0;
12553 }
12554
12555 void
12556 TwoMachinesEvent P((void))
12557 {
12558     int i;
12559     char buf[MSG_SIZ];
12560     ChessProgramState *onmove;
12561     char *bookHit = NULL;
12562     static int stalling = 0;
12563     TimeMark now;
12564     long wait;
12565
12566     if (appData.noChessProgram) return;
12567
12568     switch (gameMode) {
12569       case TwoMachinesPlay:
12570         return;
12571       case MachinePlaysWhite:
12572       case MachinePlaysBlack:
12573         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12574             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12575             return;
12576         }
12577         /* fall through */
12578       case BeginningOfGame:
12579       case PlayFromGameFile:
12580       case EndOfGame:
12581         EditGameEvent();
12582         if (gameMode != EditGame) return;
12583         break;
12584       case EditPosition:
12585         EditPositionDone(TRUE);
12586         break;
12587       case AnalyzeMode:
12588       case AnalyzeFile:
12589         ExitAnalyzeMode();
12590         break;
12591       case EditGame:
12592       default:
12593         break;
12594     }
12595
12596 //    forwardMostMove = currentMove;
12597     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12598
12599     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12600
12601     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12602     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12603       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12604       return;
12605     }
12606     if(!stalling) {
12607       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12608       SendToProgram("force\n", &second);
12609       stalling = 1;
12610       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12611       return;
12612     }
12613     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12614     if(appData.matchPause>10000 || appData.matchPause<10)
12615                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12616     wait = SubtractTimeMarks(&now, &pauseStart);
12617     if(wait < appData.matchPause) {
12618         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12619         return;
12620     }
12621     stalling = 0;
12622     DisplayMessage("", "");
12623     if (startedFromSetupPosition) {
12624         SendBoard(&second, backwardMostMove);
12625     if (appData.debugMode) {
12626         fprintf(debugFP, "Two Machines\n");
12627     }
12628     }
12629     for (i = backwardMostMove; i < forwardMostMove; i++) {
12630         SendMoveToProgram(i, &second);
12631     }
12632
12633     gameMode = TwoMachinesPlay;
12634     pausing = FALSE;
12635     ModeHighlight();
12636     SetGameInfo();
12637     DisplayTwoMachinesTitle();
12638     firstMove = TRUE;
12639     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12640         onmove = &first;
12641     } else {
12642         onmove = &second;
12643     }
12644     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12645     SendToProgram(first.computerString, &first);
12646     if (first.sendName) {
12647       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12648       SendToProgram(buf, &first);
12649     }
12650     SendToProgram(second.computerString, &second);
12651     if (second.sendName) {
12652       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12653       SendToProgram(buf, &second);
12654     }
12655
12656     ResetClocks();
12657     if (!first.sendTime || !second.sendTime) {
12658         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12659         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12660     }
12661     if (onmove->sendTime) {
12662       if (onmove->useColors) {
12663         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12664       }
12665       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12666     }
12667     if (onmove->useColors) {
12668       SendToProgram(onmove->twoMachinesColor, onmove);
12669     }
12670     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12671 //    SendToProgram("go\n", onmove);
12672     onmove->maybeThinking = TRUE;
12673     SetMachineThinkingEnables();
12674
12675     StartClocks();
12676
12677     if(bookHit) { // [HGM] book: simulate book reply
12678         static char bookMove[MSG_SIZ]; // a bit generous?
12679
12680         programStats.nodes = programStats.depth = programStats.time =
12681         programStats.score = programStats.got_only_move = 0;
12682         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12683
12684         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12685         strcat(bookMove, bookHit);
12686         savedMessage = bookMove; // args for deferred call
12687         savedState = onmove;
12688         ScheduleDelayedEvent(DeferredBookMove, 1);
12689     }
12690 }
12691
12692 void
12693 TrainingEvent()
12694 {
12695     if (gameMode == Training) {
12696       SetTrainingModeOff();
12697       gameMode = PlayFromGameFile;
12698       DisplayMessage("", _("Training mode off"));
12699     } else {
12700       gameMode = Training;
12701       animateTraining = appData.animate;
12702
12703       /* make sure we are not already at the end of the game */
12704       if (currentMove < forwardMostMove) {
12705         SetTrainingModeOn();
12706         DisplayMessage("", _("Training mode on"));
12707       } else {
12708         gameMode = PlayFromGameFile;
12709         DisplayError(_("Already at end of game"), 0);
12710       }
12711     }
12712     ModeHighlight();
12713 }
12714
12715 void
12716 IcsClientEvent()
12717 {
12718     if (!appData.icsActive) return;
12719     switch (gameMode) {
12720       case IcsPlayingWhite:
12721       case IcsPlayingBlack:
12722       case IcsObserving:
12723       case IcsIdle:
12724       case BeginningOfGame:
12725       case IcsExamining:
12726         return;
12727
12728       case EditGame:
12729         break;
12730
12731       case EditPosition:
12732         EditPositionDone(TRUE);
12733         break;
12734
12735       case AnalyzeMode:
12736       case AnalyzeFile:
12737         ExitAnalyzeMode();
12738         break;
12739
12740       default:
12741         EditGameEvent();
12742         break;
12743     }
12744
12745     gameMode = IcsIdle;
12746     ModeHighlight();
12747     return;
12748 }
12749
12750
12751 void
12752 EditGameEvent()
12753 {
12754     int i;
12755
12756     switch (gameMode) {
12757       case Training:
12758         SetTrainingModeOff();
12759         break;
12760       case MachinePlaysWhite:
12761       case MachinePlaysBlack:
12762       case BeginningOfGame:
12763         SendToProgram("force\n", &first);
12764         SetUserThinkingEnables();
12765         break;
12766       case PlayFromGameFile:
12767         (void) StopLoadGameTimer();
12768         if (gameFileFP != NULL) {
12769             gameFileFP = NULL;
12770         }
12771         break;
12772       case EditPosition:
12773         EditPositionDone(TRUE);
12774         break;
12775       case AnalyzeMode:
12776       case AnalyzeFile:
12777         ExitAnalyzeMode();
12778         SendToProgram("force\n", &first);
12779         break;
12780       case TwoMachinesPlay:
12781         GameEnds(EndOfFile, NULL, GE_PLAYER);
12782         ResurrectChessProgram();
12783         SetUserThinkingEnables();
12784         break;
12785       case EndOfGame:
12786         ResurrectChessProgram();
12787         break;
12788       case IcsPlayingBlack:
12789       case IcsPlayingWhite:
12790         DisplayError(_("Warning: You are still playing a game"), 0);
12791         break;
12792       case IcsObserving:
12793         DisplayError(_("Warning: You are still observing a game"), 0);
12794         break;
12795       case IcsExamining:
12796         DisplayError(_("Warning: You are still examining a game"), 0);
12797         break;
12798       case IcsIdle:
12799         break;
12800       case EditGame:
12801       default:
12802         return;
12803     }
12804
12805     pausing = FALSE;
12806     StopClocks();
12807     first.offeredDraw = second.offeredDraw = 0;
12808
12809     if (gameMode == PlayFromGameFile) {
12810         whiteTimeRemaining = timeRemaining[0][currentMove];
12811         blackTimeRemaining = timeRemaining[1][currentMove];
12812         DisplayTitle("");
12813     }
12814
12815     if (gameMode == MachinePlaysWhite ||
12816         gameMode == MachinePlaysBlack ||
12817         gameMode == TwoMachinesPlay ||
12818         gameMode == EndOfGame) {
12819         i = forwardMostMove;
12820         while (i > currentMove) {
12821             SendToProgram("undo\n", &first);
12822             i--;
12823         }
12824         whiteTimeRemaining = timeRemaining[0][currentMove];
12825         blackTimeRemaining = timeRemaining[1][currentMove];
12826         DisplayBothClocks();
12827         if (whiteFlag || blackFlag) {
12828             whiteFlag = blackFlag = 0;
12829         }
12830         DisplayTitle("");
12831     }
12832
12833     gameMode = EditGame;
12834     ModeHighlight();
12835     SetGameInfo();
12836 }
12837
12838
12839 void
12840 EditPositionEvent()
12841 {
12842     if (gameMode == EditPosition) {
12843         EditGameEvent();
12844         return;
12845     }
12846
12847     EditGameEvent();
12848     if (gameMode != EditGame) return;
12849
12850     gameMode = EditPosition;
12851     ModeHighlight();
12852     SetGameInfo();
12853     if (currentMove > 0)
12854       CopyBoard(boards[0], boards[currentMove]);
12855
12856     blackPlaysFirst = !WhiteOnMove(currentMove);
12857     ResetClocks();
12858     currentMove = forwardMostMove = backwardMostMove = 0;
12859     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12860     DisplayMove(-1);
12861 }
12862
12863 void
12864 ExitAnalyzeMode()
12865 {
12866     /* [DM] icsEngineAnalyze - possible call from other functions */
12867     if (appData.icsEngineAnalyze) {
12868         appData.icsEngineAnalyze = FALSE;
12869
12870         DisplayMessage("",_("Close ICS engine analyze..."));
12871     }
12872     if (first.analysisSupport && first.analyzing) {
12873       SendToProgram("exit\n", &first);
12874       first.analyzing = FALSE;
12875     }
12876     thinkOutput[0] = NULLCHAR;
12877 }
12878
12879 void
12880 EditPositionDone(Boolean fakeRights)
12881 {
12882     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12883
12884     startedFromSetupPosition = TRUE;
12885     InitChessProgram(&first, FALSE);
12886     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12887       boards[0][EP_STATUS] = EP_NONE;
12888       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12889     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12890         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12891         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12892       } else boards[0][CASTLING][2] = NoRights;
12893     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12894         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12895         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12896       } else boards[0][CASTLING][5] = NoRights;
12897     }
12898     SendToProgram("force\n", &first);
12899     if (blackPlaysFirst) {
12900         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12901         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12902         currentMove = forwardMostMove = backwardMostMove = 1;
12903         CopyBoard(boards[1], boards[0]);
12904     } else {
12905         currentMove = forwardMostMove = backwardMostMove = 0;
12906     }
12907     SendBoard(&first, forwardMostMove);
12908     if (appData.debugMode) {
12909         fprintf(debugFP, "EditPosDone\n");
12910     }
12911     DisplayTitle("");
12912     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12913     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12914     gameMode = EditGame;
12915     ModeHighlight();
12916     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12917     ClearHighlights(); /* [AS] */
12918 }
12919
12920 /* Pause for `ms' milliseconds */
12921 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12922 void
12923 TimeDelay(ms)
12924      long ms;
12925 {
12926     TimeMark m1, m2;
12927
12928     GetTimeMark(&m1);
12929     do {
12930         GetTimeMark(&m2);
12931     } while (SubtractTimeMarks(&m2, &m1) < ms);
12932 }
12933
12934 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12935 void
12936 SendMultiLineToICS(buf)
12937      char *buf;
12938 {
12939     char temp[MSG_SIZ+1], *p;
12940     int len;
12941
12942     len = strlen(buf);
12943     if (len > MSG_SIZ)
12944       len = MSG_SIZ;
12945
12946     strncpy(temp, buf, len);
12947     temp[len] = 0;
12948
12949     p = temp;
12950     while (*p) {
12951         if (*p == '\n' || *p == '\r')
12952           *p = ' ';
12953         ++p;
12954     }
12955
12956     strcat(temp, "\n");
12957     SendToICS(temp);
12958     SendToPlayer(temp, strlen(temp));
12959 }
12960
12961 void
12962 SetWhiteToPlayEvent()
12963 {
12964     if (gameMode == EditPosition) {
12965         blackPlaysFirst = FALSE;
12966         DisplayBothClocks();    /* works because currentMove is 0 */
12967     } else if (gameMode == IcsExamining) {
12968         SendToICS(ics_prefix);
12969         SendToICS("tomove white\n");
12970     }
12971 }
12972
12973 void
12974 SetBlackToPlayEvent()
12975 {
12976     if (gameMode == EditPosition) {
12977         blackPlaysFirst = TRUE;
12978         currentMove = 1;        /* kludge */
12979         DisplayBothClocks();
12980         currentMove = 0;
12981     } else if (gameMode == IcsExamining) {
12982         SendToICS(ics_prefix);
12983         SendToICS("tomove black\n");
12984     }
12985 }
12986
12987 void
12988 EditPositionMenuEvent(selection, x, y)
12989      ChessSquare selection;
12990      int x, y;
12991 {
12992     char buf[MSG_SIZ];
12993     ChessSquare piece = boards[0][y][x];
12994
12995     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12996
12997     switch (selection) {
12998       case ClearBoard:
12999         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13000             SendToICS(ics_prefix);
13001             SendToICS("bsetup clear\n");
13002         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13003             SendToICS(ics_prefix);
13004             SendToICS("clearboard\n");
13005         } else {
13006             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13007                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13008                 for (y = 0; y < BOARD_HEIGHT; y++) {
13009                     if (gameMode == IcsExamining) {
13010                         if (boards[currentMove][y][x] != EmptySquare) {
13011                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13012                                     AAA + x, ONE + y);
13013                             SendToICS(buf);
13014                         }
13015                     } else {
13016                         boards[0][y][x] = p;
13017                     }
13018                 }
13019             }
13020         }
13021         if (gameMode == EditPosition) {
13022             DrawPosition(FALSE, boards[0]);
13023         }
13024         break;
13025
13026       case WhitePlay:
13027         SetWhiteToPlayEvent();
13028         break;
13029
13030       case BlackPlay:
13031         SetBlackToPlayEvent();
13032         break;
13033
13034       case EmptySquare:
13035         if (gameMode == IcsExamining) {
13036             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13037             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13038             SendToICS(buf);
13039         } else {
13040             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13041                 if(x == BOARD_LEFT-2) {
13042                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13043                     boards[0][y][1] = 0;
13044                 } else
13045                 if(x == BOARD_RGHT+1) {
13046                     if(y >= gameInfo.holdingsSize) break;
13047                     boards[0][y][BOARD_WIDTH-2] = 0;
13048                 } else break;
13049             }
13050             boards[0][y][x] = EmptySquare;
13051             DrawPosition(FALSE, boards[0]);
13052         }
13053         break;
13054
13055       case PromotePiece:
13056         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13057            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13058             selection = (ChessSquare) (PROMOTED piece);
13059         } else if(piece == EmptySquare) selection = WhiteSilver;
13060         else selection = (ChessSquare)((int)piece - 1);
13061         goto defaultlabel;
13062
13063       case DemotePiece:
13064         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13065            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13066             selection = (ChessSquare) (DEMOTED piece);
13067         } else if(piece == EmptySquare) selection = BlackSilver;
13068         else selection = (ChessSquare)((int)piece + 1);
13069         goto defaultlabel;
13070
13071       case WhiteQueen:
13072       case BlackQueen:
13073         if(gameInfo.variant == VariantShatranj ||
13074            gameInfo.variant == VariantXiangqi  ||
13075            gameInfo.variant == VariantCourier  ||
13076            gameInfo.variant == VariantMakruk     )
13077             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13078         goto defaultlabel;
13079
13080       case WhiteKing:
13081       case BlackKing:
13082         if(gameInfo.variant == VariantXiangqi)
13083             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13084         if(gameInfo.variant == VariantKnightmate)
13085             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13086       default:
13087         defaultlabel:
13088         if (gameMode == IcsExamining) {
13089             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13090             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13091                      PieceToChar(selection), AAA + x, ONE + y);
13092             SendToICS(buf);
13093         } else {
13094             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13095                 int n;
13096                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13097                     n = PieceToNumber(selection - BlackPawn);
13098                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13099                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13100                     boards[0][BOARD_HEIGHT-1-n][1]++;
13101                 } else
13102                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13103                     n = PieceToNumber(selection);
13104                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13105                     boards[0][n][BOARD_WIDTH-1] = selection;
13106                     boards[0][n][BOARD_WIDTH-2]++;
13107                 }
13108             } else
13109             boards[0][y][x] = selection;
13110             DrawPosition(TRUE, boards[0]);
13111         }
13112         break;
13113     }
13114 }
13115
13116
13117 void
13118 DropMenuEvent(selection, x, y)
13119      ChessSquare selection;
13120      int x, y;
13121 {
13122     ChessMove moveType;
13123
13124     switch (gameMode) {
13125       case IcsPlayingWhite:
13126       case MachinePlaysBlack:
13127         if (!WhiteOnMove(currentMove)) {
13128             DisplayMoveError(_("It is Black's turn"));
13129             return;
13130         }
13131         moveType = WhiteDrop;
13132         break;
13133       case IcsPlayingBlack:
13134       case MachinePlaysWhite:
13135         if (WhiteOnMove(currentMove)) {
13136             DisplayMoveError(_("It is White's turn"));
13137             return;
13138         }
13139         moveType = BlackDrop;
13140         break;
13141       case EditGame:
13142         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13143         break;
13144       default:
13145         return;
13146     }
13147
13148     if (moveType == BlackDrop && selection < BlackPawn) {
13149       selection = (ChessSquare) ((int) selection
13150                                  + (int) BlackPawn - (int) WhitePawn);
13151     }
13152     if (boards[currentMove][y][x] != EmptySquare) {
13153         DisplayMoveError(_("That square is occupied"));
13154         return;
13155     }
13156
13157     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13158 }
13159
13160 void
13161 AcceptEvent()
13162 {
13163     /* Accept a pending offer of any kind from opponent */
13164
13165     if (appData.icsActive) {
13166         SendToICS(ics_prefix);
13167         SendToICS("accept\n");
13168     } else if (cmailMsgLoaded) {
13169         if (currentMove == cmailOldMove &&
13170             commentList[cmailOldMove] != NULL &&
13171             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13172                    "Black offers a draw" : "White offers a draw")) {
13173             TruncateGame();
13174             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13175             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13176         } else {
13177             DisplayError(_("There is no pending offer on this move"), 0);
13178             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13179         }
13180     } else {
13181         /* Not used for offers from chess program */
13182     }
13183 }
13184
13185 void
13186 DeclineEvent()
13187 {
13188     /* Decline a pending offer of any kind from opponent */
13189
13190     if (appData.icsActive) {
13191         SendToICS(ics_prefix);
13192         SendToICS("decline\n");
13193     } else if (cmailMsgLoaded) {
13194         if (currentMove == cmailOldMove &&
13195             commentList[cmailOldMove] != NULL &&
13196             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13197                    "Black offers a draw" : "White offers a draw")) {
13198 #ifdef NOTDEF
13199             AppendComment(cmailOldMove, "Draw declined", TRUE);
13200             DisplayComment(cmailOldMove - 1, "Draw declined");
13201 #endif /*NOTDEF*/
13202         } else {
13203             DisplayError(_("There is no pending offer on this move"), 0);
13204         }
13205     } else {
13206         /* Not used for offers from chess program */
13207     }
13208 }
13209
13210 void
13211 RematchEvent()
13212 {
13213     /* Issue ICS rematch command */
13214     if (appData.icsActive) {
13215         SendToICS(ics_prefix);
13216         SendToICS("rematch\n");
13217     }
13218 }
13219
13220 void
13221 CallFlagEvent()
13222 {
13223     /* Call your opponent's flag (claim a win on time) */
13224     if (appData.icsActive) {
13225         SendToICS(ics_prefix);
13226         SendToICS("flag\n");
13227     } else {
13228         switch (gameMode) {
13229           default:
13230             return;
13231           case MachinePlaysWhite:
13232             if (whiteFlag) {
13233                 if (blackFlag)
13234                   GameEnds(GameIsDrawn, "Both players ran out of time",
13235                            GE_PLAYER);
13236                 else
13237                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13238             } else {
13239                 DisplayError(_("Your opponent is not out of time"), 0);
13240             }
13241             break;
13242           case MachinePlaysBlack:
13243             if (blackFlag) {
13244                 if (whiteFlag)
13245                   GameEnds(GameIsDrawn, "Both players ran out of time",
13246                            GE_PLAYER);
13247                 else
13248                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13249             } else {
13250                 DisplayError(_("Your opponent is not out of time"), 0);
13251             }
13252             break;
13253         }
13254     }
13255 }
13256
13257 void
13258 ClockClick(int which)
13259 {       // [HGM] code moved to back-end from winboard.c
13260         if(which) { // black clock
13261           if (gameMode == EditPosition || gameMode == IcsExamining) {
13262             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13263             SetBlackToPlayEvent();
13264           } else if (gameMode == EditGame || shiftKey) {
13265             AdjustClock(which, -1);
13266           } else if (gameMode == IcsPlayingWhite ||
13267                      gameMode == MachinePlaysBlack) {
13268             CallFlagEvent();
13269           }
13270         } else { // white clock
13271           if (gameMode == EditPosition || gameMode == IcsExamining) {
13272             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13273             SetWhiteToPlayEvent();
13274           } else if (gameMode == EditGame || shiftKey) {
13275             AdjustClock(which, -1);
13276           } else if (gameMode == IcsPlayingBlack ||
13277                    gameMode == MachinePlaysWhite) {
13278             CallFlagEvent();
13279           }
13280         }
13281 }
13282
13283 void
13284 DrawEvent()
13285 {
13286     /* Offer draw or accept pending draw offer from opponent */
13287
13288     if (appData.icsActive) {
13289         /* Note: tournament rules require draw offers to be
13290            made after you make your move but before you punch
13291            your clock.  Currently ICS doesn't let you do that;
13292            instead, you immediately punch your clock after making
13293            a move, but you can offer a draw at any time. */
13294
13295         SendToICS(ics_prefix);
13296         SendToICS("draw\n");
13297         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13298     } else if (cmailMsgLoaded) {
13299         if (currentMove == cmailOldMove &&
13300             commentList[cmailOldMove] != NULL &&
13301             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13302                    "Black offers a draw" : "White offers a draw")) {
13303             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13304             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13305         } else if (currentMove == cmailOldMove + 1) {
13306             char *offer = WhiteOnMove(cmailOldMove) ?
13307               "White offers a draw" : "Black offers a draw";
13308             AppendComment(currentMove, offer, TRUE);
13309             DisplayComment(currentMove - 1, offer);
13310             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13311         } else {
13312             DisplayError(_("You must make your move before offering a draw"), 0);
13313             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13314         }
13315     } else if (first.offeredDraw) {
13316         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13317     } else {
13318         if (first.sendDrawOffers) {
13319             SendToProgram("draw\n", &first);
13320             userOfferedDraw = TRUE;
13321         }
13322     }
13323 }
13324
13325 void
13326 AdjournEvent()
13327 {
13328     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13329
13330     if (appData.icsActive) {
13331         SendToICS(ics_prefix);
13332         SendToICS("adjourn\n");
13333     } else {
13334         /* Currently GNU Chess doesn't offer or accept Adjourns */
13335     }
13336 }
13337
13338
13339 void
13340 AbortEvent()
13341 {
13342     /* Offer Abort or accept pending Abort offer from opponent */
13343
13344     if (appData.icsActive) {
13345         SendToICS(ics_prefix);
13346         SendToICS("abort\n");
13347     } else {
13348         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13349     }
13350 }
13351
13352 void
13353 ResignEvent()
13354 {
13355     /* Resign.  You can do this even if it's not your turn. */
13356
13357     if (appData.icsActive) {
13358         SendToICS(ics_prefix);
13359         SendToICS("resign\n");
13360     } else {
13361         switch (gameMode) {
13362           case MachinePlaysWhite:
13363             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13364             break;
13365           case MachinePlaysBlack:
13366             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13367             break;
13368           case EditGame:
13369             if (cmailMsgLoaded) {
13370                 TruncateGame();
13371                 if (WhiteOnMove(cmailOldMove)) {
13372                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13373                 } else {
13374                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13375                 }
13376                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13377             }
13378             break;
13379           default:
13380             break;
13381         }
13382     }
13383 }
13384
13385
13386 void
13387 StopObservingEvent()
13388 {
13389     /* Stop observing current games */
13390     SendToICS(ics_prefix);
13391     SendToICS("unobserve\n");
13392 }
13393
13394 void
13395 StopExaminingEvent()
13396 {
13397     /* Stop observing current game */
13398     SendToICS(ics_prefix);
13399     SendToICS("unexamine\n");
13400 }
13401
13402 void
13403 ForwardInner(target)
13404      int target;
13405 {
13406     int limit;
13407
13408     if (appData.debugMode)
13409         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13410                 target, currentMove, forwardMostMove);
13411
13412     if (gameMode == EditPosition)
13413       return;
13414
13415     if (gameMode == PlayFromGameFile && !pausing)
13416       PauseEvent();
13417
13418     if (gameMode == IcsExamining && pausing)
13419       limit = pauseExamForwardMostMove;
13420     else
13421       limit = forwardMostMove;
13422
13423     if (target > limit) target = limit;
13424
13425     if (target > 0 && moveList[target - 1][0]) {
13426         int fromX, fromY, toX, toY;
13427         toX = moveList[target - 1][2] - AAA;
13428         toY = moveList[target - 1][3] - ONE;
13429         if (moveList[target - 1][1] == '@') {
13430             if (appData.highlightLastMove) {
13431                 SetHighlights(-1, -1, toX, toY);
13432             }
13433         } else {
13434             fromX = moveList[target - 1][0] - AAA;
13435             fromY = moveList[target - 1][1] - ONE;
13436             if (target == currentMove + 1) {
13437                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13438             }
13439             if (appData.highlightLastMove) {
13440                 SetHighlights(fromX, fromY, toX, toY);
13441             }
13442         }
13443     }
13444     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13445         gameMode == Training || gameMode == PlayFromGameFile ||
13446         gameMode == AnalyzeFile) {
13447         while (currentMove < target) {
13448             SendMoveToProgram(currentMove++, &first);
13449         }
13450     } else {
13451         currentMove = target;
13452     }
13453
13454     if (gameMode == EditGame || gameMode == EndOfGame) {
13455         whiteTimeRemaining = timeRemaining[0][currentMove];
13456         blackTimeRemaining = timeRemaining[1][currentMove];
13457     }
13458     DisplayBothClocks();
13459     DisplayMove(currentMove - 1);
13460     DrawPosition(FALSE, boards[currentMove]);
13461     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13462     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13463         DisplayComment(currentMove - 1, commentList[currentMove]);
13464     }
13465 }
13466
13467
13468 void
13469 ForwardEvent()
13470 {
13471     if (gameMode == IcsExamining && !pausing) {
13472         SendToICS(ics_prefix);
13473         SendToICS("forward\n");
13474     } else {
13475         ForwardInner(currentMove + 1);
13476     }
13477 }
13478
13479 void
13480 ToEndEvent()
13481 {
13482     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13483         /* to optimze, we temporarily turn off analysis mode while we feed
13484          * the remaining moves to the engine. Otherwise we get analysis output
13485          * after each move.
13486          */
13487         if (first.analysisSupport) {
13488           SendToProgram("exit\nforce\n", &first);
13489           first.analyzing = FALSE;
13490         }
13491     }
13492
13493     if (gameMode == IcsExamining && !pausing) {
13494         SendToICS(ics_prefix);
13495         SendToICS("forward 999999\n");
13496     } else {
13497         ForwardInner(forwardMostMove);
13498     }
13499
13500     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13501         /* we have fed all the moves, so reactivate analysis mode */
13502         SendToProgram("analyze\n", &first);
13503         first.analyzing = TRUE;
13504         /*first.maybeThinking = TRUE;*/
13505         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13506     }
13507 }
13508
13509 void
13510 BackwardInner(target)
13511      int target;
13512 {
13513     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13514
13515     if (appData.debugMode)
13516         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13517                 target, currentMove, forwardMostMove);
13518
13519     if (gameMode == EditPosition) return;
13520     if (currentMove <= backwardMostMove) {
13521         ClearHighlights();
13522         DrawPosition(full_redraw, boards[currentMove]);
13523         return;
13524     }
13525     if (gameMode == PlayFromGameFile && !pausing)
13526       PauseEvent();
13527
13528     if (moveList[target][0]) {
13529         int fromX, fromY, toX, toY;
13530         toX = moveList[target][2] - AAA;
13531         toY = moveList[target][3] - ONE;
13532         if (moveList[target][1] == '@') {
13533             if (appData.highlightLastMove) {
13534                 SetHighlights(-1, -1, toX, toY);
13535             }
13536         } else {
13537             fromX = moveList[target][0] - AAA;
13538             fromY = moveList[target][1] - ONE;
13539             if (target == currentMove - 1) {
13540                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13541             }
13542             if (appData.highlightLastMove) {
13543                 SetHighlights(fromX, fromY, toX, toY);
13544             }
13545         }
13546     }
13547     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13548         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13549         while (currentMove > target) {
13550             SendToProgram("undo\n", &first);
13551             currentMove--;
13552         }
13553     } else {
13554         currentMove = target;
13555     }
13556
13557     if (gameMode == EditGame || gameMode == EndOfGame) {
13558         whiteTimeRemaining = timeRemaining[0][currentMove];
13559         blackTimeRemaining = timeRemaining[1][currentMove];
13560     }
13561     DisplayBothClocks();
13562     DisplayMove(currentMove - 1);
13563     DrawPosition(full_redraw, boards[currentMove]);
13564     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13565     // [HGM] PV info: routine tests if comment empty
13566     DisplayComment(currentMove - 1, commentList[currentMove]);
13567 }
13568
13569 void
13570 BackwardEvent()
13571 {
13572     if (gameMode == IcsExamining && !pausing) {
13573         SendToICS(ics_prefix);
13574         SendToICS("backward\n");
13575     } else {
13576         BackwardInner(currentMove - 1);
13577     }
13578 }
13579
13580 void
13581 ToStartEvent()
13582 {
13583     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13584         /* to optimize, we temporarily turn off analysis mode while we undo
13585          * all the moves. Otherwise we get analysis output after each undo.
13586          */
13587         if (first.analysisSupport) {
13588           SendToProgram("exit\nforce\n", &first);
13589           first.analyzing = FALSE;
13590         }
13591     }
13592
13593     if (gameMode == IcsExamining && !pausing) {
13594         SendToICS(ics_prefix);
13595         SendToICS("backward 999999\n");
13596     } else {
13597         BackwardInner(backwardMostMove);
13598     }
13599
13600     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13601         /* we have fed all the moves, so reactivate analysis mode */
13602         SendToProgram("analyze\n", &first);
13603         first.analyzing = TRUE;
13604         /*first.maybeThinking = TRUE;*/
13605         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13606     }
13607 }
13608
13609 void
13610 ToNrEvent(int to)
13611 {
13612   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13613   if (to >= forwardMostMove) to = forwardMostMove;
13614   if (to <= backwardMostMove) to = backwardMostMove;
13615   if (to < currentMove) {
13616     BackwardInner(to);
13617   } else {
13618     ForwardInner(to);
13619   }
13620 }
13621
13622 void
13623 RevertEvent(Boolean annotate)
13624 {
13625     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13626         return;
13627     }
13628     if (gameMode != IcsExamining) {
13629         DisplayError(_("You are not examining a game"), 0);
13630         return;
13631     }
13632     if (pausing) {
13633         DisplayError(_("You can't revert while pausing"), 0);
13634         return;
13635     }
13636     SendToICS(ics_prefix);
13637     SendToICS("revert\n");
13638 }
13639
13640 void
13641 RetractMoveEvent()
13642 {
13643     switch (gameMode) {
13644       case MachinePlaysWhite:
13645       case MachinePlaysBlack:
13646         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13647             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13648             return;
13649         }
13650         if (forwardMostMove < 2) return;
13651         currentMove = forwardMostMove = forwardMostMove - 2;
13652         whiteTimeRemaining = timeRemaining[0][currentMove];
13653         blackTimeRemaining = timeRemaining[1][currentMove];
13654         DisplayBothClocks();
13655         DisplayMove(currentMove - 1);
13656         ClearHighlights();/*!! could figure this out*/
13657         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13658         SendToProgram("remove\n", &first);
13659         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13660         break;
13661
13662       case BeginningOfGame:
13663       default:
13664         break;
13665
13666       case IcsPlayingWhite:
13667       case IcsPlayingBlack:
13668         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13669             SendToICS(ics_prefix);
13670             SendToICS("takeback 2\n");
13671         } else {
13672             SendToICS(ics_prefix);
13673             SendToICS("takeback 1\n");
13674         }
13675         break;
13676     }
13677 }
13678
13679 void
13680 MoveNowEvent()
13681 {
13682     ChessProgramState *cps;
13683
13684     switch (gameMode) {
13685       case MachinePlaysWhite:
13686         if (!WhiteOnMove(forwardMostMove)) {
13687             DisplayError(_("It is your turn"), 0);
13688             return;
13689         }
13690         cps = &first;
13691         break;
13692       case MachinePlaysBlack:
13693         if (WhiteOnMove(forwardMostMove)) {
13694             DisplayError(_("It is your turn"), 0);
13695             return;
13696         }
13697         cps = &first;
13698         break;
13699       case TwoMachinesPlay:
13700         if (WhiteOnMove(forwardMostMove) ==
13701             (first.twoMachinesColor[0] == 'w')) {
13702             cps = &first;
13703         } else {
13704             cps = &second;
13705         }
13706         break;
13707       case BeginningOfGame:
13708       default:
13709         return;
13710     }
13711     SendToProgram("?\n", cps);
13712 }
13713
13714 void
13715 TruncateGameEvent()
13716 {
13717     EditGameEvent();
13718     if (gameMode != EditGame) return;
13719     TruncateGame();
13720 }
13721
13722 void
13723 TruncateGame()
13724 {
13725     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13726     if (forwardMostMove > currentMove) {
13727         if (gameInfo.resultDetails != NULL) {
13728             free(gameInfo.resultDetails);
13729             gameInfo.resultDetails = NULL;
13730             gameInfo.result = GameUnfinished;
13731         }
13732         forwardMostMove = currentMove;
13733         HistorySet(parseList, backwardMostMove, forwardMostMove,
13734                    currentMove-1);
13735     }
13736 }
13737
13738 void
13739 HintEvent()
13740 {
13741     if (appData.noChessProgram) return;
13742     switch (gameMode) {
13743       case MachinePlaysWhite:
13744         if (WhiteOnMove(forwardMostMove)) {
13745             DisplayError(_("Wait until your turn"), 0);
13746             return;
13747         }
13748         break;
13749       case BeginningOfGame:
13750       case MachinePlaysBlack:
13751         if (!WhiteOnMove(forwardMostMove)) {
13752             DisplayError(_("Wait until your turn"), 0);
13753             return;
13754         }
13755         break;
13756       default:
13757         DisplayError(_("No hint available"), 0);
13758         return;
13759     }
13760     SendToProgram("hint\n", &first);
13761     hintRequested = TRUE;
13762 }
13763
13764 void
13765 BookEvent()
13766 {
13767     if (appData.noChessProgram) return;
13768     switch (gameMode) {
13769       case MachinePlaysWhite:
13770         if (WhiteOnMove(forwardMostMove)) {
13771             DisplayError(_("Wait until your turn"), 0);
13772             return;
13773         }
13774         break;
13775       case BeginningOfGame:
13776       case MachinePlaysBlack:
13777         if (!WhiteOnMove(forwardMostMove)) {
13778             DisplayError(_("Wait until your turn"), 0);
13779             return;
13780         }
13781         break;
13782       case EditPosition:
13783         EditPositionDone(TRUE);
13784         break;
13785       case TwoMachinesPlay:
13786         return;
13787       default:
13788         break;
13789     }
13790     SendToProgram("bk\n", &first);
13791     bookOutput[0] = NULLCHAR;
13792     bookRequested = TRUE;
13793 }
13794
13795 void
13796 AboutGameEvent()
13797 {
13798     char *tags = PGNTags(&gameInfo);
13799     TagsPopUp(tags, CmailMsg());
13800     free(tags);
13801 }
13802
13803 /* end button procedures */
13804
13805 void
13806 PrintPosition(fp, move)
13807      FILE *fp;
13808      int move;
13809 {
13810     int i, j;
13811
13812     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13813         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13814             char c = PieceToChar(boards[move][i][j]);
13815             fputc(c == 'x' ? '.' : c, fp);
13816             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13817         }
13818     }
13819     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13820       fprintf(fp, "white to play\n");
13821     else
13822       fprintf(fp, "black to play\n");
13823 }
13824
13825 void
13826 PrintOpponents(fp)
13827      FILE *fp;
13828 {
13829     if (gameInfo.white != NULL) {
13830         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13831     } else {
13832         fprintf(fp, "\n");
13833     }
13834 }
13835
13836 /* Find last component of program's own name, using some heuristics */
13837 void
13838 TidyProgramName(prog, host, buf)
13839      char *prog, *host, buf[MSG_SIZ];
13840 {
13841     char *p, *q;
13842     int local = (strcmp(host, "localhost") == 0);
13843     while (!local && (p = strchr(prog, ';')) != NULL) {
13844         p++;
13845         while (*p == ' ') p++;
13846         prog = p;
13847     }
13848     if (*prog == '"' || *prog == '\'') {
13849         q = strchr(prog + 1, *prog);
13850     } else {
13851         q = strchr(prog, ' ');
13852     }
13853     if (q == NULL) q = prog + strlen(prog);
13854     p = q;
13855     while (p >= prog && *p != '/' && *p != '\\') p--;
13856     p++;
13857     if(p == prog && *p == '"') p++;
13858     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13859     memcpy(buf, p, q - p);
13860     buf[q - p] = NULLCHAR;
13861     if (!local) {
13862         strcat(buf, "@");
13863         strcat(buf, host);
13864     }
13865 }
13866
13867 char *
13868 TimeControlTagValue()
13869 {
13870     char buf[MSG_SIZ];
13871     if (!appData.clockMode) {
13872       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13873     } else if (movesPerSession > 0) {
13874       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13875     } else if (timeIncrement == 0) {
13876       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13877     } else {
13878       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13879     }
13880     return StrSave(buf);
13881 }
13882
13883 void
13884 SetGameInfo()
13885 {
13886     /* This routine is used only for certain modes */
13887     VariantClass v = gameInfo.variant;
13888     ChessMove r = GameUnfinished;
13889     char *p = NULL;
13890
13891     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13892         r = gameInfo.result;
13893         p = gameInfo.resultDetails;
13894         gameInfo.resultDetails = NULL;
13895     }
13896     ClearGameInfo(&gameInfo);
13897     gameInfo.variant = v;
13898
13899     switch (gameMode) {
13900       case MachinePlaysWhite:
13901         gameInfo.event = StrSave( appData.pgnEventHeader );
13902         gameInfo.site = StrSave(HostName());
13903         gameInfo.date = PGNDate();
13904         gameInfo.round = StrSave("-");
13905         gameInfo.white = StrSave(first.tidy);
13906         gameInfo.black = StrSave(UserName());
13907         gameInfo.timeControl = TimeControlTagValue();
13908         break;
13909
13910       case MachinePlaysBlack:
13911         gameInfo.event = StrSave( appData.pgnEventHeader );
13912         gameInfo.site = StrSave(HostName());
13913         gameInfo.date = PGNDate();
13914         gameInfo.round = StrSave("-");
13915         gameInfo.white = StrSave(UserName());
13916         gameInfo.black = StrSave(first.tidy);
13917         gameInfo.timeControl = TimeControlTagValue();
13918         break;
13919
13920       case TwoMachinesPlay:
13921         gameInfo.event = StrSave( appData.pgnEventHeader );
13922         gameInfo.site = StrSave(HostName());
13923         gameInfo.date = PGNDate();
13924         if (roundNr > 0) {
13925             char buf[MSG_SIZ];
13926             snprintf(buf, MSG_SIZ, "%d", roundNr);
13927             gameInfo.round = StrSave(buf);
13928         } else {
13929             gameInfo.round = StrSave("-");
13930         }
13931         if (first.twoMachinesColor[0] == 'w') {
13932             gameInfo.white = StrSave(first.tidy);
13933             gameInfo.black = StrSave(second.tidy);
13934         } else {
13935             gameInfo.white = StrSave(second.tidy);
13936             gameInfo.black = StrSave(first.tidy);
13937         }
13938         gameInfo.timeControl = TimeControlTagValue();
13939         break;
13940
13941       case EditGame:
13942         gameInfo.event = StrSave("Edited game");
13943         gameInfo.site = StrSave(HostName());
13944         gameInfo.date = PGNDate();
13945         gameInfo.round = StrSave("-");
13946         gameInfo.white = StrSave("-");
13947         gameInfo.black = StrSave("-");
13948         gameInfo.result = r;
13949         gameInfo.resultDetails = p;
13950         break;
13951
13952       case EditPosition:
13953         gameInfo.event = StrSave("Edited position");
13954         gameInfo.site = StrSave(HostName());
13955         gameInfo.date = PGNDate();
13956         gameInfo.round = StrSave("-");
13957         gameInfo.white = StrSave("-");
13958         gameInfo.black = StrSave("-");
13959         break;
13960
13961       case IcsPlayingWhite:
13962       case IcsPlayingBlack:
13963       case IcsObserving:
13964       case IcsExamining:
13965         break;
13966
13967       case PlayFromGameFile:
13968         gameInfo.event = StrSave("Game from non-PGN file");
13969         gameInfo.site = StrSave(HostName());
13970         gameInfo.date = PGNDate();
13971         gameInfo.round = StrSave("-");
13972         gameInfo.white = StrSave("?");
13973         gameInfo.black = StrSave("?");
13974         break;
13975
13976       default:
13977         break;
13978     }
13979 }
13980
13981 void
13982 ReplaceComment(index, text)
13983      int index;
13984      char *text;
13985 {
13986     int len;
13987     char *p;
13988     float score;
13989
13990     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13991        pvInfoList[index-1].depth == len &&
13992        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13993        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13994     while (*text == '\n') text++;
13995     len = strlen(text);
13996     while (len > 0 && text[len - 1] == '\n') len--;
13997
13998     if (commentList[index] != NULL)
13999       free(commentList[index]);
14000
14001     if (len == 0) {
14002         commentList[index] = NULL;
14003         return;
14004     }
14005   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14006       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14007       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14008     commentList[index] = (char *) malloc(len + 2);
14009     strncpy(commentList[index], text, len);
14010     commentList[index][len] = '\n';
14011     commentList[index][len + 1] = NULLCHAR;
14012   } else {
14013     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14014     char *p;
14015     commentList[index] = (char *) malloc(len + 7);
14016     safeStrCpy(commentList[index], "{\n", 3);
14017     safeStrCpy(commentList[index]+2, text, len+1);
14018     commentList[index][len+2] = NULLCHAR;
14019     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14020     strcat(commentList[index], "\n}\n");
14021   }
14022 }
14023
14024 void
14025 CrushCRs(text)
14026      char *text;
14027 {
14028   char *p = text;
14029   char *q = text;
14030   char ch;
14031
14032   do {
14033     ch = *p++;
14034     if (ch == '\r') continue;
14035     *q++ = ch;
14036   } while (ch != '\0');
14037 }
14038
14039 void
14040 AppendComment(index, text, addBraces)
14041      int index;
14042      char *text;
14043      Boolean addBraces; // [HGM] braces: tells if we should add {}
14044 {
14045     int oldlen, len;
14046     char *old;
14047
14048 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14049     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14050
14051     CrushCRs(text);
14052     while (*text == '\n') text++;
14053     len = strlen(text);
14054     while (len > 0 && text[len - 1] == '\n') len--;
14055
14056     if (len == 0) return;
14057
14058     if (commentList[index] != NULL) {
14059         old = commentList[index];
14060         oldlen = strlen(old);
14061         while(commentList[index][oldlen-1] ==  '\n')
14062           commentList[index][--oldlen] = NULLCHAR;
14063         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14064         safeStrCpy(commentList[index], old, oldlen + len + 6);
14065         free(old);
14066         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14067         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14068           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14069           while (*text == '\n') { text++; len--; }
14070           commentList[index][--oldlen] = NULLCHAR;
14071       }
14072         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14073         else          strcat(commentList[index], "\n");
14074         strcat(commentList[index], text);
14075         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14076         else          strcat(commentList[index], "\n");
14077     } else {
14078         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14079         if(addBraces)
14080           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14081         else commentList[index][0] = NULLCHAR;
14082         strcat(commentList[index], text);
14083         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14084         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14085     }
14086 }
14087
14088 static char * FindStr( char * text, char * sub_text )
14089 {
14090     char * result = strstr( text, sub_text );
14091
14092     if( result != NULL ) {
14093         result += strlen( sub_text );
14094     }
14095
14096     return result;
14097 }
14098
14099 /* [AS] Try to extract PV info from PGN comment */
14100 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14101 char *GetInfoFromComment( int index, char * text )
14102 {
14103     char * sep = text, *p;
14104
14105     if( text != NULL && index > 0 ) {
14106         int score = 0;
14107         int depth = 0;
14108         int time = -1, sec = 0, deci;
14109         char * s_eval = FindStr( text, "[%eval " );
14110         char * s_emt = FindStr( text, "[%emt " );
14111
14112         if( s_eval != NULL || s_emt != NULL ) {
14113             /* New style */
14114             char delim;
14115
14116             if( s_eval != NULL ) {
14117                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14118                     return text;
14119                 }
14120
14121                 if( delim != ']' ) {
14122                     return text;
14123                 }
14124             }
14125
14126             if( s_emt != NULL ) {
14127             }
14128                 return text;
14129         }
14130         else {
14131             /* We expect something like: [+|-]nnn.nn/dd */
14132             int score_lo = 0;
14133
14134             if(*text != '{') return text; // [HGM] braces: must be normal comment
14135
14136             sep = strchr( text, '/' );
14137             if( sep == NULL || sep < (text+4) ) {
14138                 return text;
14139             }
14140
14141             p = text;
14142             if(p[1] == '(') { // comment starts with PV
14143                p = strchr(p, ')'); // locate end of PV
14144                if(p == NULL || sep < p+5) return text;
14145                // at this point we have something like "{(.*) +0.23/6 ..."
14146                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14147                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14148                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14149             }
14150             time = -1; sec = -1; deci = -1;
14151             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14152                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14153                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14154                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14155                 return text;
14156             }
14157
14158             if( score_lo < 0 || score_lo >= 100 ) {
14159                 return text;
14160             }
14161
14162             if(sec >= 0) time = 600*time + 10*sec; else
14163             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14164
14165             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14166
14167             /* [HGM] PV time: now locate end of PV info */
14168             while( *++sep >= '0' && *sep <= '9'); // strip depth
14169             if(time >= 0)
14170             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14171             if(sec >= 0)
14172             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14173             if(deci >= 0)
14174             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14175             while(*sep == ' ') sep++;
14176         }
14177
14178         if( depth <= 0 ) {
14179             return text;
14180         }
14181
14182         if( time < 0 ) {
14183             time = -1;
14184         }
14185
14186         pvInfoList[index-1].depth = depth;
14187         pvInfoList[index-1].score = score;
14188         pvInfoList[index-1].time  = 10*time; // centi-sec
14189         if(*sep == '}') *sep = 0; else *--sep = '{';
14190         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14191     }
14192     return sep;
14193 }
14194
14195 void
14196 SendToProgram(message, cps)
14197      char *message;
14198      ChessProgramState *cps;
14199 {
14200     int count, outCount, error;
14201     char buf[MSG_SIZ];
14202
14203     if (cps->pr == NULL) return;
14204     Attention(cps);
14205
14206     if (appData.debugMode) {
14207         TimeMark now;
14208         GetTimeMark(&now);
14209         fprintf(debugFP, "%ld >%-6s: %s",
14210                 SubtractTimeMarks(&now, &programStartTime),
14211                 cps->which, message);
14212     }
14213
14214     count = strlen(message);
14215     outCount = OutputToProcess(cps->pr, message, count, &error);
14216     if (outCount < count && !exiting
14217                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14218       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14219       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14220         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14221             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14222                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14223                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14224                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14225             } else {
14226                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14227                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14228                 gameInfo.result = res;
14229             }
14230             gameInfo.resultDetails = StrSave(buf);
14231         }
14232         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14233         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14234     }
14235 }
14236
14237 void
14238 ReceiveFromProgram(isr, closure, message, count, error)
14239      InputSourceRef isr;
14240      VOIDSTAR closure;
14241      char *message;
14242      int count;
14243      int error;
14244 {
14245     char *end_str;
14246     char buf[MSG_SIZ];
14247     ChessProgramState *cps = (ChessProgramState *)closure;
14248
14249     if (isr != cps->isr) return; /* Killed intentionally */
14250     if (count <= 0) {
14251         if (count == 0) {
14252             RemoveInputSource(cps->isr);
14253             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14254                     _(cps->which), cps->program);
14255         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14256                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14257                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14258                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14259                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14260                 } else {
14261                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14262                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14263                     gameInfo.result = res;
14264                 }
14265                 gameInfo.resultDetails = StrSave(buf);
14266             }
14267             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14268             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14269         } else {
14270             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14271                     _(cps->which), cps->program);
14272             RemoveInputSource(cps->isr);
14273
14274             /* [AS] Program is misbehaving badly... kill it */
14275             if( count == -2 ) {
14276                 DestroyChildProcess( cps->pr, 9 );
14277                 cps->pr = NoProc;
14278             }
14279
14280             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14281         }
14282         return;
14283     }
14284
14285     if ((end_str = strchr(message, '\r')) != NULL)
14286       *end_str = NULLCHAR;
14287     if ((end_str = strchr(message, '\n')) != NULL)
14288       *end_str = NULLCHAR;
14289
14290     if (appData.debugMode) {
14291         TimeMark now; int print = 1;
14292         char *quote = ""; char c; int i;
14293
14294         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14295                 char start = message[0];
14296                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14297                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14298                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14299                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14300                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14301                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14302                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14303                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14304                    sscanf(message, "hint: %c", &c)!=1 && 
14305                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14306                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14307                     print = (appData.engineComments >= 2);
14308                 }
14309                 message[0] = start; // restore original message
14310         }
14311         if(print) {
14312                 GetTimeMark(&now);
14313                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14314                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14315                         quote,
14316                         message);
14317         }
14318     }
14319
14320     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14321     if (appData.icsEngineAnalyze) {
14322         if (strstr(message, "whisper") != NULL ||
14323              strstr(message, "kibitz") != NULL ||
14324             strstr(message, "tellics") != NULL) return;
14325     }
14326
14327     HandleMachineMove(message, cps);
14328 }
14329
14330
14331 void
14332 SendTimeControl(cps, mps, tc, inc, sd, st)
14333      ChessProgramState *cps;
14334      int mps, inc, sd, st;
14335      long tc;
14336 {
14337     char buf[MSG_SIZ];
14338     int seconds;
14339
14340     if( timeControl_2 > 0 ) {
14341         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14342             tc = timeControl_2;
14343         }
14344     }
14345     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14346     inc /= cps->timeOdds;
14347     st  /= cps->timeOdds;
14348
14349     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14350
14351     if (st > 0) {
14352       /* Set exact time per move, normally using st command */
14353       if (cps->stKludge) {
14354         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14355         seconds = st % 60;
14356         if (seconds == 0) {
14357           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14358         } else {
14359           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14360         }
14361       } else {
14362         snprintf(buf, MSG_SIZ, "st %d\n", st);
14363       }
14364     } else {
14365       /* Set conventional or incremental time control, using level command */
14366       if (seconds == 0) {
14367         /* Note old gnuchess bug -- minutes:seconds used to not work.
14368            Fixed in later versions, but still avoid :seconds
14369            when seconds is 0. */
14370         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14371       } else {
14372         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14373                  seconds, inc/1000.);
14374       }
14375     }
14376     SendToProgram(buf, cps);
14377
14378     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14379     /* Orthogonally, limit search to given depth */
14380     if (sd > 0) {
14381       if (cps->sdKludge) {
14382         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14383       } else {
14384         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14385       }
14386       SendToProgram(buf, cps);
14387     }
14388
14389     if(cps->nps >= 0) { /* [HGM] nps */
14390         if(cps->supportsNPS == FALSE)
14391           cps->nps = -1; // don't use if engine explicitly says not supported!
14392         else {
14393           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14394           SendToProgram(buf, cps);
14395         }
14396     }
14397 }
14398
14399 ChessProgramState *WhitePlayer()
14400 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14401 {
14402     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14403        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14404         return &second;
14405     return &first;
14406 }
14407
14408 void
14409 SendTimeRemaining(cps, machineWhite)
14410      ChessProgramState *cps;
14411      int /*boolean*/ machineWhite;
14412 {
14413     char message[MSG_SIZ];
14414     long time, otime;
14415
14416     /* Note: this routine must be called when the clocks are stopped
14417        or when they have *just* been set or switched; otherwise
14418        it will be off by the time since the current tick started.
14419     */
14420     if (machineWhite) {
14421         time = whiteTimeRemaining / 10;
14422         otime = blackTimeRemaining / 10;
14423     } else {
14424         time = blackTimeRemaining / 10;
14425         otime = whiteTimeRemaining / 10;
14426     }
14427     /* [HGM] translate opponent's time by time-odds factor */
14428     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14429     if (appData.debugMode) {
14430         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14431     }
14432
14433     if (time <= 0) time = 1;
14434     if (otime <= 0) otime = 1;
14435
14436     snprintf(message, MSG_SIZ, "time %ld\n", time);
14437     SendToProgram(message, cps);
14438
14439     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14440     SendToProgram(message, cps);
14441 }
14442
14443 int
14444 BoolFeature(p, name, loc, cps)
14445      char **p;
14446      char *name;
14447      int *loc;
14448      ChessProgramState *cps;
14449 {
14450   char buf[MSG_SIZ];
14451   int len = strlen(name);
14452   int val;
14453
14454   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14455     (*p) += len + 1;
14456     sscanf(*p, "%d", &val);
14457     *loc = (val != 0);
14458     while (**p && **p != ' ')
14459       (*p)++;
14460     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14461     SendToProgram(buf, cps);
14462     return TRUE;
14463   }
14464   return FALSE;
14465 }
14466
14467 int
14468 IntFeature(p, name, loc, cps)
14469      char **p;
14470      char *name;
14471      int *loc;
14472      ChessProgramState *cps;
14473 {
14474   char buf[MSG_SIZ];
14475   int len = strlen(name);
14476   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14477     (*p) += len + 1;
14478     sscanf(*p, "%d", loc);
14479     while (**p && **p != ' ') (*p)++;
14480     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14481     SendToProgram(buf, cps);
14482     return TRUE;
14483   }
14484   return FALSE;
14485 }
14486
14487 int
14488 StringFeature(p, name, loc, cps)
14489      char **p;
14490      char *name;
14491      char loc[];
14492      ChessProgramState *cps;
14493 {
14494   char buf[MSG_SIZ];
14495   int len = strlen(name);
14496   if (strncmp((*p), name, len) == 0
14497       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14498     (*p) += len + 2;
14499     sscanf(*p, "%[^\"]", loc);
14500     while (**p && **p != '\"') (*p)++;
14501     if (**p == '\"') (*p)++;
14502     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14503     SendToProgram(buf, cps);
14504     return TRUE;
14505   }
14506   return FALSE;
14507 }
14508
14509 int
14510 ParseOption(Option *opt, ChessProgramState *cps)
14511 // [HGM] options: process the string that defines an engine option, and determine
14512 // name, type, default value, and allowed value range
14513 {
14514         char *p, *q, buf[MSG_SIZ];
14515         int n, min = (-1)<<31, max = 1<<31, def;
14516
14517         if(p = strstr(opt->name, " -spin ")) {
14518             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14519             if(max < min) max = min; // enforce consistency
14520             if(def < min) def = min;
14521             if(def > max) def = max;
14522             opt->value = def;
14523             opt->min = min;
14524             opt->max = max;
14525             opt->type = Spin;
14526         } else if((p = strstr(opt->name, " -slider "))) {
14527             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14528             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14529             if(max < min) max = min; // enforce consistency
14530             if(def < min) def = min;
14531             if(def > max) def = max;
14532             opt->value = def;
14533             opt->min = min;
14534             opt->max = max;
14535             opt->type = Spin; // Slider;
14536         } else if((p = strstr(opt->name, " -string "))) {
14537             opt->textValue = p+9;
14538             opt->type = TextBox;
14539         } else if((p = strstr(opt->name, " -file "))) {
14540             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14541             opt->textValue = p+7;
14542             opt->type = FileName; // FileName;
14543         } else if((p = strstr(opt->name, " -path "))) {
14544             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14545             opt->textValue = p+7;
14546             opt->type = PathName; // PathName;
14547         } else if(p = strstr(opt->name, " -check ")) {
14548             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14549             opt->value = (def != 0);
14550             opt->type = CheckBox;
14551         } else if(p = strstr(opt->name, " -combo ")) {
14552             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14553             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14554             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14555             opt->value = n = 0;
14556             while(q = StrStr(q, " /// ")) {
14557                 n++; *q = 0;    // count choices, and null-terminate each of them
14558                 q += 5;
14559                 if(*q == '*') { // remember default, which is marked with * prefix
14560                     q++;
14561                     opt->value = n;
14562                 }
14563                 cps->comboList[cps->comboCnt++] = q;
14564             }
14565             cps->comboList[cps->comboCnt++] = NULL;
14566             opt->max = n + 1;
14567             opt->type = ComboBox;
14568         } else if(p = strstr(opt->name, " -button")) {
14569             opt->type = Button;
14570         } else if(p = strstr(opt->name, " -save")) {
14571             opt->type = SaveButton;
14572         } else return FALSE;
14573         *p = 0; // terminate option name
14574         // now look if the command-line options define a setting for this engine option.
14575         if(cps->optionSettings && cps->optionSettings[0])
14576             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14577         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14578           snprintf(buf, MSG_SIZ, "option %s", p);
14579                 if(p = strstr(buf, ",")) *p = 0;
14580                 if(q = strchr(buf, '=')) switch(opt->type) {
14581                     case ComboBox:
14582                         for(n=0; n<opt->max; n++)
14583                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14584                         break;
14585                     case TextBox:
14586                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14587                         break;
14588                     case Spin:
14589                     case CheckBox:
14590                         opt->value = atoi(q+1);
14591                     default:
14592                         break;
14593                 }
14594                 strcat(buf, "\n");
14595                 SendToProgram(buf, cps);
14596         }
14597         return TRUE;
14598 }
14599
14600 void
14601 FeatureDone(cps, val)
14602      ChessProgramState* cps;
14603      int val;
14604 {
14605   DelayedEventCallback cb = GetDelayedEvent();
14606   if ((cb == InitBackEnd3 && cps == &first) ||
14607       (cb == SettingsMenuIfReady && cps == &second) ||
14608       (cb == LoadEngine) ||
14609       (cb == TwoMachinesEventIfReady)) {
14610     CancelDelayedEvent();
14611     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14612   }
14613   cps->initDone = val;
14614 }
14615
14616 /* Parse feature command from engine */
14617 void
14618 ParseFeatures(args, cps)
14619      char* args;
14620      ChessProgramState *cps;
14621 {
14622   char *p = args;
14623   char *q;
14624   int val;
14625   char buf[MSG_SIZ];
14626
14627   for (;;) {
14628     while (*p == ' ') p++;
14629     if (*p == NULLCHAR) return;
14630
14631     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14632     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14633     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14634     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14635     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14636     if (BoolFeature(&p, "reuse", &val, cps)) {
14637       /* Engine can disable reuse, but can't enable it if user said no */
14638       if (!val) cps->reuse = FALSE;
14639       continue;
14640     }
14641     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14642     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14643       if (gameMode == TwoMachinesPlay) {
14644         DisplayTwoMachinesTitle();
14645       } else {
14646         DisplayTitle("");
14647       }
14648       continue;
14649     }
14650     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14651     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14652     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14653     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14654     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14655     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14656     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14657     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14658     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14659     if (IntFeature(&p, "done", &val, cps)) {
14660       FeatureDone(cps, val);
14661       continue;
14662     }
14663     /* Added by Tord: */
14664     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14665     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14666     /* End of additions by Tord */
14667
14668     /* [HGM] added features: */
14669     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14670     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14671     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14672     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14673     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14674     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14675     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14676         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14677           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14678             SendToProgram(buf, cps);
14679             continue;
14680         }
14681         if(cps->nrOptions >= MAX_OPTIONS) {
14682             cps->nrOptions--;
14683             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14684             DisplayError(buf, 0);
14685         }
14686         continue;
14687     }
14688     /* End of additions by HGM */
14689
14690     /* unknown feature: complain and skip */
14691     q = p;
14692     while (*q && *q != '=') q++;
14693     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14694     SendToProgram(buf, cps);
14695     p = q;
14696     if (*p == '=') {
14697       p++;
14698       if (*p == '\"') {
14699         p++;
14700         while (*p && *p != '\"') p++;
14701         if (*p == '\"') p++;
14702       } else {
14703         while (*p && *p != ' ') p++;
14704       }
14705     }
14706   }
14707
14708 }
14709
14710 void
14711 PeriodicUpdatesEvent(newState)
14712      int newState;
14713 {
14714     if (newState == appData.periodicUpdates)
14715       return;
14716
14717     appData.periodicUpdates=newState;
14718
14719     /* Display type changes, so update it now */
14720 //    DisplayAnalysis();
14721
14722     /* Get the ball rolling again... */
14723     if (newState) {
14724         AnalysisPeriodicEvent(1);
14725         StartAnalysisClock();
14726     }
14727 }
14728
14729 void
14730 PonderNextMoveEvent(newState)
14731      int newState;
14732 {
14733     if (newState == appData.ponderNextMove) return;
14734     if (gameMode == EditPosition) EditPositionDone(TRUE);
14735     if (newState) {
14736         SendToProgram("hard\n", &first);
14737         if (gameMode == TwoMachinesPlay) {
14738             SendToProgram("hard\n", &second);
14739         }
14740     } else {
14741         SendToProgram("easy\n", &first);
14742         thinkOutput[0] = NULLCHAR;
14743         if (gameMode == TwoMachinesPlay) {
14744             SendToProgram("easy\n", &second);
14745         }
14746     }
14747     appData.ponderNextMove = newState;
14748 }
14749
14750 void
14751 NewSettingEvent(option, feature, command, value)
14752      char *command;
14753      int option, value, *feature;
14754 {
14755     char buf[MSG_SIZ];
14756
14757     if (gameMode == EditPosition) EditPositionDone(TRUE);
14758     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14759     if(feature == NULL || *feature) SendToProgram(buf, &first);
14760     if (gameMode == TwoMachinesPlay) {
14761         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14762     }
14763 }
14764
14765 void
14766 ShowThinkingEvent()
14767 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14768 {
14769     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14770     int newState = appData.showThinking
14771         // [HGM] thinking: other features now need thinking output as well
14772         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14773
14774     if (oldState == newState) return;
14775     oldState = newState;
14776     if (gameMode == EditPosition) EditPositionDone(TRUE);
14777     if (oldState) {
14778         SendToProgram("post\n", &first);
14779         if (gameMode == TwoMachinesPlay) {
14780             SendToProgram("post\n", &second);
14781         }
14782     } else {
14783         SendToProgram("nopost\n", &first);
14784         thinkOutput[0] = NULLCHAR;
14785         if (gameMode == TwoMachinesPlay) {
14786             SendToProgram("nopost\n", &second);
14787         }
14788     }
14789 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14790 }
14791
14792 void
14793 AskQuestionEvent(title, question, replyPrefix, which)
14794      char *title; char *question; char *replyPrefix; char *which;
14795 {
14796   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14797   if (pr == NoProc) return;
14798   AskQuestion(title, question, replyPrefix, pr);
14799 }
14800
14801 void
14802 TypeInEvent(char firstChar)
14803 {
14804     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14805         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14806         gameMode == AnalyzeMode || gameMode == EditGame || \r
14807         gameMode == EditPosition || gameMode == IcsExamining ||\r
14808         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14809         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14810                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14811                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14812         gameMode == Training) PopUpMoveDialog(firstChar);
14813 }
14814
14815 void
14816 TypeInDoneEvent(char *move)
14817 {
14818         Board board;
14819         int n, fromX, fromY, toX, toY;
14820         char promoChar;
14821         ChessMove moveType;\r
14822
14823         // [HGM] FENedit\r
14824         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14825                 EditPositionPasteFEN(move);\r
14826                 return;\r
14827         }\r
14828         // [HGM] movenum: allow move number to be typed in any mode\r
14829         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14830           ToNrEvent(2*n-1);\r
14831           return;\r
14832         }\r
14833
14834       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14835         gameMode != Training) {\r
14836         DisplayMoveError(_("Displayed move is not current"));\r
14837       } else {\r
14838         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14839           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14840         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14841         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14842           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14843           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14844         } else {\r
14845           DisplayMoveError(_("Could not parse move"));\r
14846         }
14847       }\r
14848 }\r
14849
14850 void
14851 DisplayMove(moveNumber)
14852      int moveNumber;
14853 {
14854     char message[MSG_SIZ];
14855     char res[MSG_SIZ];
14856     char cpThinkOutput[MSG_SIZ];
14857
14858     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14859
14860     if (moveNumber == forwardMostMove - 1 ||
14861         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14862
14863         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14864
14865         if (strchr(cpThinkOutput, '\n')) {
14866             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14867         }
14868     } else {
14869         *cpThinkOutput = NULLCHAR;
14870     }
14871
14872     /* [AS] Hide thinking from human user */
14873     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14874         *cpThinkOutput = NULLCHAR;
14875         if( thinkOutput[0] != NULLCHAR ) {
14876             int i;
14877
14878             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14879                 cpThinkOutput[i] = '.';
14880             }
14881             cpThinkOutput[i] = NULLCHAR;
14882             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14883         }
14884     }
14885
14886     if (moveNumber == forwardMostMove - 1 &&
14887         gameInfo.resultDetails != NULL) {
14888         if (gameInfo.resultDetails[0] == NULLCHAR) {
14889           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14890         } else {
14891           snprintf(res, MSG_SIZ, " {%s} %s",
14892                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14893         }
14894     } else {
14895         res[0] = NULLCHAR;
14896     }
14897
14898     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14899         DisplayMessage(res, cpThinkOutput);
14900     } else {
14901       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14902                 WhiteOnMove(moveNumber) ? " " : ".. ",
14903                 parseList[moveNumber], res);
14904         DisplayMessage(message, cpThinkOutput);
14905     }
14906 }
14907
14908 void
14909 DisplayComment(moveNumber, text)
14910      int moveNumber;
14911      char *text;
14912 {
14913     char title[MSG_SIZ];
14914     char buf[8000]; // comment can be long!
14915     int score, depth;
14916
14917     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14918       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14919     } else {
14920       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14921               WhiteOnMove(moveNumber) ? " " : ".. ",
14922               parseList[moveNumber]);
14923     }
14924     // [HGM] PV info: display PV info together with (or as) comment
14925     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14926       if(text == NULL) text = "";
14927       score = pvInfoList[moveNumber].score;
14928       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14929               depth, (pvInfoList[moveNumber].time+50)/100, text);
14930       text = buf;
14931     }
14932     if (text != NULL && (appData.autoDisplayComment || commentUp))
14933         CommentPopUp(title, text);
14934 }
14935
14936 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14937  * might be busy thinking or pondering.  It can be omitted if your
14938  * gnuchess is configured to stop thinking immediately on any user
14939  * input.  However, that gnuchess feature depends on the FIONREAD
14940  * ioctl, which does not work properly on some flavors of Unix.
14941  */
14942 void
14943 Attention(cps)
14944      ChessProgramState *cps;
14945 {
14946 #if ATTENTION
14947     if (!cps->useSigint) return;
14948     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14949     switch (gameMode) {
14950       case MachinePlaysWhite:
14951       case MachinePlaysBlack:
14952       case TwoMachinesPlay:
14953       case IcsPlayingWhite:
14954       case IcsPlayingBlack:
14955       case AnalyzeMode:
14956       case AnalyzeFile:
14957         /* Skip if we know it isn't thinking */
14958         if (!cps->maybeThinking) return;
14959         if (appData.debugMode)
14960           fprintf(debugFP, "Interrupting %s\n", cps->which);
14961         InterruptChildProcess(cps->pr);
14962         cps->maybeThinking = FALSE;
14963         break;
14964       default:
14965         break;
14966     }
14967 #endif /*ATTENTION*/
14968 }
14969
14970 int
14971 CheckFlags()
14972 {
14973     if (whiteTimeRemaining <= 0) {
14974         if (!whiteFlag) {
14975             whiteFlag = TRUE;
14976             if (appData.icsActive) {
14977                 if (appData.autoCallFlag &&
14978                     gameMode == IcsPlayingBlack && !blackFlag) {
14979                   SendToICS(ics_prefix);
14980                   SendToICS("flag\n");
14981                 }
14982             } else {
14983                 if (blackFlag) {
14984                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14985                 } else {
14986                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14987                     if (appData.autoCallFlag) {
14988                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14989                         return TRUE;
14990                     }
14991                 }
14992             }
14993         }
14994     }
14995     if (blackTimeRemaining <= 0) {
14996         if (!blackFlag) {
14997             blackFlag = TRUE;
14998             if (appData.icsActive) {
14999                 if (appData.autoCallFlag &&
15000                     gameMode == IcsPlayingWhite && !whiteFlag) {
15001                   SendToICS(ics_prefix);
15002                   SendToICS("flag\n");
15003                 }
15004             } else {
15005                 if (whiteFlag) {
15006                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15007                 } else {
15008                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15009                     if (appData.autoCallFlag) {
15010                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15011                         return TRUE;
15012                     }
15013                 }
15014             }
15015         }
15016     }
15017     return FALSE;
15018 }
15019
15020 void
15021 CheckTimeControl()
15022 {
15023     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15024         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15025
15026     /*
15027      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15028      */
15029     if ( !WhiteOnMove(forwardMostMove) ) {
15030         /* White made time control */
15031         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15032         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15033         /* [HGM] time odds: correct new time quota for time odds! */
15034                                             / WhitePlayer()->timeOdds;
15035         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15036     } else {
15037         lastBlack -= blackTimeRemaining;
15038         /* Black made time control */
15039         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15040                                             / WhitePlayer()->other->timeOdds;
15041         lastWhite = whiteTimeRemaining;
15042     }
15043 }
15044
15045 void
15046 DisplayBothClocks()
15047 {
15048     int wom = gameMode == EditPosition ?
15049       !blackPlaysFirst : WhiteOnMove(currentMove);
15050     DisplayWhiteClock(whiteTimeRemaining, wom);
15051     DisplayBlackClock(blackTimeRemaining, !wom);
15052 }
15053
15054
15055 /* Timekeeping seems to be a portability nightmare.  I think everyone
15056    has ftime(), but I'm really not sure, so I'm including some ifdefs
15057    to use other calls if you don't.  Clocks will be less accurate if
15058    you have neither ftime nor gettimeofday.
15059 */
15060
15061 /* VS 2008 requires the #include outside of the function */
15062 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15063 #include <sys/timeb.h>
15064 #endif
15065
15066 /* Get the current time as a TimeMark */
15067 void
15068 GetTimeMark(tm)
15069      TimeMark *tm;
15070 {
15071 #if HAVE_GETTIMEOFDAY
15072
15073     struct timeval timeVal;
15074     struct timezone timeZone;
15075
15076     gettimeofday(&timeVal, &timeZone);
15077     tm->sec = (long) timeVal.tv_sec;
15078     tm->ms = (int) (timeVal.tv_usec / 1000L);
15079
15080 #else /*!HAVE_GETTIMEOFDAY*/
15081 #if HAVE_FTIME
15082
15083 // include <sys/timeb.h> / moved to just above start of function
15084     struct timeb timeB;
15085
15086     ftime(&timeB);
15087     tm->sec = (long) timeB.time;
15088     tm->ms = (int) timeB.millitm;
15089
15090 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15091     tm->sec = (long) time(NULL);
15092     tm->ms = 0;
15093 #endif
15094 #endif
15095 }
15096
15097 /* Return the difference in milliseconds between two
15098    time marks.  We assume the difference will fit in a long!
15099 */
15100 long
15101 SubtractTimeMarks(tm2, tm1)
15102      TimeMark *tm2, *tm1;
15103 {
15104     return 1000L*(tm2->sec - tm1->sec) +
15105            (long) (tm2->ms - tm1->ms);
15106 }
15107
15108
15109 /*
15110  * Code to manage the game clocks.
15111  *
15112  * In tournament play, black starts the clock and then white makes a move.
15113  * We give the human user a slight advantage if he is playing white---the
15114  * clocks don't run until he makes his first move, so it takes zero time.
15115  * Also, we don't account for network lag, so we could get out of sync
15116  * with GNU Chess's clock -- but then, referees are always right.
15117  */
15118
15119 static TimeMark tickStartTM;
15120 static long intendedTickLength;
15121
15122 long
15123 NextTickLength(timeRemaining)
15124      long timeRemaining;
15125 {
15126     long nominalTickLength, nextTickLength;
15127
15128     if (timeRemaining > 0L && timeRemaining <= 10000L)
15129       nominalTickLength = 100L;
15130     else
15131       nominalTickLength = 1000L;
15132     nextTickLength = timeRemaining % nominalTickLength;
15133     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15134
15135     return nextTickLength;
15136 }
15137
15138 /* Adjust clock one minute up or down */
15139 void
15140 AdjustClock(Boolean which, int dir)
15141 {
15142     if(which) blackTimeRemaining += 60000*dir;
15143     else      whiteTimeRemaining += 60000*dir;
15144     DisplayBothClocks();
15145 }
15146
15147 /* Stop clocks and reset to a fresh time control */
15148 void
15149 ResetClocks()
15150 {
15151     (void) StopClockTimer();
15152     if (appData.icsActive) {
15153         whiteTimeRemaining = blackTimeRemaining = 0;
15154     } else if (searchTime) {
15155         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15156         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15157     } else { /* [HGM] correct new time quote for time odds */
15158         whiteTC = blackTC = fullTimeControlString;
15159         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15160         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15161     }
15162     if (whiteFlag || blackFlag) {
15163         DisplayTitle("");
15164         whiteFlag = blackFlag = FALSE;
15165     }
15166     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15167     DisplayBothClocks();
15168 }
15169
15170 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15171
15172 /* Decrement running clock by amount of time that has passed */
15173 void
15174 DecrementClocks()
15175 {
15176     long timeRemaining;
15177     long lastTickLength, fudge;
15178     TimeMark now;
15179
15180     if (!appData.clockMode) return;
15181     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15182
15183     GetTimeMark(&now);
15184
15185     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15186
15187     /* Fudge if we woke up a little too soon */
15188     fudge = intendedTickLength - lastTickLength;
15189     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15190
15191     if (WhiteOnMove(forwardMostMove)) {
15192         if(whiteNPS >= 0) lastTickLength = 0;
15193         timeRemaining = whiteTimeRemaining -= lastTickLength;
15194         if(timeRemaining < 0 && !appData.icsActive) {
15195             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15196             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15197                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15198                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15199             }
15200         }
15201         DisplayWhiteClock(whiteTimeRemaining - fudge,
15202                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15203     } else {
15204         if(blackNPS >= 0) lastTickLength = 0;
15205         timeRemaining = blackTimeRemaining -= lastTickLength;
15206         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15207             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15208             if(suddenDeath) {
15209                 blackStartMove = forwardMostMove;
15210                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15211             }
15212         }
15213         DisplayBlackClock(blackTimeRemaining - fudge,
15214                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15215     }
15216     if (CheckFlags()) return;
15217
15218     tickStartTM = now;
15219     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15220     StartClockTimer(intendedTickLength);
15221
15222     /* if the time remaining has fallen below the alarm threshold, sound the
15223      * alarm. if the alarm has sounded and (due to a takeback or time control
15224      * with increment) the time remaining has increased to a level above the
15225      * threshold, reset the alarm so it can sound again.
15226      */
15227
15228     if (appData.icsActive && appData.icsAlarm) {
15229
15230         /* make sure we are dealing with the user's clock */
15231         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15232                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15233            )) return;
15234
15235         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15236             alarmSounded = FALSE;
15237         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15238             PlayAlarmSound();
15239             alarmSounded = TRUE;
15240         }
15241     }
15242 }
15243
15244
15245 /* A player has just moved, so stop the previously running
15246    clock and (if in clock mode) start the other one.
15247    We redisplay both clocks in case we're in ICS mode, because
15248    ICS gives us an update to both clocks after every move.
15249    Note that this routine is called *after* forwardMostMove
15250    is updated, so the last fractional tick must be subtracted
15251    from the color that is *not* on move now.
15252 */
15253 void
15254 SwitchClocks(int newMoveNr)
15255 {
15256     long lastTickLength;
15257     TimeMark now;
15258     int flagged = FALSE;
15259
15260     GetTimeMark(&now);
15261
15262     if (StopClockTimer() && appData.clockMode) {
15263         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15264         if (!WhiteOnMove(forwardMostMove)) {
15265             if(blackNPS >= 0) lastTickLength = 0;
15266             blackTimeRemaining -= lastTickLength;
15267            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15268 //         if(pvInfoList[forwardMostMove].time == -1)
15269                  pvInfoList[forwardMostMove].time =               // use GUI time
15270                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15271         } else {
15272            if(whiteNPS >= 0) lastTickLength = 0;
15273            whiteTimeRemaining -= 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 =
15277                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15278         }
15279         flagged = CheckFlags();
15280     }
15281     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15282     CheckTimeControl();
15283
15284     if (flagged || !appData.clockMode) return;
15285
15286     switch (gameMode) {
15287       case MachinePlaysBlack:
15288       case MachinePlaysWhite:
15289       case BeginningOfGame:
15290         if (pausing) return;
15291         break;
15292
15293       case EditGame:
15294       case PlayFromGameFile:
15295       case IcsExamining:
15296         return;
15297
15298       default:
15299         break;
15300     }
15301
15302     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15303         if(WhiteOnMove(forwardMostMove))
15304              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15305         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15306     }
15307
15308     tickStartTM = now;
15309     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15310       whiteTimeRemaining : blackTimeRemaining);
15311     StartClockTimer(intendedTickLength);
15312 }
15313
15314
15315 /* Stop both clocks */
15316 void
15317 StopClocks()
15318 {
15319     long lastTickLength;
15320     TimeMark now;
15321
15322     if (!StopClockTimer()) return;
15323     if (!appData.clockMode) return;
15324
15325     GetTimeMark(&now);
15326
15327     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15328     if (WhiteOnMove(forwardMostMove)) {
15329         if(whiteNPS >= 0) lastTickLength = 0;
15330         whiteTimeRemaining -= lastTickLength;
15331         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15332     } else {
15333         if(blackNPS >= 0) lastTickLength = 0;
15334         blackTimeRemaining -= lastTickLength;
15335         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15336     }
15337     CheckFlags();
15338 }
15339
15340 /* Start clock of player on move.  Time may have been reset, so
15341    if clock is already running, stop and restart it. */
15342 void
15343 StartClocks()
15344 {
15345     (void) StopClockTimer(); /* in case it was running already */
15346     DisplayBothClocks();
15347     if (CheckFlags()) return;
15348
15349     if (!appData.clockMode) return;
15350     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15351
15352     GetTimeMark(&tickStartTM);
15353     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15354       whiteTimeRemaining : blackTimeRemaining);
15355
15356    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15357     whiteNPS = blackNPS = -1;
15358     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15359        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15360         whiteNPS = first.nps;
15361     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15362        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15363         blackNPS = first.nps;
15364     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15365         whiteNPS = second.nps;
15366     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15367         blackNPS = second.nps;
15368     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15369
15370     StartClockTimer(intendedTickLength);
15371 }
15372
15373 char *
15374 TimeString(ms)
15375      long ms;
15376 {
15377     long second, minute, hour, day;
15378     char *sign = "";
15379     static char buf[32];
15380
15381     if (ms > 0 && ms <= 9900) {
15382       /* convert milliseconds to tenths, rounding up */
15383       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15384
15385       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15386       return buf;
15387     }
15388
15389     /* convert milliseconds to seconds, rounding up */
15390     /* use floating point to avoid strangeness of integer division
15391        with negative dividends on many machines */
15392     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15393
15394     if (second < 0) {
15395         sign = "-";
15396         second = -second;
15397     }
15398
15399     day = second / (60 * 60 * 24);
15400     second = second % (60 * 60 * 24);
15401     hour = second / (60 * 60);
15402     second = second % (60 * 60);
15403     minute = second / 60;
15404     second = second % 60;
15405
15406     if (day > 0)
15407       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15408               sign, day, hour, minute, second);
15409     else if (hour > 0)
15410       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15411     else
15412       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15413
15414     return buf;
15415 }
15416
15417
15418 /*
15419  * This is necessary because some C libraries aren't ANSI C compliant yet.
15420  */
15421 char *
15422 StrStr(string, match)
15423      char *string, *match;
15424 {
15425     int i, length;
15426
15427     length = strlen(match);
15428
15429     for (i = strlen(string) - length; i >= 0; i--, string++)
15430       if (!strncmp(match, string, length))
15431         return string;
15432
15433     return NULL;
15434 }
15435
15436 char *
15437 StrCaseStr(string, match)
15438      char *string, *match;
15439 {
15440     int i, j, length;
15441
15442     length = strlen(match);
15443
15444     for (i = strlen(string) - length; i >= 0; i--, string++) {
15445         for (j = 0; j < length; j++) {
15446             if (ToLower(match[j]) != ToLower(string[j]))
15447               break;
15448         }
15449         if (j == length) return string;
15450     }
15451
15452     return NULL;
15453 }
15454
15455 #ifndef _amigados
15456 int
15457 StrCaseCmp(s1, s2)
15458      char *s1, *s2;
15459 {
15460     char c1, c2;
15461
15462     for (;;) {
15463         c1 = ToLower(*s1++);
15464         c2 = ToLower(*s2++);
15465         if (c1 > c2) return 1;
15466         if (c1 < c2) return -1;
15467         if (c1 == NULLCHAR) return 0;
15468     }
15469 }
15470
15471
15472 int
15473 ToLower(c)
15474      int c;
15475 {
15476     return isupper(c) ? tolower(c) : c;
15477 }
15478
15479
15480 int
15481 ToUpper(c)
15482      int c;
15483 {
15484     return islower(c) ? toupper(c) : c;
15485 }
15486 #endif /* !_amigados    */
15487
15488 char *
15489 StrSave(s)
15490      char *s;
15491 {
15492   char *ret;
15493
15494   if ((ret = (char *) malloc(strlen(s) + 1)))
15495     {
15496       safeStrCpy(ret, s, strlen(s)+1);
15497     }
15498   return ret;
15499 }
15500
15501 char *
15502 StrSavePtr(s, savePtr)
15503      char *s, **savePtr;
15504 {
15505     if (*savePtr) {
15506         free(*savePtr);
15507     }
15508     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15509       safeStrCpy(*savePtr, s, strlen(s)+1);
15510     }
15511     return(*savePtr);
15512 }
15513
15514 char *
15515 PGNDate()
15516 {
15517     time_t clock;
15518     struct tm *tm;
15519     char buf[MSG_SIZ];
15520
15521     clock = time((time_t *)NULL);
15522     tm = localtime(&clock);
15523     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15524             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15525     return StrSave(buf);
15526 }
15527
15528
15529 char *
15530 PositionToFEN(move, overrideCastling)
15531      int move;
15532      char *overrideCastling;
15533 {
15534     int i, j, fromX, fromY, toX, toY;
15535     int whiteToPlay;
15536     char buf[128];
15537     char *p, *q;
15538     int emptycount;
15539     ChessSquare piece;
15540
15541     whiteToPlay = (gameMode == EditPosition) ?
15542       !blackPlaysFirst : (move % 2 == 0);
15543     p = buf;
15544
15545     /* Piece placement data */
15546     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15547         emptycount = 0;
15548         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15549             if (boards[move][i][j] == EmptySquare) {
15550                 emptycount++;
15551             } else { ChessSquare piece = boards[move][i][j];
15552                 if (emptycount > 0) {
15553                     if(emptycount<10) /* [HGM] can be >= 10 */
15554                         *p++ = '0' + emptycount;
15555                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15556                     emptycount = 0;
15557                 }
15558                 if(PieceToChar(piece) == '+') {
15559                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15560                     *p++ = '+';
15561                     piece = (ChessSquare)(DEMOTED piece);
15562                 }
15563                 *p++ = PieceToChar(piece);
15564                 if(p[-1] == '~') {
15565                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15566                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15567                     *p++ = '~';
15568                 }
15569             }
15570         }
15571         if (emptycount > 0) {
15572             if(emptycount<10) /* [HGM] can be >= 10 */
15573                 *p++ = '0' + emptycount;
15574             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15575             emptycount = 0;
15576         }
15577         *p++ = '/';
15578     }
15579     *(p - 1) = ' ';
15580
15581     /* [HGM] print Crazyhouse or Shogi holdings */
15582     if( gameInfo.holdingsWidth ) {
15583         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15584         q = p;
15585         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15586             piece = boards[move][i][BOARD_WIDTH-1];
15587             if( piece != EmptySquare )
15588               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15589                   *p++ = PieceToChar(piece);
15590         }
15591         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15592             piece = boards[move][BOARD_HEIGHT-i-1][0];
15593             if( piece != EmptySquare )
15594               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15595                   *p++ = PieceToChar(piece);
15596         }
15597
15598         if( q == p ) *p++ = '-';
15599         *p++ = ']';
15600         *p++ = ' ';
15601     }
15602
15603     /* Active color */
15604     *p++ = whiteToPlay ? 'w' : 'b';
15605     *p++ = ' ';
15606
15607   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15608     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15609   } else {
15610   if(nrCastlingRights) {
15611      q = p;
15612      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15613        /* [HGM] write directly from rights */
15614            if(boards[move][CASTLING][2] != NoRights &&
15615               boards[move][CASTLING][0] != NoRights   )
15616                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15617            if(boards[move][CASTLING][2] != NoRights &&
15618               boards[move][CASTLING][1] != NoRights   )
15619                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15620            if(boards[move][CASTLING][5] != NoRights &&
15621               boards[move][CASTLING][3] != NoRights   )
15622                 *p++ = boards[move][CASTLING][3] + AAA;
15623            if(boards[move][CASTLING][5] != NoRights &&
15624               boards[move][CASTLING][4] != NoRights   )
15625                 *p++ = boards[move][CASTLING][4] + AAA;
15626      } else {
15627
15628         /* [HGM] write true castling rights */
15629         if( nrCastlingRights == 6 ) {
15630             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15631                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15632             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15633                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15634             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15635                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15636             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15637                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15638         }
15639      }
15640      if (q == p) *p++ = '-'; /* No castling rights */
15641      *p++ = ' ';
15642   }
15643
15644   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15645      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15646     /* En passant target square */
15647     if (move > backwardMostMove) {
15648         fromX = moveList[move - 1][0] - AAA;
15649         fromY = moveList[move - 1][1] - ONE;
15650         toX = moveList[move - 1][2] - AAA;
15651         toY = moveList[move - 1][3] - ONE;
15652         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15653             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15654             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15655             fromX == toX) {
15656             /* 2-square pawn move just happened */
15657             *p++ = toX + AAA;
15658             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15659         } else {
15660             *p++ = '-';
15661         }
15662     } else if(move == backwardMostMove) {
15663         // [HGM] perhaps we should always do it like this, and forget the above?
15664         if((signed char)boards[move][EP_STATUS] >= 0) {
15665             *p++ = boards[move][EP_STATUS] + AAA;
15666             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15667         } else {
15668             *p++ = '-';
15669         }
15670     } else {
15671         *p++ = '-';
15672     }
15673     *p++ = ' ';
15674   }
15675   }
15676
15677     /* [HGM] find reversible plies */
15678     {   int i = 0, j=move;
15679
15680         if (appData.debugMode) { int k;
15681             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15682             for(k=backwardMostMove; k<=forwardMostMove; k++)
15683                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15684
15685         }
15686
15687         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15688         if( j == backwardMostMove ) i += initialRulePlies;
15689         sprintf(p, "%d ", i);
15690         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15691     }
15692     /* Fullmove number */
15693     sprintf(p, "%d", (move / 2) + 1);
15694
15695     return StrSave(buf);
15696 }
15697
15698 Boolean
15699 ParseFEN(board, blackPlaysFirst, fen)
15700     Board board;
15701      int *blackPlaysFirst;
15702      char *fen;
15703 {
15704     int i, j;
15705     char *p, c;
15706     int emptycount;
15707     ChessSquare piece;
15708
15709     p = fen;
15710
15711     /* [HGM] by default clear Crazyhouse holdings, if present */
15712     if(gameInfo.holdingsWidth) {
15713        for(i=0; i<BOARD_HEIGHT; i++) {
15714            board[i][0]             = EmptySquare; /* black holdings */
15715            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15716            board[i][1]             = (ChessSquare) 0; /* black counts */
15717            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15718        }
15719     }
15720
15721     /* Piece placement data */
15722     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15723         j = 0;
15724         for (;;) {
15725             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15726                 if (*p == '/') p++;
15727                 emptycount = gameInfo.boardWidth - j;
15728                 while (emptycount--)
15729                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15730                 break;
15731 #if(BOARD_FILES >= 10)
15732             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15733                 p++; emptycount=10;
15734                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15735                 while (emptycount--)
15736                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15737 #endif
15738             } else if (isdigit(*p)) {
15739                 emptycount = *p++ - '0';
15740                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15741                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15742                 while (emptycount--)
15743                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15744             } else if (*p == '+' || isalpha(*p)) {
15745                 if (j >= gameInfo.boardWidth) return FALSE;
15746                 if(*p=='+') {
15747                     piece = CharToPiece(*++p);
15748                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15749                     piece = (ChessSquare) (PROMOTED piece ); p++;
15750                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15751                 } else piece = CharToPiece(*p++);
15752
15753                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15754                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15755                     piece = (ChessSquare) (PROMOTED piece);
15756                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15757                     p++;
15758                 }
15759                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15760             } else {
15761                 return FALSE;
15762             }
15763         }
15764     }
15765     while (*p == '/' || *p == ' ') p++;
15766
15767     /* [HGM] look for Crazyhouse holdings here */
15768     while(*p==' ') p++;
15769     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15770         if(*p == '[') p++;
15771         if(*p == '-' ) p++; /* empty holdings */ else {
15772             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15773             /* if we would allow FEN reading to set board size, we would   */
15774             /* have to add holdings and shift the board read so far here   */
15775             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15776                 p++;
15777                 if((int) piece >= (int) BlackPawn ) {
15778                     i = (int)piece - (int)BlackPawn;
15779                     i = PieceToNumber((ChessSquare)i);
15780                     if( i >= gameInfo.holdingsSize ) return FALSE;
15781                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15782                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15783                 } else {
15784                     i = (int)piece - (int)WhitePawn;
15785                     i = PieceToNumber((ChessSquare)i);
15786                     if( i >= gameInfo.holdingsSize ) return FALSE;
15787                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15788                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15789                 }
15790             }
15791         }
15792         if(*p == ']') p++;
15793     }
15794
15795     while(*p == ' ') p++;
15796
15797     /* Active color */
15798     c = *p++;
15799     if(appData.colorNickNames) {
15800       if( c == appData.colorNickNames[0] ) c = 'w'; else
15801       if( c == appData.colorNickNames[1] ) c = 'b';
15802     }
15803     switch (c) {
15804       case 'w':
15805         *blackPlaysFirst = FALSE;
15806         break;
15807       case 'b':
15808         *blackPlaysFirst = TRUE;
15809         break;
15810       default:
15811         return FALSE;
15812     }
15813
15814     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15815     /* return the extra info in global variiables             */
15816
15817     /* set defaults in case FEN is incomplete */
15818     board[EP_STATUS] = EP_UNKNOWN;
15819     for(i=0; i<nrCastlingRights; i++ ) {
15820         board[CASTLING][i] =
15821             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15822     }   /* assume possible unless obviously impossible */
15823     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15824     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15825     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15826                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15827     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15828     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15829     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15830                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15831     FENrulePlies = 0;
15832
15833     while(*p==' ') p++;
15834     if(nrCastlingRights) {
15835       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15836           /* castling indicator present, so default becomes no castlings */
15837           for(i=0; i<nrCastlingRights; i++ ) {
15838                  board[CASTLING][i] = NoRights;
15839           }
15840       }
15841       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15842              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15843              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15844              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15845         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15846
15847         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15848             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15849             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15850         }
15851         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15852             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15853         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15854                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15855         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15856                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15857         switch(c) {
15858           case'K':
15859               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15860               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15861               board[CASTLING][2] = whiteKingFile;
15862               break;
15863           case'Q':
15864               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15865               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15866               board[CASTLING][2] = whiteKingFile;
15867               break;
15868           case'k':
15869               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15870               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15871               board[CASTLING][5] = blackKingFile;
15872               break;
15873           case'q':
15874               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15875               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15876               board[CASTLING][5] = blackKingFile;
15877           case '-':
15878               break;
15879           default: /* FRC castlings */
15880               if(c >= 'a') { /* black rights */
15881                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15882                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15883                   if(i == BOARD_RGHT) break;
15884                   board[CASTLING][5] = i;
15885                   c -= AAA;
15886                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15887                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15888                   if(c > i)
15889                       board[CASTLING][3] = c;
15890                   else
15891                       board[CASTLING][4] = c;
15892               } else { /* white rights */
15893                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15894                     if(board[0][i] == WhiteKing) break;
15895                   if(i == BOARD_RGHT) break;
15896                   board[CASTLING][2] = i;
15897                   c -= AAA - 'a' + 'A';
15898                   if(board[0][c] >= WhiteKing) break;
15899                   if(c > i)
15900                       board[CASTLING][0] = c;
15901                   else
15902                       board[CASTLING][1] = c;
15903               }
15904         }
15905       }
15906       for(i=0; i<nrCastlingRights; i++)
15907         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15908     if (appData.debugMode) {
15909         fprintf(debugFP, "FEN castling rights:");
15910         for(i=0; i<nrCastlingRights; i++)
15911         fprintf(debugFP, " %d", board[CASTLING][i]);
15912         fprintf(debugFP, "\n");
15913     }
15914
15915       while(*p==' ') p++;
15916     }
15917
15918     /* read e.p. field in games that know e.p. capture */
15919     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15920        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15921       if(*p=='-') {
15922         p++; board[EP_STATUS] = EP_NONE;
15923       } else {
15924          char c = *p++ - AAA;
15925
15926          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15927          if(*p >= '0' && *p <='9') p++;
15928          board[EP_STATUS] = c;
15929       }
15930     }
15931
15932
15933     if(sscanf(p, "%d", &i) == 1) {
15934         FENrulePlies = i; /* 50-move ply counter */
15935         /* (The move number is still ignored)    */
15936     }
15937
15938     return TRUE;
15939 }
15940
15941 void
15942 EditPositionPasteFEN(char *fen)
15943 {
15944   if (fen != NULL) {
15945     Board initial_position;
15946
15947     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15948       DisplayError(_("Bad FEN position in clipboard"), 0);
15949       return ;
15950     } else {
15951       int savedBlackPlaysFirst = blackPlaysFirst;
15952       EditPositionEvent();
15953       blackPlaysFirst = savedBlackPlaysFirst;
15954       CopyBoard(boards[0], initial_position);
15955       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15956       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15957       DisplayBothClocks();
15958       DrawPosition(FALSE, boards[currentMove]);
15959     }
15960   }
15961 }
15962
15963 static char cseq[12] = "\\   ";
15964
15965 Boolean set_cont_sequence(char *new_seq)
15966 {
15967     int len;
15968     Boolean ret;
15969
15970     // handle bad attempts to set the sequence
15971         if (!new_seq)
15972                 return 0; // acceptable error - no debug
15973
15974     len = strlen(new_seq);
15975     ret = (len > 0) && (len < sizeof(cseq));
15976     if (ret)
15977       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15978     else if (appData.debugMode)
15979       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15980     return ret;
15981 }
15982
15983 /*
15984     reformat a source message so words don't cross the width boundary.  internal
15985     newlines are not removed.  returns the wrapped size (no null character unless
15986     included in source message).  If dest is NULL, only calculate the size required
15987     for the dest buffer.  lp argument indicats line position upon entry, and it's
15988     passed back upon exit.
15989 */
15990 int wrap(char *dest, char *src, int count, int width, int *lp)
15991 {
15992     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15993
15994     cseq_len = strlen(cseq);
15995     old_line = line = *lp;
15996     ansi = len = clen = 0;
15997
15998     for (i=0; i < count; i++)
15999     {
16000         if (src[i] == '\033')
16001             ansi = 1;
16002
16003         // if we hit the width, back up
16004         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16005         {
16006             // store i & len in case the word is too long
16007             old_i = i, old_len = len;
16008
16009             // find the end of the last word
16010             while (i && src[i] != ' ' && src[i] != '\n')
16011             {
16012                 i--;
16013                 len--;
16014             }
16015
16016             // word too long?  restore i & len before splitting it
16017             if ((old_i-i+clen) >= width)
16018             {
16019                 i = old_i;
16020                 len = old_len;
16021             }
16022
16023             // extra space?
16024             if (i && src[i-1] == ' ')
16025                 len--;
16026
16027             if (src[i] != ' ' && src[i] != '\n')
16028             {
16029                 i--;
16030                 if (len)
16031                     len--;
16032             }
16033
16034             // now append the newline and continuation sequence
16035             if (dest)
16036                 dest[len] = '\n';
16037             len++;
16038             if (dest)
16039                 strncpy(dest+len, cseq, cseq_len);
16040             len += cseq_len;
16041             line = cseq_len;
16042             clen = cseq_len;
16043             continue;
16044         }
16045
16046         if (dest)
16047             dest[len] = src[i];
16048         len++;
16049         if (!ansi)
16050             line++;
16051         if (src[i] == '\n')
16052             line = 0;
16053         if (src[i] == 'm')
16054             ansi = 0;
16055     }
16056     if (dest && appData.debugMode)
16057     {
16058         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16059             count, width, line, len, *lp);
16060         show_bytes(debugFP, src, count);
16061         fprintf(debugFP, "\ndest: ");
16062         show_bytes(debugFP, dest, len);
16063         fprintf(debugFP, "\n");
16064     }
16065     *lp = dest ? line : old_line;
16066
16067     return len;
16068 }
16069
16070 // [HGM] vari: routines for shelving variations
16071
16072 void
16073 PushInner(int firstMove, int lastMove)
16074 {
16075         int i, j, nrMoves = lastMove - firstMove;
16076
16077         // push current tail of game on stack
16078         savedResult[storedGames] = gameInfo.result;
16079         savedDetails[storedGames] = gameInfo.resultDetails;
16080         gameInfo.resultDetails = NULL;
16081         savedFirst[storedGames] = firstMove;
16082         savedLast [storedGames] = lastMove;
16083         savedFramePtr[storedGames] = framePtr;
16084         framePtr -= nrMoves; // reserve space for the boards
16085         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16086             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16087             for(j=0; j<MOVE_LEN; j++)
16088                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16089             for(j=0; j<2*MOVE_LEN; j++)
16090                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16091             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16092             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16093             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16094             pvInfoList[firstMove+i-1].depth = 0;
16095             commentList[framePtr+i] = commentList[firstMove+i];
16096             commentList[firstMove+i] = NULL;
16097         }
16098
16099         storedGames++;
16100         forwardMostMove = firstMove; // truncate game so we can start variation
16101 }
16102
16103 void
16104 PushTail(int firstMove, int lastMove)
16105 {
16106         if(appData.icsActive) { // only in local mode
16107                 forwardMostMove = currentMove; // mimic old ICS behavior
16108                 return;
16109         }
16110         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16111
16112         PushInner(firstMove, lastMove);
16113         if(storedGames == 1) GreyRevert(FALSE);
16114 }
16115
16116 void
16117 PopInner(Boolean annotate)
16118 {
16119         int i, j, nrMoves;
16120         char buf[8000], moveBuf[20];
16121
16122         storedGames--;
16123         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16124         nrMoves = savedLast[storedGames] - currentMove;
16125         if(annotate) {
16126                 int cnt = 10;
16127                 if(!WhiteOnMove(currentMove))
16128                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16129                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16130                 for(i=currentMove; i<forwardMostMove; i++) {
16131                         if(WhiteOnMove(i))
16132                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16133                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16134                         strcat(buf, moveBuf);
16135                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16136                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16137                 }
16138                 strcat(buf, ")");
16139         }
16140         for(i=1; i<=nrMoves; i++) { // copy last variation back
16141             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16142             for(j=0; j<MOVE_LEN; j++)
16143                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16144             for(j=0; j<2*MOVE_LEN; j++)
16145                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16146             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16147             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16148             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16149             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16150             commentList[currentMove+i] = commentList[framePtr+i];
16151             commentList[framePtr+i] = NULL;
16152         }
16153         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16154         framePtr = savedFramePtr[storedGames];
16155         gameInfo.result = savedResult[storedGames];
16156         if(gameInfo.resultDetails != NULL) {
16157             free(gameInfo.resultDetails);
16158       }
16159         gameInfo.resultDetails = savedDetails[storedGames];
16160         forwardMostMove = currentMove + nrMoves;
16161 }
16162
16163 Boolean
16164 PopTail(Boolean annotate)
16165 {
16166         if(appData.icsActive) return FALSE; // only in local mode
16167         if(!storedGames) return FALSE; // sanity
16168         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16169
16170         PopInner(annotate);
16171
16172         if(storedGames == 0) GreyRevert(TRUE);
16173         return TRUE;
16174 }
16175
16176 void
16177 CleanupTail()
16178 {       // remove all shelved variations
16179         int i;
16180         for(i=0; i<storedGames; i++) {
16181             if(savedDetails[i])
16182                 free(savedDetails[i]);
16183             savedDetails[i] = NULL;
16184         }
16185         for(i=framePtr; i<MAX_MOVES; i++) {
16186                 if(commentList[i]) free(commentList[i]);
16187                 commentList[i] = NULL;
16188         }
16189         framePtr = MAX_MOVES-1;
16190         storedGames = 0;
16191 }
16192
16193 void
16194 LoadVariation(int index, char *text)
16195 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16196         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16197         int level = 0, move;
16198
16199         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16200         // first find outermost bracketing variation
16201         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16202             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16203                 if(*p == '{') wait = '}'; else
16204                 if(*p == '[') wait = ']'; else
16205                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16206                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16207             }
16208             if(*p == wait) wait = NULLCHAR; // closing ]} found
16209             p++;
16210         }
16211         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16212         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16213         end[1] = NULLCHAR; // clip off comment beyond variation
16214         ToNrEvent(currentMove-1);
16215         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16216         // kludge: use ParsePV() to append variation to game
16217         move = currentMove;
16218         ParsePV(start, TRUE);
16219         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16220         ClearPremoveHighlights();
16221         CommentPopDown();
16222         ToNrEvent(currentMove+1);
16223 }
16224