265840258b89f0002e82ea18c21fe141f9e1e7ef
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236
237 #ifdef WIN32
238        extern void ConsoleCreate();
239 #endif
240
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
244
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
252 Boolean abortMatch;
253
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
257 int endPV = -1;
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
261 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
265 Boolean partnerUp;
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
277 int chattingPartner;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
283
284 /* States for ics_getting_history */
285 #define H_FALSE 0
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
291
292 /* whosays values for GameEnds */
293 #define GE_ICS 0
294 #define GE_ENGINE 1
295 #define GE_PLAYER 2
296 #define GE_FILE 3
297 #define GE_XBOARD 4
298 #define GE_ENGINE1 5
299 #define GE_ENGINE2 6
300
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
303
304 /* Different types of move when calling RegisterMove */
305 #define CMAIL_MOVE   0
306 #define CMAIL_RESIGN 1
307 #define CMAIL_DRAW   2
308 #define CMAIL_ACCEPT 3
309
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
314
315 /* Telnet protocol constants */
316 #define TN_WILL 0373
317 #define TN_WONT 0374
318 #define TN_DO   0375
319 #define TN_DONT 0376
320 #define TN_IAC  0377
321 #define TN_ECHO 0001
322 #define TN_SGA  0003
323 #define TN_PORT 23
324
325 char*
326 safeStrCpy( char *dst, const char *src, size_t count )
327 { // [HGM] made safe
328   int i;
329   assert( dst != NULL );
330   assert( src != NULL );
331   assert( count > 0 );
332
333   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334   if(  i == count && dst[count-1] != NULLCHAR)
335     {
336       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337       if(appData.debugMode)
338       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339     }
340
341   return dst;
342 }
343
344 /* Some compiler can't cast u64 to double
345  * This function do the job for us:
346
347  * We use the highest bit for cast, this only
348  * works if the highest bit is not
349  * in use (This should not happen)
350  *
351  * We used this for all compiler
352  */
353 double
354 u64ToDouble(u64 value)
355 {
356   double r;
357   u64 tmp = value & u64Const(0x7fffffffffffffff);
358   r = (double)(s64)tmp;
359   if (value & u64Const(0x8000000000000000))
360        r +=  9.2233720368547758080e18; /* 2^63 */
361  return r;
362 }
363
364 /* Fake up flags for now, as we aren't keeping track of castling
365    availability yet. [HGM] Change of logic: the flag now only
366    indicates the type of castlings allowed by the rule of the game.
367    The actual rights themselves are maintained in the array
368    castlingRights, as part of the game history, and are not probed
369    by this function.
370  */
371 int
372 PosFlags(index)
373 {
374   int flags = F_ALL_CASTLE_OK;
375   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376   switch (gameInfo.variant) {
377   case VariantSuicide:
378     flags &= ~F_ALL_CASTLE_OK;
379   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380     flags |= F_IGNORE_CHECK;
381   case VariantLosers:
382     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
383     break;
384   case VariantAtomic:
385     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
386     break;
387   case VariantKriegspiel:
388     flags |= F_KRIEGSPIEL_CAPTURE;
389     break;
390   case VariantCapaRandom:
391   case VariantFischeRandom:
392     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393   case VariantNoCastle:
394   case VariantShatranj:
395   case VariantCourier:
396   case VariantMakruk:
397     flags &= ~F_ALL_CASTLE_OK;
398     break;
399   default:
400     break;
401   }
402   return flags;
403 }
404
405 FILE *gameFileFP, *debugFP;
406
407 /*
408     [AS] Note: sometimes, the sscanf() function is used to parse the input
409     into a fixed-size buffer. Because of this, we must be prepared to
410     receive strings as long as the size of the input buffer, which is currently
411     set to 4K for Windows and 8K for the rest.
412     So, we must either allocate sufficiently large buffers here, or
413     reduce the size of the input buffer in the input reading part.
414 */
415
416 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
417 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
418 char thinkOutput1[MSG_SIZ*10];
419
420 ChessProgramState first, second;
421
422 /* premove variables */
423 int premoveToX = 0;
424 int premoveToY = 0;
425 int premoveFromX = 0;
426 int premoveFromY = 0;
427 int premovePromoChar = 0;
428 int gotPremove = 0;
429 Boolean alarmSounded;
430 /* end premove variables */
431
432 char *ics_prefix = "$";
433 int ics_type = ICS_GENERIC;
434
435 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
436 int pauseExamForwardMostMove = 0;
437 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
438 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
439 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
440 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
441 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
442 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
443 int whiteFlag = FALSE, blackFlag = FALSE;
444 int userOfferedDraw = FALSE;
445 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
446 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
447 int cmailMoveType[CMAIL_MAX_GAMES];
448 long ics_clock_paused = 0;
449 ProcRef icsPR = NoProc, cmailPR = NoProc;
450 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
451 GameMode gameMode = BeginningOfGame;
452 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
453 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
454 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
455 int hiddenThinkOutputState = 0; /* [AS] */
456 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
457 int adjudicateLossPlies = 6;
458 char white_holding[64], black_holding[64];
459 TimeMark lastNodeCountTime;
460 long lastNodeCount=0;
461 int shiftKey; // [HGM] set by mouse handler
462
463 int have_sent_ICS_logon = 0;
464 int movesPerSession;
465 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
466 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
475
476 /* animateTraining preserves the state of appData.animate
477  * when Training mode is activated. This allows the
478  * response to be animated when appData.animate == TRUE and
479  * appData.animateDragging == TRUE.
480  */
481 Boolean animateTraining;
482
483 GameInfo gameInfo;
484
485 AppData appData;
486
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char  initialRights[BOARD_FILES];
491 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int   initialRulePlies, FENrulePlies;
493 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 int loadFlag = 0;
495 int shuffleOpenings;
496 int mute; // mute all sounds
497
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int storedGames = 0;
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
507
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
513
514 ChessSquare  FIDEArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackBishop, BlackKnight, BlackRook }
519 };
520
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525         BlackKing, BlackKing, BlackKnight, BlackRook }
526 };
527
528 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531     { BlackRook, BlackMan, BlackBishop, BlackQueen,
532         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
533 };
534
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
540 };
541
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
547 };
548
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 };
555
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackMan, BlackFerz,
560         BlackKing, BlackMan, BlackKnight, BlackRook }
561 };
562
563
564 #if (BOARD_FILES>=10)
565 ChessSquare ShogiArray[2][BOARD_FILES] = {
566     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
567         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
568     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
569         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
570 };
571
572 ChessSquare XiangqiArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
574         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
576         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
577 };
578
579 ChessSquare CapablancaArray[2][BOARD_FILES] = {
580     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
581         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
583         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
584 };
585
586 ChessSquare GreatArray[2][BOARD_FILES] = {
587     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
588         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
589     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
590         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
591 };
592
593 ChessSquare JanusArray[2][BOARD_FILES] = {
594     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
595         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
596     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
597         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
614         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
616         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating(str)
649   char *str;
650 {
651   while(*str && !isdigit(*str)) ++str;
652   if (!*str)
653     return 0;   /* One of the special "no rating" cases */
654   else
655     return atoi(str);
656 }
657
658 void
659 ClearProgramStats()
660 {
661     /* Init programStats */
662     programStats.movelist[0] = 0;
663     programStats.depth = 0;
664     programStats.nr_moves = 0;
665     programStats.moves_left = 0;
666     programStats.nodes = 0;
667     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
668     programStats.score = 0;
669     programStats.got_only_move = 0;
670     programStats.got_fail = 0;
671     programStats.line_is_book = 0;
672 }
673
674 void
675 CommonEngineInit()
676 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677     if (appData.firstPlaysBlack) {
678         first.twoMachinesColor = "black\n";
679         second.twoMachinesColor = "white\n";
680     } else {
681         first.twoMachinesColor = "white\n";
682         second.twoMachinesColor = "black\n";
683     }
684
685     first.other = &second;
686     second.other = &first;
687
688     { float norm = 1;
689         if(appData.timeOddsMode) {
690             norm = appData.timeOdds[0];
691             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692         }
693         first.timeOdds  = appData.timeOdds[0]/norm;
694         second.timeOdds = appData.timeOdds[1]/norm;
695     }
696
697     if(programVersion) free(programVersion);
698     if (appData.noChessProgram) {
699         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700         sprintf(programVersion, "%s", PACKAGE_STRING);
701     } else {
702       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
705     }
706 }
707
708 void
709 UnloadEngine(ChessProgramState *cps)
710 {
711         /* Kill off first chess program */
712         if (cps->isr != NULL)
713           RemoveInputSource(cps->isr);
714         cps->isr = NULL;
715
716         if (cps->pr != NoProc) {
717             ExitAnalyzeMode();
718             DoSleep( appData.delayBeforeQuit );
719             SendToProgram("quit\n", cps);
720             DoSleep( appData.delayAfterQuit );
721             DestroyChildProcess(cps->pr, cps->useSigterm);
722         }
723         cps->pr = NoProc;
724         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 }
726
727 void
728 ClearOptions(ChessProgramState *cps)
729 {
730     int i;
731     cps->nrOptions = cps->comboCnt = 0;
732     for(i=0; i<MAX_OPTIONS; i++) {
733         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734         cps->option[i].textValue = 0;
735     }
736 }
737
738 char *engineNames[] = {
739 "first",
740 "second"
741 };
742
743 void
744 InitEngine(ChessProgramState *cps, int n)
745 {   // [HGM] all engine initialiation put in a function that does one engine
746
747     ClearOptions(cps);
748
749     cps->which = engineNames[n];
750     cps->maybeThinking = FALSE;
751     cps->pr = NoProc;
752     cps->isr = NULL;
753     cps->sendTime = 2;
754     cps->sendDrawOffers = 1;
755
756     cps->program = appData.chessProgram[n];
757     cps->host = appData.host[n];
758     cps->dir = appData.directory[n];
759     cps->initString = appData.engInitString[n];
760     cps->computerString = appData.computerString[n];
761     cps->useSigint  = TRUE;
762     cps->useSigterm = TRUE;
763     cps->reuse = appData.reuse[n];
764     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
765     cps->useSetboard = FALSE;
766     cps->useSAN = FALSE;
767     cps->usePing = FALSE;
768     cps->lastPing = 0;
769     cps->lastPong = 0;
770     cps->usePlayother = FALSE;
771     cps->useColors = TRUE;
772     cps->useUsermove = FALSE;
773     cps->sendICS = FALSE;
774     cps->sendName = appData.icsActive;
775     cps->sdKludge = FALSE;
776     cps->stKludge = FALSE;
777     TidyProgramName(cps->program, cps->host, cps->tidy);
778     cps->matchWins = 0;
779     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
780     cps->analysisSupport = 2; /* detect */
781     cps->analyzing = FALSE;
782     cps->initDone = FALSE;
783
784     /* New features added by Tord: */
785     cps->useFEN960 = FALSE;
786     cps->useOOCastle = TRUE;
787     /* End of new features added by Tord. */
788     cps->fenOverride  = appData.fenOverride[n];
789
790     /* [HGM] time odds: set factor for each machine */
791     cps->timeOdds  = appData.timeOdds[n];
792
793     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
794     cps->accumulateTC = appData.accumulateTC[n];
795     cps->maxNrOfSessions = 1;
796
797     /* [HGM] debug */
798     cps->debug = FALSE;
799     cps->supportsNPS = UNKNOWN;
800
801     /* [HGM] options */
802     cps->optionSettings  = appData.engOptions[n];
803
804     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
805     cps->isUCI = appData.isUCI[n]; /* [AS] */
806     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
807
808     if (appData.protocolVersion[n] > PROTOVER
809         || appData.protocolVersion[n] < 1)
810       {
811         char buf[MSG_SIZ];
812         int len;
813
814         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
815                        appData.protocolVersion[n]);
816         if( (len > MSG_SIZ) && appData.debugMode )
817           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
818
819         DisplayFatalError(buf, 0, 2);
820       }
821     else
822       {
823         cps->protocolVersion = appData.protocolVersion[n];
824       }
825
826     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
827 }
828
829 ChessProgramState *savCps;
830
831 void
832 LoadEngine()
833 {
834     int i;
835     if(WaitForEngine(savCps, LoadEngine)) return;
836     CommonEngineInit(); // recalculate time odds
837     if(gameInfo.variant != StringToVariant(appData.variant)) {
838         // we changed variant when loading the engine; this forces us to reset
839         Reset(TRUE, savCps != &first);
840         EditGameEvent(); // for consistency with other path, as Reset changes mode
841     }
842     InitChessProgram(savCps, FALSE);
843     SendToProgram("force\n", savCps);
844     DisplayMessage("", "");
845     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
846     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
847     ThawUI();
848     SetGNUMode();
849 }
850
851 void
852 ReplaceEngine(ChessProgramState *cps, int n)
853 {
854     EditGameEvent();
855     UnloadEngine(cps);
856     appData.noChessProgram = FALSE;
857     appData.clockMode = TRUE;
858     InitEngine(cps, n);
859     if(n) return; // only startup first engine immediately; second can wait
860     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
861     LoadEngine();
862 }
863
864 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
865 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
866
867 static char resetOptions[] = 
868         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
869         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
870
871 void
872 Load(ChessProgramState *cps, int i)
873 {
874     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
875     if(engineLine[0]) { // an engine was selected from the combo box
876         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
877         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
878         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
879         ParseArgsFromString(buf);
880         SwapEngines(i);
881         ReplaceEngine(cps, i);
882         return;
883     }
884     p = engineName;
885     while(q = strchr(p, SLASH)) p = q+1;
886     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
887     if(engineDir[0] != NULLCHAR)
888         appData.directory[i] = engineDir;
889     else if(p != engineName) { // derive directory from engine path, when not given
890         p[-1] = 0;
891         appData.directory[i] = strdup(engineName);
892         p[-1] = SLASH;
893     } else appData.directory[i] = ".";
894     if(params[0]) {
895         snprintf(command, MSG_SIZ, "%s %s", p, params);
896         p = command;
897     }
898     appData.chessProgram[i] = strdup(p);
899     appData.isUCI[i] = isUCI;
900     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
901     appData.hasOwnBookUCI[i] = hasBook;
902     if(!nickName[0]) useNick = FALSE;
903     if(useNick) ASSIGN(appData.pgnName[i], nickName);
904     if(addToList) {
905         int len;
906         q = firstChessProgramNames;
907         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
908         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
909                         useNick ? " -fn \"" : "",
910                         useNick ? nickName : "",
911                         useNick ? "\"" : "",
912                         v1 ? " -firstProtocolVersion 1" : "",
913                         hasBook ? "" : " -fNoOwnBookUCI",
914                         isUCI ? " -fUCI" : "",
915                         storeVariant ? " -variant " : "",
916                         storeVariant ? VariantName(gameInfo.variant) : "");
917         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
918         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
919         if(q)   free(q);
920     }
921     ReplaceEngine(cps, i);
922 }
923
924 void
925 InitTimeControls()
926 {
927     int matched, min, sec;
928     /*
929      * Parse timeControl resource
930      */
931     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
932                           appData.movesPerSession)) {
933         char buf[MSG_SIZ];
934         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
935         DisplayFatalError(buf, 0, 2);
936     }
937
938     /*
939      * Parse searchTime resource
940      */
941     if (*appData.searchTime != NULLCHAR) {
942         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
943         if (matched == 1) {
944             searchTime = min * 60;
945         } else if (matched == 2) {
946             searchTime = min * 60 + sec;
947         } else {
948             char buf[MSG_SIZ];
949             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
950             DisplayFatalError(buf, 0, 2);
951         }
952     }
953 }
954
955 void
956 InitBackEnd1()
957 {
958
959     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
960     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
961
962     GetTimeMark(&programStartTime);
963     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
964     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
965
966     ClearProgramStats();
967     programStats.ok_to_send = 1;
968     programStats.seen_stat = 0;
969
970     /*
971      * Initialize game list
972      */
973     ListNew(&gameList);
974
975
976     /*
977      * Internet chess server status
978      */
979     if (appData.icsActive) {
980         appData.matchMode = FALSE;
981         appData.matchGames = 0;
982 #if ZIPPY
983         appData.noChessProgram = !appData.zippyPlay;
984 #else
985         appData.zippyPlay = FALSE;
986         appData.zippyTalk = FALSE;
987         appData.noChessProgram = TRUE;
988 #endif
989         if (*appData.icsHelper != NULLCHAR) {
990             appData.useTelnet = TRUE;
991             appData.telnetProgram = appData.icsHelper;
992         }
993     } else {
994         appData.zippyTalk = appData.zippyPlay = FALSE;
995     }
996
997     /* [AS] Initialize pv info list [HGM] and game state */
998     {
999         int i, j;
1000
1001         for( i=0; i<=framePtr; i++ ) {
1002             pvInfoList[i].depth = -1;
1003             boards[i][EP_STATUS] = EP_NONE;
1004             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1005         }
1006     }
1007
1008     InitTimeControls();
1009
1010     /* [AS] Adjudication threshold */
1011     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1012
1013     InitEngine(&first, 0);
1014     InitEngine(&second, 1);
1015     CommonEngineInit();
1016
1017     if (appData.icsActive) {
1018         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1019     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1020         appData.clockMode = FALSE;
1021         first.sendTime = second.sendTime = 0;
1022     }
1023
1024 #if ZIPPY
1025     /* Override some settings from environment variables, for backward
1026        compatibility.  Unfortunately it's not feasible to have the env
1027        vars just set defaults, at least in xboard.  Ugh.
1028     */
1029     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1030       ZippyInit();
1031     }
1032 #endif
1033
1034     if (!appData.icsActive) {
1035       char buf[MSG_SIZ];
1036       int len;
1037
1038       /* Check for variants that are supported only in ICS mode,
1039          or not at all.  Some that are accepted here nevertheless
1040          have bugs; see comments below.
1041       */
1042       VariantClass variant = StringToVariant(appData.variant);
1043       switch (variant) {
1044       case VariantBughouse:     /* need four players and two boards */
1045       case VariantKriegspiel:   /* need to hide pieces and move details */
1046         /* case VariantFischeRandom: (Fabien: moved below) */
1047         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1048         if( (len > MSG_SIZ) && appData.debugMode )
1049           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1050
1051         DisplayFatalError(buf, 0, 2);
1052         return;
1053
1054       case VariantUnknown:
1055       case VariantLoadable:
1056       case Variant29:
1057       case Variant30:
1058       case Variant31:
1059       case Variant32:
1060       case Variant33:
1061       case Variant34:
1062       case Variant35:
1063       case Variant36:
1064       default:
1065         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1066         if( (len > MSG_SIZ) && appData.debugMode )
1067           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1068
1069         DisplayFatalError(buf, 0, 2);
1070         return;
1071
1072       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1073       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1074       case VariantGothic:     /* [HGM] should work */
1075       case VariantCapablanca: /* [HGM] should work */
1076       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1077       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1078       case VariantKnightmate: /* [HGM] should work */
1079       case VariantCylinder:   /* [HGM] untested */
1080       case VariantFalcon:     /* [HGM] untested */
1081       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1082                                  offboard interposition not understood */
1083       case VariantNormal:     /* definitely works! */
1084       case VariantWildCastle: /* pieces not automatically shuffled */
1085       case VariantNoCastle:   /* pieces not automatically shuffled */
1086       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1087       case VariantLosers:     /* should work except for win condition,
1088                                  and doesn't know captures are mandatory */
1089       case VariantSuicide:    /* should work except for win condition,
1090                                  and doesn't know captures are mandatory */
1091       case VariantGiveaway:   /* should work except for win condition,
1092                                  and doesn't know captures are mandatory */
1093       case VariantTwoKings:   /* should work */
1094       case VariantAtomic:     /* should work except for win condition */
1095       case Variant3Check:     /* should work except for win condition */
1096       case VariantShatranj:   /* should work except for all win conditions */
1097       case VariantMakruk:     /* should work except for daw countdown */
1098       case VariantBerolina:   /* might work if TestLegality is off */
1099       case VariantCapaRandom: /* should work */
1100       case VariantJanus:      /* should work */
1101       case VariantSuper:      /* experimental */
1102       case VariantGreat:      /* experimental, requires legality testing to be off */
1103       case VariantSChess:     /* S-Chess, should work */
1104       case VariantSpartan:    /* should work */
1105         break;
1106       }
1107     }
1108
1109 }
1110
1111 int NextIntegerFromString( char ** str, long * value )
1112 {
1113     int result = -1;
1114     char * s = *str;
1115
1116     while( *s == ' ' || *s == '\t' ) {
1117         s++;
1118     }
1119
1120     *value = 0;
1121
1122     if( *s >= '0' && *s <= '9' ) {
1123         while( *s >= '0' && *s <= '9' ) {
1124             *value = *value * 10 + (*s - '0');
1125             s++;
1126         }
1127
1128         result = 0;
1129     }
1130
1131     *str = s;
1132
1133     return result;
1134 }
1135
1136 int NextTimeControlFromString( char ** str, long * value )
1137 {
1138     long temp;
1139     int result = NextIntegerFromString( str, &temp );
1140
1141     if( result == 0 ) {
1142         *value = temp * 60; /* Minutes */
1143         if( **str == ':' ) {
1144             (*str)++;
1145             result = NextIntegerFromString( str, &temp );
1146             *value += temp; /* Seconds */
1147         }
1148     }
1149
1150     return result;
1151 }
1152
1153 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1154 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1155     int result = -1, type = 0; long temp, temp2;
1156
1157     if(**str != ':') return -1; // old params remain in force!
1158     (*str)++;
1159     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1160     if( NextIntegerFromString( str, &temp ) ) return -1;
1161     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1162
1163     if(**str != '/') {
1164         /* time only: incremental or sudden-death time control */
1165         if(**str == '+') { /* increment follows; read it */
1166             (*str)++;
1167             if(**str == '!') type = *(*str)++; // Bronstein TC
1168             if(result = NextIntegerFromString( str, &temp2)) return -1;
1169             *inc = temp2 * 1000;
1170             if(**str == '.') { // read fraction of increment
1171                 char *start = ++(*str);
1172                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1173                 temp2 *= 1000;
1174                 while(start++ < *str) temp2 /= 10;
1175                 *inc += temp2;
1176             }
1177         } else *inc = 0;
1178         *moves = 0; *tc = temp * 1000; *incType = type;
1179         return 0;
1180     }
1181
1182     (*str)++; /* classical time control */
1183     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1184
1185     if(result == 0) {
1186         *moves = temp;
1187         *tc    = temp2 * 1000;
1188         *inc   = 0;
1189         *incType = type;
1190     }
1191     return result;
1192 }
1193
1194 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1195 {   /* [HGM] get time to add from the multi-session time-control string */
1196     int incType, moves=1; /* kludge to force reading of first session */
1197     long time, increment;
1198     char *s = tcString;
1199
1200     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1201     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1202     do {
1203         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1204         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1205         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1206         if(movenr == -1) return time;    /* last move before new session     */
1207         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1208         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1209         if(!moves) return increment;     /* current session is incremental   */
1210         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1211     } while(movenr >= -1);               /* try again for next session       */
1212
1213     return 0; // no new time quota on this move
1214 }
1215
1216 int
1217 ParseTimeControl(tc, ti, mps)
1218      char *tc;
1219      float ti;
1220      int mps;
1221 {
1222   long tc1;
1223   long tc2;
1224   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1225   int min, sec=0;
1226
1227   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1228   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1229       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1230   if(ti > 0) {
1231
1232     if(mps)
1233       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1234     else 
1235       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1236   } else {
1237     if(mps)
1238       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1239     else 
1240       snprintf(buf, MSG_SIZ, ":%s", mytc);
1241   }
1242   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1243   
1244   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1245     return FALSE;
1246   }
1247
1248   if( *tc == '/' ) {
1249     /* Parse second time control */
1250     tc++;
1251
1252     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1253       return FALSE;
1254     }
1255
1256     if( tc2 == 0 ) {
1257       return FALSE;
1258     }
1259
1260     timeControl_2 = tc2 * 1000;
1261   }
1262   else {
1263     timeControl_2 = 0;
1264   }
1265
1266   if( tc1 == 0 ) {
1267     return FALSE;
1268   }
1269
1270   timeControl = tc1 * 1000;
1271
1272   if (ti >= 0) {
1273     timeIncrement = ti * 1000;  /* convert to ms */
1274     movesPerSession = 0;
1275   } else {
1276     timeIncrement = 0;
1277     movesPerSession = mps;
1278   }
1279   return TRUE;
1280 }
1281
1282 void
1283 InitBackEnd2()
1284 {
1285     if (appData.debugMode) {
1286         fprintf(debugFP, "%s\n", programVersion);
1287     }
1288
1289     set_cont_sequence(appData.wrapContSeq);
1290     if (appData.matchGames > 0) {
1291         appData.matchMode = TRUE;
1292     } else if (appData.matchMode) {
1293         appData.matchGames = 1;
1294     }
1295     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1296         appData.matchGames = appData.sameColorGames;
1297     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1298         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1299         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1300     }
1301     Reset(TRUE, FALSE);
1302     if (appData.noChessProgram || first.protocolVersion == 1) {
1303       InitBackEnd3();
1304     } else {
1305       /* kludge: allow timeout for initial "feature" commands */
1306       FreezeUI();
1307       DisplayMessage("", _("Starting chess program"));
1308       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1309     }
1310 }
1311
1312 int
1313 CalculateIndex(int index, int gameNr)
1314 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1315     int res;
1316     if(index > 0) return index; // fixed nmber
1317     if(index == 0) return 1;
1318     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1319     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1320     return res;
1321 }
1322
1323 int
1324 LoadGameOrPosition(int gameNr)
1325 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1326     if (*appData.loadGameFile != NULLCHAR) {
1327         if (!LoadGameFromFile(appData.loadGameFile,
1328                 CalculateIndex(appData.loadGameIndex, gameNr),
1329                               appData.loadGameFile, FALSE)) {
1330             DisplayFatalError(_("Bad game file"), 0, 1);
1331             return 0;
1332         }
1333     } else if (*appData.loadPositionFile != NULLCHAR) {
1334         if (!LoadPositionFromFile(appData.loadPositionFile,
1335                 CalculateIndex(appData.loadPositionIndex, gameNr),
1336                                   appData.loadPositionFile)) {
1337             DisplayFatalError(_("Bad position file"), 0, 1);
1338             return 0;
1339         }
1340     }
1341     return 1;
1342 }
1343
1344 void
1345 ReserveGame(int gameNr, char resChar)
1346 {
1347     FILE *tf = fopen(appData.tourneyFile, "r+");
1348     char *p, *q, c, buf[MSG_SIZ];
1349     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1350     safeStrCpy(buf, lastMsg, MSG_SIZ);
1351     DisplayMessage(_("Pick new game"), "");
1352     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1353     ParseArgsFromFile(tf);
1354     p = q = appData.results;
1355     if(appData.debugMode) {
1356       char *r = appData.participants;
1357       fprintf(debugFP, "results = '%s'\n", p);
1358       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1359       fprintf(debugFP, "\n");
1360     }
1361     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1362     nextGame = q - p;
1363     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1364     safeStrCpy(q, p, strlen(p) + 2);
1365     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1366     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1367     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1368         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1369         q[nextGame] = '*';
1370     }
1371     fseek(tf, -(strlen(p)+4), SEEK_END);
1372     c = fgetc(tf);
1373     if(c != '"') // depending on DOS or Unix line endings we can be one off
1374          fseek(tf, -(strlen(p)+2), SEEK_END);
1375     else fseek(tf, -(strlen(p)+3), SEEK_END);
1376     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1377     DisplayMessage(buf, "");
1378     free(p); appData.results = q;
1379     if(nextGame <= appData.matchGames && resChar != ' ' &&
1380        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1381         UnloadEngine(&first);  // next game belongs to other pairing;
1382         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1383     }
1384 }
1385
1386 void
1387 MatchEvent(int mode)
1388 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1389         int dummy;
1390         if(matchMode) { // already in match mode: switch it off
1391             abortMatch = TRUE;
1392             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1393             ModeHighlight(); // kludgey way to remove checkmark...
1394             return;
1395         }
1396 //      if(gameMode != BeginningOfGame) {
1397 //          DisplayError(_("You can only start a match from the initial position."), 0);
1398 //          return;
1399 //      }
1400         abortMatch = FALSE;
1401         appData.matchGames = appData.defaultMatchGames;
1402         /* Set up machine vs. machine match */
1403         nextGame = 0;
1404         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1405         if(appData.tourneyFile[0]) {
1406             ReserveGame(-1, 0);
1407             if(nextGame > appData.matchGames) {
1408                 char buf[MSG_SIZ];
1409                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1410                 DisplayError(buf, 0);
1411                 appData.tourneyFile[0] = 0;
1412                 return;
1413             }
1414         } else
1415         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1416             DisplayFatalError(_("Can't have a match with no chess programs"),
1417                               0, 2);
1418             return;
1419         }
1420         matchMode = mode;
1421         matchGame = roundNr = 1;
1422         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1423         NextMatchGame();
1424 }
1425
1426 void
1427 InitBackEnd3 P((void))
1428 {
1429     GameMode initialMode;
1430     char buf[MSG_SIZ];
1431     int err, len;
1432
1433     InitChessProgram(&first, startedFromSetupPosition);
1434
1435     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1436         free(programVersion);
1437         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1438         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1439     }
1440
1441     if (appData.icsActive) {
1442 #ifdef WIN32
1443         /* [DM] Make a console window if needed [HGM] merged ifs */
1444         ConsoleCreate();
1445 #endif
1446         err = establish();
1447         if (err != 0)
1448           {
1449             if (*appData.icsCommPort != NULLCHAR)
1450               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1451                              appData.icsCommPort);
1452             else
1453               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1454                         appData.icsHost, appData.icsPort);
1455
1456             if( (len > MSG_SIZ) && appData.debugMode )
1457               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1458
1459             DisplayFatalError(buf, err, 1);
1460             return;
1461         }
1462         SetICSMode();
1463         telnetISR =
1464           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1465         fromUserISR =
1466           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1467         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1468             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1469     } else if (appData.noChessProgram) {
1470         SetNCPMode();
1471     } else {
1472         SetGNUMode();
1473     }
1474
1475     if (*appData.cmailGameName != NULLCHAR) {
1476         SetCmailMode();
1477         OpenLoopback(&cmailPR);
1478         cmailISR =
1479           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1480     }
1481
1482     ThawUI();
1483     DisplayMessage("", "");
1484     if (StrCaseCmp(appData.initialMode, "") == 0) {
1485       initialMode = BeginningOfGame;
1486       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1487         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1488         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1489         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1490         ModeHighlight();
1491       }
1492     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1493       initialMode = TwoMachinesPlay;
1494     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1495       initialMode = AnalyzeFile;
1496     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1497       initialMode = AnalyzeMode;
1498     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1499       initialMode = MachinePlaysWhite;
1500     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1501       initialMode = MachinePlaysBlack;
1502     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1503       initialMode = EditGame;
1504     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1505       initialMode = EditPosition;
1506     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1507       initialMode = Training;
1508     } else {
1509       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1510       if( (len > MSG_SIZ) && appData.debugMode )
1511         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1512
1513       DisplayFatalError(buf, 0, 2);
1514       return;
1515     }
1516
1517     if (appData.matchMode) {
1518         if(appData.tourneyFile[0]) { // start tourney from command line
1519             FILE *f;
1520             if(f = fopen(appData.tourneyFile, "r")) {
1521                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1522                 fclose(f);
1523             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1524         }
1525         MatchEvent(TRUE);
1526     } else if (*appData.cmailGameName != NULLCHAR) {
1527         /* Set up cmail mode */
1528         ReloadCmailMsgEvent(TRUE);
1529     } else {
1530         /* Set up other modes */
1531         if (initialMode == AnalyzeFile) {
1532           if (*appData.loadGameFile == NULLCHAR) {
1533             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1534             return;
1535           }
1536         }
1537         if (*appData.loadGameFile != NULLCHAR) {
1538             (void) LoadGameFromFile(appData.loadGameFile,
1539                                     appData.loadGameIndex,
1540                                     appData.loadGameFile, TRUE);
1541         } else if (*appData.loadPositionFile != NULLCHAR) {
1542             (void) LoadPositionFromFile(appData.loadPositionFile,
1543                                         appData.loadPositionIndex,
1544                                         appData.loadPositionFile);
1545             /* [HGM] try to make self-starting even after FEN load */
1546             /* to allow automatic setup of fairy variants with wtm */
1547             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1548                 gameMode = BeginningOfGame;
1549                 setboardSpoiledMachineBlack = 1;
1550             }
1551             /* [HGM] loadPos: make that every new game uses the setup */
1552             /* from file as long as we do not switch variant          */
1553             if(!blackPlaysFirst) {
1554                 startedFromPositionFile = TRUE;
1555                 CopyBoard(filePosition, boards[0]);
1556             }
1557         }
1558         if (initialMode == AnalyzeMode) {
1559           if (appData.noChessProgram) {
1560             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1561             return;
1562           }
1563           if (appData.icsActive) {
1564             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1565             return;
1566           }
1567           AnalyzeModeEvent();
1568         } else if (initialMode == AnalyzeFile) {
1569           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1570           ShowThinkingEvent();
1571           AnalyzeFileEvent();
1572           AnalysisPeriodicEvent(1);
1573         } else if (initialMode == MachinePlaysWhite) {
1574           if (appData.noChessProgram) {
1575             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1576                               0, 2);
1577             return;
1578           }
1579           if (appData.icsActive) {
1580             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1581                               0, 2);
1582             return;
1583           }
1584           MachineWhiteEvent();
1585         } else if (initialMode == MachinePlaysBlack) {
1586           if (appData.noChessProgram) {
1587             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1588                               0, 2);
1589             return;
1590           }
1591           if (appData.icsActive) {
1592             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1593                               0, 2);
1594             return;
1595           }
1596           MachineBlackEvent();
1597         } else if (initialMode == TwoMachinesPlay) {
1598           if (appData.noChessProgram) {
1599             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1600                               0, 2);
1601             return;
1602           }
1603           if (appData.icsActive) {
1604             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1605                               0, 2);
1606             return;
1607           }
1608           TwoMachinesEvent();
1609         } else if (initialMode == EditGame) {
1610           EditGameEvent();
1611         } else if (initialMode == EditPosition) {
1612           EditPositionEvent();
1613         } else if (initialMode == Training) {
1614           if (*appData.loadGameFile == NULLCHAR) {
1615             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1616             return;
1617           }
1618           TrainingEvent();
1619         }
1620     }
1621 }
1622
1623 /*
1624  * Establish will establish a contact to a remote host.port.
1625  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1626  *  used to talk to the host.
1627  * Returns 0 if okay, error code if not.
1628  */
1629 int
1630 establish()
1631 {
1632     char buf[MSG_SIZ];
1633
1634     if (*appData.icsCommPort != NULLCHAR) {
1635         /* Talk to the host through a serial comm port */
1636         return OpenCommPort(appData.icsCommPort, &icsPR);
1637
1638     } else if (*appData.gateway != NULLCHAR) {
1639         if (*appData.remoteShell == NULLCHAR) {
1640             /* Use the rcmd protocol to run telnet program on a gateway host */
1641             snprintf(buf, sizeof(buf), "%s %s %s",
1642                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1643             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1644
1645         } else {
1646             /* Use the rsh program to run telnet program on a gateway host */
1647             if (*appData.remoteUser == NULLCHAR) {
1648                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1649                         appData.gateway, appData.telnetProgram,
1650                         appData.icsHost, appData.icsPort);
1651             } else {
1652                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1653                         appData.remoteShell, appData.gateway,
1654                         appData.remoteUser, appData.telnetProgram,
1655                         appData.icsHost, appData.icsPort);
1656             }
1657             return StartChildProcess(buf, "", &icsPR);
1658
1659         }
1660     } else if (appData.useTelnet) {
1661         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1662
1663     } else {
1664         /* TCP socket interface differs somewhat between
1665            Unix and NT; handle details in the front end.
1666            */
1667         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1668     }
1669 }
1670
1671 void EscapeExpand(char *p, char *q)
1672 {       // [HGM] initstring: routine to shape up string arguments
1673         while(*p++ = *q++) if(p[-1] == '\\')
1674             switch(*q++) {
1675                 case 'n': p[-1] = '\n'; break;
1676                 case 'r': p[-1] = '\r'; break;
1677                 case 't': p[-1] = '\t'; break;
1678                 case '\\': p[-1] = '\\'; break;
1679                 case 0: *p = 0; return;
1680                 default: p[-1] = q[-1]; break;
1681             }
1682 }
1683
1684 void
1685 show_bytes(fp, buf, count)
1686      FILE *fp;
1687      char *buf;
1688      int count;
1689 {
1690     while (count--) {
1691         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1692             fprintf(fp, "\\%03o", *buf & 0xff);
1693         } else {
1694             putc(*buf, fp);
1695         }
1696         buf++;
1697     }
1698     fflush(fp);
1699 }
1700
1701 /* Returns an errno value */
1702 int
1703 OutputMaybeTelnet(pr, message, count, outError)
1704      ProcRef pr;
1705      char *message;
1706      int count;
1707      int *outError;
1708 {
1709     char buf[8192], *p, *q, *buflim;
1710     int left, newcount, outcount;
1711
1712     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1713         *appData.gateway != NULLCHAR) {
1714         if (appData.debugMode) {
1715             fprintf(debugFP, ">ICS: ");
1716             show_bytes(debugFP, message, count);
1717             fprintf(debugFP, "\n");
1718         }
1719         return OutputToProcess(pr, message, count, outError);
1720     }
1721
1722     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1723     p = message;
1724     q = buf;
1725     left = count;
1726     newcount = 0;
1727     while (left) {
1728         if (q >= buflim) {
1729             if (appData.debugMode) {
1730                 fprintf(debugFP, ">ICS: ");
1731                 show_bytes(debugFP, buf, newcount);
1732                 fprintf(debugFP, "\n");
1733             }
1734             outcount = OutputToProcess(pr, buf, newcount, outError);
1735             if (outcount < newcount) return -1; /* to be sure */
1736             q = buf;
1737             newcount = 0;
1738         }
1739         if (*p == '\n') {
1740             *q++ = '\r';
1741             newcount++;
1742         } else if (((unsigned char) *p) == TN_IAC) {
1743             *q++ = (char) TN_IAC;
1744             newcount ++;
1745         }
1746         *q++ = *p++;
1747         newcount++;
1748         left--;
1749     }
1750     if (appData.debugMode) {
1751         fprintf(debugFP, ">ICS: ");
1752         show_bytes(debugFP, buf, newcount);
1753         fprintf(debugFP, "\n");
1754     }
1755     outcount = OutputToProcess(pr, buf, newcount, outError);
1756     if (outcount < newcount) return -1; /* to be sure */
1757     return count;
1758 }
1759
1760 void
1761 read_from_player(isr, closure, message, count, error)
1762      InputSourceRef isr;
1763      VOIDSTAR closure;
1764      char *message;
1765      int count;
1766      int error;
1767 {
1768     int outError, outCount;
1769     static int gotEof = 0;
1770
1771     /* Pass data read from player on to ICS */
1772     if (count > 0) {
1773         gotEof = 0;
1774         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1775         if (outCount < count) {
1776             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1777         }
1778     } else if (count < 0) {
1779         RemoveInputSource(isr);
1780         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1781     } else if (gotEof++ > 0) {
1782         RemoveInputSource(isr);
1783         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1784     }
1785 }
1786
1787 void
1788 KeepAlive()
1789 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1790     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1791     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1792     SendToICS("date\n");
1793     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1794 }
1795
1796 /* added routine for printf style output to ics */
1797 void ics_printf(char *format, ...)
1798 {
1799     char buffer[MSG_SIZ];
1800     va_list args;
1801
1802     va_start(args, format);
1803     vsnprintf(buffer, sizeof(buffer), format, args);
1804     buffer[sizeof(buffer)-1] = '\0';
1805     SendToICS(buffer);
1806     va_end(args);
1807 }
1808
1809 void
1810 SendToICS(s)
1811      char *s;
1812 {
1813     int count, outCount, outError;
1814
1815     if (icsPR == NULL) return;
1816
1817     count = strlen(s);
1818     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1819     if (outCount < count) {
1820         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1821     }
1822 }
1823
1824 /* This is used for sending logon scripts to the ICS. Sending
1825    without a delay causes problems when using timestamp on ICC
1826    (at least on my machine). */
1827 void
1828 SendToICSDelayed(s,msdelay)
1829      char *s;
1830      long msdelay;
1831 {
1832     int count, outCount, outError;
1833
1834     if (icsPR == NULL) return;
1835
1836     count = strlen(s);
1837     if (appData.debugMode) {
1838         fprintf(debugFP, ">ICS: ");
1839         show_bytes(debugFP, s, count);
1840         fprintf(debugFP, "\n");
1841     }
1842     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1843                                       msdelay);
1844     if (outCount < count) {
1845         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1846     }
1847 }
1848
1849
1850 /* Remove all highlighting escape sequences in s
1851    Also deletes any suffix starting with '('
1852    */
1853 char *
1854 StripHighlightAndTitle(s)
1855      char *s;
1856 {
1857     static char retbuf[MSG_SIZ];
1858     char *p = retbuf;
1859
1860     while (*s != NULLCHAR) {
1861         while (*s == '\033') {
1862             while (*s != NULLCHAR && !isalpha(*s)) s++;
1863             if (*s != NULLCHAR) s++;
1864         }
1865         while (*s != NULLCHAR && *s != '\033') {
1866             if (*s == '(' || *s == '[') {
1867                 *p = NULLCHAR;
1868                 return retbuf;
1869             }
1870             *p++ = *s++;
1871         }
1872     }
1873     *p = NULLCHAR;
1874     return retbuf;
1875 }
1876
1877 /* Remove all highlighting escape sequences in s */
1878 char *
1879 StripHighlight(s)
1880      char *s;
1881 {
1882     static char retbuf[MSG_SIZ];
1883     char *p = retbuf;
1884
1885     while (*s != NULLCHAR) {
1886         while (*s == '\033') {
1887             while (*s != NULLCHAR && !isalpha(*s)) s++;
1888             if (*s != NULLCHAR) s++;
1889         }
1890         while (*s != NULLCHAR && *s != '\033') {
1891             *p++ = *s++;
1892         }
1893     }
1894     *p = NULLCHAR;
1895     return retbuf;
1896 }
1897
1898 char *variantNames[] = VARIANT_NAMES;
1899 char *
1900 VariantName(v)
1901      VariantClass v;
1902 {
1903     return variantNames[v];
1904 }
1905
1906
1907 /* Identify a variant from the strings the chess servers use or the
1908    PGN Variant tag names we use. */
1909 VariantClass
1910 StringToVariant(e)
1911      char *e;
1912 {
1913     char *p;
1914     int wnum = -1;
1915     VariantClass v = VariantNormal;
1916     int i, found = FALSE;
1917     char buf[MSG_SIZ];
1918     int len;
1919
1920     if (!e) return v;
1921
1922     /* [HGM] skip over optional board-size prefixes */
1923     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1924         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1925         while( *e++ != '_');
1926     }
1927
1928     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1929         v = VariantNormal;
1930         found = TRUE;
1931     } else
1932     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1933       if (StrCaseStr(e, variantNames[i])) {
1934         v = (VariantClass) i;
1935         found = TRUE;
1936         break;
1937       }
1938     }
1939
1940     if (!found) {
1941       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1942           || StrCaseStr(e, "wild/fr")
1943           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1944         v = VariantFischeRandom;
1945       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1946                  (i = 1, p = StrCaseStr(e, "w"))) {
1947         p += i;
1948         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1949         if (isdigit(*p)) {
1950           wnum = atoi(p);
1951         } else {
1952           wnum = -1;
1953         }
1954         switch (wnum) {
1955         case 0: /* FICS only, actually */
1956         case 1:
1957           /* Castling legal even if K starts on d-file */
1958           v = VariantWildCastle;
1959           break;
1960         case 2:
1961         case 3:
1962         case 4:
1963           /* Castling illegal even if K & R happen to start in
1964              normal positions. */
1965           v = VariantNoCastle;
1966           break;
1967         case 5:
1968         case 7:
1969         case 8:
1970         case 10:
1971         case 11:
1972         case 12:
1973         case 13:
1974         case 14:
1975         case 15:
1976         case 18:
1977         case 19:
1978           /* Castling legal iff K & R start in normal positions */
1979           v = VariantNormal;
1980           break;
1981         case 6:
1982         case 20:
1983         case 21:
1984           /* Special wilds for position setup; unclear what to do here */
1985           v = VariantLoadable;
1986           break;
1987         case 9:
1988           /* Bizarre ICC game */
1989           v = VariantTwoKings;
1990           break;
1991         case 16:
1992           v = VariantKriegspiel;
1993           break;
1994         case 17:
1995           v = VariantLosers;
1996           break;
1997         case 22:
1998           v = VariantFischeRandom;
1999           break;
2000         case 23:
2001           v = VariantCrazyhouse;
2002           break;
2003         case 24:
2004           v = VariantBughouse;
2005           break;
2006         case 25:
2007           v = Variant3Check;
2008           break;
2009         case 26:
2010           /* Not quite the same as FICS suicide! */
2011           v = VariantGiveaway;
2012           break;
2013         case 27:
2014           v = VariantAtomic;
2015           break;
2016         case 28:
2017           v = VariantShatranj;
2018           break;
2019
2020         /* Temporary names for future ICC types.  The name *will* change in
2021            the next xboard/WinBoard release after ICC defines it. */
2022         case 29:
2023           v = Variant29;
2024           break;
2025         case 30:
2026           v = Variant30;
2027           break;
2028         case 31:
2029           v = Variant31;
2030           break;
2031         case 32:
2032           v = Variant32;
2033           break;
2034         case 33:
2035           v = Variant33;
2036           break;
2037         case 34:
2038           v = Variant34;
2039           break;
2040         case 35:
2041           v = Variant35;
2042           break;
2043         case 36:
2044           v = Variant36;
2045           break;
2046         case 37:
2047           v = VariantShogi;
2048           break;
2049         case 38:
2050           v = VariantXiangqi;
2051           break;
2052         case 39:
2053           v = VariantCourier;
2054           break;
2055         case 40:
2056           v = VariantGothic;
2057           break;
2058         case 41:
2059           v = VariantCapablanca;
2060           break;
2061         case 42:
2062           v = VariantKnightmate;
2063           break;
2064         case 43:
2065           v = VariantFairy;
2066           break;
2067         case 44:
2068           v = VariantCylinder;
2069           break;
2070         case 45:
2071           v = VariantFalcon;
2072           break;
2073         case 46:
2074           v = VariantCapaRandom;
2075           break;
2076         case 47:
2077           v = VariantBerolina;
2078           break;
2079         case 48:
2080           v = VariantJanus;
2081           break;
2082         case 49:
2083           v = VariantSuper;
2084           break;
2085         case 50:
2086           v = VariantGreat;
2087           break;
2088         case -1:
2089           /* Found "wild" or "w" in the string but no number;
2090              must assume it's normal chess. */
2091           v = VariantNormal;
2092           break;
2093         default:
2094           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2095           if( (len > MSG_SIZ) && appData.debugMode )
2096             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2097
2098           DisplayError(buf, 0);
2099           v = VariantUnknown;
2100           break;
2101         }
2102       }
2103     }
2104     if (appData.debugMode) {
2105       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2106               e, wnum, VariantName(v));
2107     }
2108     return v;
2109 }
2110
2111 static int leftover_start = 0, leftover_len = 0;
2112 char star_match[STAR_MATCH_N][MSG_SIZ];
2113
2114 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2115    advance *index beyond it, and set leftover_start to the new value of
2116    *index; else return FALSE.  If pattern contains the character '*', it
2117    matches any sequence of characters not containing '\r', '\n', or the
2118    character following the '*' (if any), and the matched sequence(s) are
2119    copied into star_match.
2120    */
2121 int
2122 looking_at(buf, index, pattern)
2123      char *buf;
2124      int *index;
2125      char *pattern;
2126 {
2127     char *bufp = &buf[*index], *patternp = pattern;
2128     int star_count = 0;
2129     char *matchp = star_match[0];
2130
2131     for (;;) {
2132         if (*patternp == NULLCHAR) {
2133             *index = leftover_start = bufp - buf;
2134             *matchp = NULLCHAR;
2135             return TRUE;
2136         }
2137         if (*bufp == NULLCHAR) return FALSE;
2138         if (*patternp == '*') {
2139             if (*bufp == *(patternp + 1)) {
2140                 *matchp = NULLCHAR;
2141                 matchp = star_match[++star_count];
2142                 patternp += 2;
2143                 bufp++;
2144                 continue;
2145             } else if (*bufp == '\n' || *bufp == '\r') {
2146                 patternp++;
2147                 if (*patternp == NULLCHAR)
2148                   continue;
2149                 else
2150                   return FALSE;
2151             } else {
2152                 *matchp++ = *bufp++;
2153                 continue;
2154             }
2155         }
2156         if (*patternp != *bufp) return FALSE;
2157         patternp++;
2158         bufp++;
2159     }
2160 }
2161
2162 void
2163 SendToPlayer(data, length)
2164      char *data;
2165      int length;
2166 {
2167     int error, outCount;
2168     outCount = OutputToProcess(NoProc, data, length, &error);
2169     if (outCount < length) {
2170         DisplayFatalError(_("Error writing to display"), error, 1);
2171     }
2172 }
2173
2174 void
2175 PackHolding(packed, holding)
2176      char packed[];
2177      char *holding;
2178 {
2179     char *p = holding;
2180     char *q = packed;
2181     int runlength = 0;
2182     int curr = 9999;
2183     do {
2184         if (*p == curr) {
2185             runlength++;
2186         } else {
2187             switch (runlength) {
2188               case 0:
2189                 break;
2190               case 1:
2191                 *q++ = curr;
2192                 break;
2193               case 2:
2194                 *q++ = curr;
2195                 *q++ = curr;
2196                 break;
2197               default:
2198                 sprintf(q, "%d", runlength);
2199                 while (*q) q++;
2200                 *q++ = curr;
2201                 break;
2202             }
2203             runlength = 1;
2204             curr = *p;
2205         }
2206     } while (*p++);
2207     *q = NULLCHAR;
2208 }
2209
2210 /* Telnet protocol requests from the front end */
2211 void
2212 TelnetRequest(ddww, option)
2213      unsigned char ddww, option;
2214 {
2215     unsigned char msg[3];
2216     int outCount, outError;
2217
2218     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2219
2220     if (appData.debugMode) {
2221         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2222         switch (ddww) {
2223           case TN_DO:
2224             ddwwStr = "DO";
2225             break;
2226           case TN_DONT:
2227             ddwwStr = "DONT";
2228             break;
2229           case TN_WILL:
2230             ddwwStr = "WILL";
2231             break;
2232           case TN_WONT:
2233             ddwwStr = "WONT";
2234             break;
2235           default:
2236             ddwwStr = buf1;
2237             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2238             break;
2239         }
2240         switch (option) {
2241           case TN_ECHO:
2242             optionStr = "ECHO";
2243             break;
2244           default:
2245             optionStr = buf2;
2246             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2247             break;
2248         }
2249         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2250     }
2251     msg[0] = TN_IAC;
2252     msg[1] = ddww;
2253     msg[2] = option;
2254     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2255     if (outCount < 3) {
2256         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2257     }
2258 }
2259
2260 void
2261 DoEcho()
2262 {
2263     if (!appData.icsActive) return;
2264     TelnetRequest(TN_DO, TN_ECHO);
2265 }
2266
2267 void
2268 DontEcho()
2269 {
2270     if (!appData.icsActive) return;
2271     TelnetRequest(TN_DONT, TN_ECHO);
2272 }
2273
2274 void
2275 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2276 {
2277     /* put the holdings sent to us by the server on the board holdings area */
2278     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2279     char p;
2280     ChessSquare piece;
2281
2282     if(gameInfo.holdingsWidth < 2)  return;
2283     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2284         return; // prevent overwriting by pre-board holdings
2285
2286     if( (int)lowestPiece >= BlackPawn ) {
2287         holdingsColumn = 0;
2288         countsColumn = 1;
2289         holdingsStartRow = BOARD_HEIGHT-1;
2290         direction = -1;
2291     } else {
2292         holdingsColumn = BOARD_WIDTH-1;
2293         countsColumn = BOARD_WIDTH-2;
2294         holdingsStartRow = 0;
2295         direction = 1;
2296     }
2297
2298     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2299         board[i][holdingsColumn] = EmptySquare;
2300         board[i][countsColumn]   = (ChessSquare) 0;
2301     }
2302     while( (p=*holdings++) != NULLCHAR ) {
2303         piece = CharToPiece( ToUpper(p) );
2304         if(piece == EmptySquare) continue;
2305         /*j = (int) piece - (int) WhitePawn;*/
2306         j = PieceToNumber(piece);
2307         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2308         if(j < 0) continue;               /* should not happen */
2309         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2310         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2311         board[holdingsStartRow+j*direction][countsColumn]++;
2312     }
2313 }
2314
2315
2316 void
2317 VariantSwitch(Board board, VariantClass newVariant)
2318 {
2319    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2320    static Board oldBoard;
2321
2322    startedFromPositionFile = FALSE;
2323    if(gameInfo.variant == newVariant) return;
2324
2325    /* [HGM] This routine is called each time an assignment is made to
2326     * gameInfo.variant during a game, to make sure the board sizes
2327     * are set to match the new variant. If that means adding or deleting
2328     * holdings, we shift the playing board accordingly
2329     * This kludge is needed because in ICS observe mode, we get boards
2330     * of an ongoing game without knowing the variant, and learn about the
2331     * latter only later. This can be because of the move list we requested,
2332     * in which case the game history is refilled from the beginning anyway,
2333     * but also when receiving holdings of a crazyhouse game. In the latter
2334     * case we want to add those holdings to the already received position.
2335     */
2336
2337
2338    if (appData.debugMode) {
2339      fprintf(debugFP, "Switch board from %s to %s\n",
2340              VariantName(gameInfo.variant), VariantName(newVariant));
2341      setbuf(debugFP, NULL);
2342    }
2343    shuffleOpenings = 0;       /* [HGM] shuffle */
2344    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2345    switch(newVariant)
2346      {
2347      case VariantShogi:
2348        newWidth = 9;  newHeight = 9;
2349        gameInfo.holdingsSize = 7;
2350      case VariantBughouse:
2351      case VariantCrazyhouse:
2352        newHoldingsWidth = 2; break;
2353      case VariantGreat:
2354        newWidth = 10;
2355      case VariantSuper:
2356        newHoldingsWidth = 2;
2357        gameInfo.holdingsSize = 8;
2358        break;
2359      case VariantGothic:
2360      case VariantCapablanca:
2361      case VariantCapaRandom:
2362        newWidth = 10;
2363      default:
2364        newHoldingsWidth = gameInfo.holdingsSize = 0;
2365      };
2366
2367    if(newWidth  != gameInfo.boardWidth  ||
2368       newHeight != gameInfo.boardHeight ||
2369       newHoldingsWidth != gameInfo.holdingsWidth ) {
2370
2371      /* shift position to new playing area, if needed */
2372      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2373        for(i=0; i<BOARD_HEIGHT; i++)
2374          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2375            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2376              board[i][j];
2377        for(i=0; i<newHeight; i++) {
2378          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2379          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2380        }
2381      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2382        for(i=0; i<BOARD_HEIGHT; i++)
2383          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2384            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2385              board[i][j];
2386      }
2387      gameInfo.boardWidth  = newWidth;
2388      gameInfo.boardHeight = newHeight;
2389      gameInfo.holdingsWidth = newHoldingsWidth;
2390      gameInfo.variant = newVariant;
2391      InitDrawingSizes(-2, 0);
2392    } else gameInfo.variant = newVariant;
2393    CopyBoard(oldBoard, board);   // remember correctly formatted board
2394      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2395    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2396 }
2397
2398 static int loggedOn = FALSE;
2399
2400 /*-- Game start info cache: --*/
2401 int gs_gamenum;
2402 char gs_kind[MSG_SIZ];
2403 static char player1Name[128] = "";
2404 static char player2Name[128] = "";
2405 static char cont_seq[] = "\n\\   ";
2406 static int player1Rating = -1;
2407 static int player2Rating = -1;
2408 /*----------------------------*/
2409
2410 ColorClass curColor = ColorNormal;
2411 int suppressKibitz = 0;
2412
2413 // [HGM] seekgraph
2414 Boolean soughtPending = FALSE;
2415 Boolean seekGraphUp;
2416 #define MAX_SEEK_ADS 200
2417 #define SQUARE 0x80
2418 char *seekAdList[MAX_SEEK_ADS];
2419 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2420 float tcList[MAX_SEEK_ADS];
2421 char colorList[MAX_SEEK_ADS];
2422 int nrOfSeekAds = 0;
2423 int minRating = 1010, maxRating = 2800;
2424 int hMargin = 10, vMargin = 20, h, w;
2425 extern int squareSize, lineGap;
2426
2427 void
2428 PlotSeekAd(int i)
2429 {
2430         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2431         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2432         if(r < minRating+100 && r >=0 ) r = minRating+100;
2433         if(r > maxRating) r = maxRating;
2434         if(tc < 1.) tc = 1.;
2435         if(tc > 95.) tc = 95.;
2436         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2437         y = ((double)r - minRating)/(maxRating - minRating)
2438             * (h-vMargin-squareSize/8-1) + vMargin;
2439         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2440         if(strstr(seekAdList[i], " u ")) color = 1;
2441         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2442            !strstr(seekAdList[i], "bullet") &&
2443            !strstr(seekAdList[i], "blitz") &&
2444            !strstr(seekAdList[i], "standard") ) color = 2;
2445         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2446         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2447 }
2448
2449 void
2450 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2451 {
2452         char buf[MSG_SIZ], *ext = "";
2453         VariantClass v = StringToVariant(type);
2454         if(strstr(type, "wild")) {
2455             ext = type + 4; // append wild number
2456             if(v == VariantFischeRandom) type = "chess960"; else
2457             if(v == VariantLoadable) type = "setup"; else
2458             type = VariantName(v);
2459         }
2460         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2461         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2462             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2463             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2464             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2465             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2466             seekNrList[nrOfSeekAds] = nr;
2467             zList[nrOfSeekAds] = 0;
2468             seekAdList[nrOfSeekAds++] = StrSave(buf);
2469             if(plot) PlotSeekAd(nrOfSeekAds-1);
2470         }
2471 }
2472
2473 void
2474 EraseSeekDot(int i)
2475 {
2476     int x = xList[i], y = yList[i], d=squareSize/4, k;
2477     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2478     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2479     // now replot every dot that overlapped
2480     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2481         int xx = xList[k], yy = yList[k];
2482         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2483             DrawSeekDot(xx, yy, colorList[k]);
2484     }
2485 }
2486
2487 void
2488 RemoveSeekAd(int nr)
2489 {
2490         int i;
2491         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2492             EraseSeekDot(i);
2493             if(seekAdList[i]) free(seekAdList[i]);
2494             seekAdList[i] = seekAdList[--nrOfSeekAds];
2495             seekNrList[i] = seekNrList[nrOfSeekAds];
2496             ratingList[i] = ratingList[nrOfSeekAds];
2497             colorList[i]  = colorList[nrOfSeekAds];
2498             tcList[i] = tcList[nrOfSeekAds];
2499             xList[i]  = xList[nrOfSeekAds];
2500             yList[i]  = yList[nrOfSeekAds];
2501             zList[i]  = zList[nrOfSeekAds];
2502             seekAdList[nrOfSeekAds] = NULL;
2503             break;
2504         }
2505 }
2506
2507 Boolean
2508 MatchSoughtLine(char *line)
2509 {
2510     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2511     int nr, base, inc, u=0; char dummy;
2512
2513     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2514        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2515        (u=1) &&
2516        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2517         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2518         // match: compact and save the line
2519         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2520         return TRUE;
2521     }
2522     return FALSE;
2523 }
2524
2525 int
2526 DrawSeekGraph()
2527 {
2528     int i;
2529     if(!seekGraphUp) return FALSE;
2530     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2531     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2532
2533     DrawSeekBackground(0, 0, w, h);
2534     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2535     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2536     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2537         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2538         yy = h-1-yy;
2539         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2540         if(i%500 == 0) {
2541             char buf[MSG_SIZ];
2542             snprintf(buf, MSG_SIZ, "%d", i);
2543             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2544         }
2545     }
2546     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2547     for(i=1; i<100; i+=(i<10?1:5)) {
2548         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2549         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2550         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2551             char buf[MSG_SIZ];
2552             snprintf(buf, MSG_SIZ, "%d", i);
2553             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2554         }
2555     }
2556     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2557     return TRUE;
2558 }
2559
2560 int SeekGraphClick(ClickType click, int x, int y, int moving)
2561 {
2562     static int lastDown = 0, displayed = 0, lastSecond;
2563     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2564         if(click == Release || moving) return FALSE;
2565         nrOfSeekAds = 0;
2566         soughtPending = TRUE;
2567         SendToICS(ics_prefix);
2568         SendToICS("sought\n"); // should this be "sought all"?
2569     } else { // issue challenge based on clicked ad
2570         int dist = 10000; int i, closest = 0, second = 0;
2571         for(i=0; i<nrOfSeekAds; i++) {
2572             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2573             if(d < dist) { dist = d; closest = i; }
2574             second += (d - zList[i] < 120); // count in-range ads
2575             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2576         }
2577         if(dist < 120) {
2578             char buf[MSG_SIZ];
2579             second = (second > 1);
2580             if(displayed != closest || second != lastSecond) {
2581                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2582                 lastSecond = second; displayed = closest;
2583             }
2584             if(click == Press) {
2585                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2586                 lastDown = closest;
2587                 return TRUE;
2588             } // on press 'hit', only show info
2589             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2590             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2591             SendToICS(ics_prefix);
2592             SendToICS(buf);
2593             return TRUE; // let incoming board of started game pop down the graph
2594         } else if(click == Release) { // release 'miss' is ignored
2595             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2596             if(moving == 2) { // right up-click
2597                 nrOfSeekAds = 0; // refresh graph
2598                 soughtPending = TRUE;
2599                 SendToICS(ics_prefix);
2600                 SendToICS("sought\n"); // should this be "sought all"?
2601             }
2602             return TRUE;
2603         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2604         // press miss or release hit 'pop down' seek graph
2605         seekGraphUp = FALSE;
2606         DrawPosition(TRUE, NULL);
2607     }
2608     return TRUE;
2609 }
2610
2611 void
2612 read_from_ics(isr, closure, data, count, error)
2613      InputSourceRef isr;
2614      VOIDSTAR closure;
2615      char *data;
2616      int count;
2617      int error;
2618 {
2619 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2620 #define STARTED_NONE 0
2621 #define STARTED_MOVES 1
2622 #define STARTED_BOARD 2
2623 #define STARTED_OBSERVE 3
2624 #define STARTED_HOLDINGS 4
2625 #define STARTED_CHATTER 5
2626 #define STARTED_COMMENT 6
2627 #define STARTED_MOVES_NOHIDE 7
2628
2629     static int started = STARTED_NONE;
2630     static char parse[20000];
2631     static int parse_pos = 0;
2632     static char buf[BUF_SIZE + 1];
2633     static int firstTime = TRUE, intfSet = FALSE;
2634     static ColorClass prevColor = ColorNormal;
2635     static int savingComment = FALSE;
2636     static int cmatch = 0; // continuation sequence match
2637     char *bp;
2638     char str[MSG_SIZ];
2639     int i, oldi;
2640     int buf_len;
2641     int next_out;
2642     int tkind;
2643     int backup;    /* [DM] For zippy color lines */
2644     char *p;
2645     char talker[MSG_SIZ]; // [HGM] chat
2646     int channel;
2647
2648     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2649
2650     if (appData.debugMode) {
2651       if (!error) {
2652         fprintf(debugFP, "<ICS: ");
2653         show_bytes(debugFP, data, count);
2654         fprintf(debugFP, "\n");
2655       }
2656     }
2657
2658     if (appData.debugMode) { int f = forwardMostMove;
2659         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2660                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2661                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2662     }
2663     if (count > 0) {
2664         /* If last read ended with a partial line that we couldn't parse,
2665            prepend it to the new read and try again. */
2666         if (leftover_len > 0) {
2667             for (i=0; i<leftover_len; i++)
2668               buf[i] = buf[leftover_start + i];
2669         }
2670
2671     /* copy new characters into the buffer */
2672     bp = buf + leftover_len;
2673     buf_len=leftover_len;
2674     for (i=0; i<count; i++)
2675     {
2676         // ignore these
2677         if (data[i] == '\r')
2678             continue;
2679
2680         // join lines split by ICS?
2681         if (!appData.noJoin)
2682         {
2683             /*
2684                 Joining just consists of finding matches against the
2685                 continuation sequence, and discarding that sequence
2686                 if found instead of copying it.  So, until a match
2687                 fails, there's nothing to do since it might be the
2688                 complete sequence, and thus, something we don't want
2689                 copied.
2690             */
2691             if (data[i] == cont_seq[cmatch])
2692             {
2693                 cmatch++;
2694                 if (cmatch == strlen(cont_seq))
2695                 {
2696                     cmatch = 0; // complete match.  just reset the counter
2697
2698                     /*
2699                         it's possible for the ICS to not include the space
2700                         at the end of the last word, making our [correct]
2701                         join operation fuse two separate words.  the server
2702                         does this when the space occurs at the width setting.
2703                     */
2704                     if (!buf_len || buf[buf_len-1] != ' ')
2705                     {
2706                         *bp++ = ' ';
2707                         buf_len++;
2708                     }
2709                 }
2710                 continue;
2711             }
2712             else if (cmatch)
2713             {
2714                 /*
2715                     match failed, so we have to copy what matched before
2716                     falling through and copying this character.  In reality,
2717                     this will only ever be just the newline character, but
2718                     it doesn't hurt to be precise.
2719                 */
2720                 strncpy(bp, cont_seq, cmatch);
2721                 bp += cmatch;
2722                 buf_len += cmatch;
2723                 cmatch = 0;
2724             }
2725         }
2726
2727         // copy this char
2728         *bp++ = data[i];
2729         buf_len++;
2730     }
2731
2732         buf[buf_len] = NULLCHAR;
2733 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2734         next_out = 0;
2735         leftover_start = 0;
2736
2737         i = 0;
2738         while (i < buf_len) {
2739             /* Deal with part of the TELNET option negotiation
2740                protocol.  We refuse to do anything beyond the
2741                defaults, except that we allow the WILL ECHO option,
2742                which ICS uses to turn off password echoing when we are
2743                directly connected to it.  We reject this option
2744                if localLineEditing mode is on (always on in xboard)
2745                and we are talking to port 23, which might be a real
2746                telnet server that will try to keep WILL ECHO on permanently.
2747              */
2748             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2749                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2750                 unsigned char option;
2751                 oldi = i;
2752                 switch ((unsigned char) buf[++i]) {
2753                   case TN_WILL:
2754                     if (appData.debugMode)
2755                       fprintf(debugFP, "\n<WILL ");
2756                     switch (option = (unsigned char) buf[++i]) {
2757                       case TN_ECHO:
2758                         if (appData.debugMode)
2759                           fprintf(debugFP, "ECHO ");
2760                         /* Reply only if this is a change, according
2761                            to the protocol rules. */
2762                         if (remoteEchoOption) break;
2763                         if (appData.localLineEditing &&
2764                             atoi(appData.icsPort) == TN_PORT) {
2765                             TelnetRequest(TN_DONT, TN_ECHO);
2766                         } else {
2767                             EchoOff();
2768                             TelnetRequest(TN_DO, TN_ECHO);
2769                             remoteEchoOption = TRUE;
2770                         }
2771                         break;
2772                       default:
2773                         if (appData.debugMode)
2774                           fprintf(debugFP, "%d ", option);
2775                         /* Whatever this is, we don't want it. */
2776                         TelnetRequest(TN_DONT, option);
2777                         break;
2778                     }
2779                     break;
2780                   case TN_WONT:
2781                     if (appData.debugMode)
2782                       fprintf(debugFP, "\n<WONT ");
2783                     switch (option = (unsigned char) buf[++i]) {
2784                       case TN_ECHO:
2785                         if (appData.debugMode)
2786                           fprintf(debugFP, "ECHO ");
2787                         /* Reply only if this is a change, according
2788                            to the protocol rules. */
2789                         if (!remoteEchoOption) break;
2790                         EchoOn();
2791                         TelnetRequest(TN_DONT, TN_ECHO);
2792                         remoteEchoOption = FALSE;
2793                         break;
2794                       default:
2795                         if (appData.debugMode)
2796                           fprintf(debugFP, "%d ", (unsigned char) option);
2797                         /* Whatever this is, it must already be turned
2798                            off, because we never agree to turn on
2799                            anything non-default, so according to the
2800                            protocol rules, we don't reply. */
2801                         break;
2802                     }
2803                     break;
2804                   case TN_DO:
2805                     if (appData.debugMode)
2806                       fprintf(debugFP, "\n<DO ");
2807                     switch (option = (unsigned char) buf[++i]) {
2808                       default:
2809                         /* Whatever this is, we refuse to do it. */
2810                         if (appData.debugMode)
2811                           fprintf(debugFP, "%d ", option);
2812                         TelnetRequest(TN_WONT, option);
2813                         break;
2814                     }
2815                     break;
2816                   case TN_DONT:
2817                     if (appData.debugMode)
2818                       fprintf(debugFP, "\n<DONT ");
2819                     switch (option = (unsigned char) buf[++i]) {
2820                       default:
2821                         if (appData.debugMode)
2822                           fprintf(debugFP, "%d ", option);
2823                         /* Whatever this is, we are already not doing
2824                            it, because we never agree to do anything
2825                            non-default, so according to the protocol
2826                            rules, we don't reply. */
2827                         break;
2828                     }
2829                     break;
2830                   case TN_IAC:
2831                     if (appData.debugMode)
2832                       fprintf(debugFP, "\n<IAC ");
2833                     /* Doubled IAC; pass it through */
2834                     i--;
2835                     break;
2836                   default:
2837                     if (appData.debugMode)
2838                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2839                     /* Drop all other telnet commands on the floor */
2840                     break;
2841                 }
2842                 if (oldi > next_out)
2843                   SendToPlayer(&buf[next_out], oldi - next_out);
2844                 if (++i > next_out)
2845                   next_out = i;
2846                 continue;
2847             }
2848
2849             /* OK, this at least will *usually* work */
2850             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2851                 loggedOn = TRUE;
2852             }
2853
2854             if (loggedOn && !intfSet) {
2855                 if (ics_type == ICS_ICC) {
2856                   snprintf(str, MSG_SIZ,
2857                           "/set-quietly interface %s\n/set-quietly style 12\n",
2858                           programVersion);
2859                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2860                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2861                 } else if (ics_type == ICS_CHESSNET) {
2862                   snprintf(str, MSG_SIZ, "/style 12\n");
2863                 } else {
2864                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2865                   strcat(str, programVersion);
2866                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2867                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2868                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2869 #ifdef WIN32
2870                   strcat(str, "$iset nohighlight 1\n");
2871 #endif
2872                   strcat(str, "$iset lock 1\n$style 12\n");
2873                 }
2874                 SendToICS(str);
2875                 NotifyFrontendLogin();
2876                 intfSet = TRUE;
2877             }
2878
2879             if (started == STARTED_COMMENT) {
2880                 /* Accumulate characters in comment */
2881                 parse[parse_pos++] = buf[i];
2882                 if (buf[i] == '\n') {
2883                     parse[parse_pos] = NULLCHAR;
2884                     if(chattingPartner>=0) {
2885                         char mess[MSG_SIZ];
2886                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2887                         OutputChatMessage(chattingPartner, mess);
2888                         chattingPartner = -1;
2889                         next_out = i+1; // [HGM] suppress printing in ICS window
2890                     } else
2891                     if(!suppressKibitz) // [HGM] kibitz
2892                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2893                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2894                         int nrDigit = 0, nrAlph = 0, j;
2895                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2896                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2897                         parse[parse_pos] = NULLCHAR;
2898                         // try to be smart: if it does not look like search info, it should go to
2899                         // ICS interaction window after all, not to engine-output window.
2900                         for(j=0; j<parse_pos; j++) { // count letters and digits
2901                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2902                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2903                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2904                         }
2905                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2906                             int depth=0; float score;
2907                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2908                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2909                                 pvInfoList[forwardMostMove-1].depth = depth;
2910                                 pvInfoList[forwardMostMove-1].score = 100*score;
2911                             }
2912                             OutputKibitz(suppressKibitz, parse);
2913                         } else {
2914                             char tmp[MSG_SIZ];
2915                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2916                             SendToPlayer(tmp, strlen(tmp));
2917                         }
2918                         next_out = i+1; // [HGM] suppress printing in ICS window
2919                     }
2920                     started = STARTED_NONE;
2921                 } else {
2922                     /* Don't match patterns against characters in comment */
2923                     i++;
2924                     continue;
2925                 }
2926             }
2927             if (started == STARTED_CHATTER) {
2928                 if (buf[i] != '\n') {
2929                     /* Don't match patterns against characters in chatter */
2930                     i++;
2931                     continue;
2932                 }
2933                 started = STARTED_NONE;
2934                 if(suppressKibitz) next_out = i+1;
2935             }
2936
2937             /* Kludge to deal with rcmd protocol */
2938             if (firstTime && looking_at(buf, &i, "\001*")) {
2939                 DisplayFatalError(&buf[1], 0, 1);
2940                 continue;
2941             } else {
2942                 firstTime = FALSE;
2943             }
2944
2945             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2946                 ics_type = ICS_ICC;
2947                 ics_prefix = "/";
2948                 if (appData.debugMode)
2949                   fprintf(debugFP, "ics_type %d\n", ics_type);
2950                 continue;
2951             }
2952             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2953                 ics_type = ICS_FICS;
2954                 ics_prefix = "$";
2955                 if (appData.debugMode)
2956                   fprintf(debugFP, "ics_type %d\n", ics_type);
2957                 continue;
2958             }
2959             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2960                 ics_type = ICS_CHESSNET;
2961                 ics_prefix = "/";
2962                 if (appData.debugMode)
2963                   fprintf(debugFP, "ics_type %d\n", ics_type);
2964                 continue;
2965             }
2966
2967             if (!loggedOn &&
2968                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2969                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2970                  looking_at(buf, &i, "will be \"*\""))) {
2971               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2972               continue;
2973             }
2974
2975             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2976               char buf[MSG_SIZ];
2977               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2978               DisplayIcsInteractionTitle(buf);
2979               have_set_title = TRUE;
2980             }
2981
2982             /* skip finger notes */
2983             if (started == STARTED_NONE &&
2984                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2985                  (buf[i] == '1' && buf[i+1] == '0')) &&
2986                 buf[i+2] == ':' && buf[i+3] == ' ') {
2987               started = STARTED_CHATTER;
2988               i += 3;
2989               continue;
2990             }
2991
2992             oldi = i;
2993             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2994             if(appData.seekGraph) {
2995                 if(soughtPending && MatchSoughtLine(buf+i)) {
2996                     i = strstr(buf+i, "rated") - buf;
2997                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2998                     next_out = leftover_start = i;
2999                     started = STARTED_CHATTER;
3000                     suppressKibitz = TRUE;
3001                     continue;
3002                 }
3003                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3004                         && looking_at(buf, &i, "* ads displayed")) {
3005                     soughtPending = FALSE;
3006                     seekGraphUp = TRUE;
3007                     DrawSeekGraph();
3008                     continue;
3009                 }
3010                 if(appData.autoRefresh) {
3011                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3012                         int s = (ics_type == ICS_ICC); // ICC format differs
3013                         if(seekGraphUp)
3014                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3015                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3016                         looking_at(buf, &i, "*% "); // eat prompt
3017                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3018                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3019                         next_out = i; // suppress
3020                         continue;
3021                     }
3022                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3023                         char *p = star_match[0];
3024                         while(*p) {
3025                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3026                             while(*p && *p++ != ' '); // next
3027                         }
3028                         looking_at(buf, &i, "*% "); // eat prompt
3029                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3030                         next_out = i;
3031                         continue;
3032                     }
3033                 }
3034             }
3035
3036             /* skip formula vars */
3037             if (started == STARTED_NONE &&
3038                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3039               started = STARTED_CHATTER;
3040               i += 3;
3041               continue;
3042             }
3043
3044             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3045             if (appData.autoKibitz && started == STARTED_NONE &&
3046                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3047                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3048                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3049                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3050                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3051                         suppressKibitz = TRUE;
3052                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3053                         next_out = i;
3054                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3055                                 && (gameMode == IcsPlayingWhite)) ||
3056                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3057                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3058                             started = STARTED_CHATTER; // own kibitz we simply discard
3059                         else {
3060                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3061                             parse_pos = 0; parse[0] = NULLCHAR;
3062                             savingComment = TRUE;
3063                             suppressKibitz = gameMode != IcsObserving ? 2 :
3064                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3065                         }
3066                         continue;
3067                 } else
3068                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3069                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3070                          && atoi(star_match[0])) {
3071                     // suppress the acknowledgements of our own autoKibitz
3072                     char *p;
3073                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3075                     SendToPlayer(star_match[0], strlen(star_match[0]));
3076                     if(looking_at(buf, &i, "*% ")) // eat prompt
3077                         suppressKibitz = FALSE;
3078                     next_out = i;
3079                     continue;
3080                 }
3081             } // [HGM] kibitz: end of patch
3082
3083             // [HGM] chat: intercept tells by users for which we have an open chat window
3084             channel = -1;
3085             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3086                                            looking_at(buf, &i, "* whispers:") ||
3087                                            looking_at(buf, &i, "* kibitzes:") ||
3088                                            looking_at(buf, &i, "* shouts:") ||
3089                                            looking_at(buf, &i, "* c-shouts:") ||
3090                                            looking_at(buf, &i, "--> * ") ||
3091                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3092                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3093                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3094                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3095                 int p;
3096                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3097                 chattingPartner = -1;
3098
3099                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3100                 for(p=0; p<MAX_CHAT; p++) {
3101                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3102                     talker[0] = '['; strcat(talker, "] ");
3103                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3104                     chattingPartner = p; break;
3105                     }
3106                 } else
3107                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3108                 for(p=0; p<MAX_CHAT; p++) {
3109                     if(!strcmp("kibitzes", chatPartner[p])) {
3110                         talker[0] = '['; strcat(talker, "] ");
3111                         chattingPartner = p; break;
3112                     }
3113                 } else
3114                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3115                 for(p=0; p<MAX_CHAT; p++) {
3116                     if(!strcmp("whispers", chatPartner[p])) {
3117                         talker[0] = '['; strcat(talker, "] ");
3118                         chattingPartner = p; break;
3119                     }
3120                 } else
3121                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3122                   if(buf[i-8] == '-' && buf[i-3] == 't')
3123                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3124                     if(!strcmp("c-shouts", chatPartner[p])) {
3125                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3126                         chattingPartner = p; break;
3127                     }
3128                   }
3129                   if(chattingPartner < 0)
3130                   for(p=0; p<MAX_CHAT; p++) {
3131                     if(!strcmp("shouts", chatPartner[p])) {
3132                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3133                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3134                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3135                         chattingPartner = p; break;
3136                     }
3137                   }
3138                 }
3139                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3140                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3141                     talker[0] = 0; Colorize(ColorTell, FALSE);
3142                     chattingPartner = p; break;
3143                 }
3144                 if(chattingPartner<0) i = oldi; else {
3145                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3146                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3147                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3148                     started = STARTED_COMMENT;
3149                     parse_pos = 0; parse[0] = NULLCHAR;
3150                     savingComment = 3 + chattingPartner; // counts as TRUE
3151                     suppressKibitz = TRUE;
3152                     continue;
3153                 }
3154             } // [HGM] chat: end of patch
3155
3156           backup = i;
3157             if (appData.zippyTalk || appData.zippyPlay) {
3158                 /* [DM] Backup address for color zippy lines */
3159 #if ZIPPY
3160                if (loggedOn == TRUE)
3161                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3162                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3163 #endif
3164             } // [DM] 'else { ' deleted
3165                 if (
3166                     /* Regular tells and says */
3167                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3168                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3169                     looking_at(buf, &i, "* says: ") ||
3170                     /* Don't color "message" or "messages" output */
3171                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3172                     looking_at(buf, &i, "*. * at *:*: ") ||
3173                     looking_at(buf, &i, "--* (*:*): ") ||
3174                     /* Message notifications (same color as tells) */
3175                     looking_at(buf, &i, "* has left a message ") ||
3176                     looking_at(buf, &i, "* just sent you a message:\n") ||
3177                     /* Whispers and kibitzes */
3178                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3179                     looking_at(buf, &i, "* kibitzes: ") ||
3180                     /* Channel tells */
3181                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3182
3183                   if (tkind == 1 && strchr(star_match[0], ':')) {
3184                       /* Avoid "tells you:" spoofs in channels */
3185                      tkind = 3;
3186                   }
3187                   if (star_match[0][0] == NULLCHAR ||
3188                       strchr(star_match[0], ' ') ||
3189                       (tkind == 3 && strchr(star_match[1], ' '))) {
3190                     /* Reject bogus matches */
3191                     i = oldi;
3192                   } else {
3193                     if (appData.colorize) {
3194                       if (oldi > next_out) {
3195                         SendToPlayer(&buf[next_out], oldi - next_out);
3196                         next_out = oldi;
3197                       }
3198                       switch (tkind) {
3199                       case 1:
3200                         Colorize(ColorTell, FALSE);
3201                         curColor = ColorTell;
3202                         break;
3203                       case 2:
3204                         Colorize(ColorKibitz, FALSE);
3205                         curColor = ColorKibitz;
3206                         break;
3207                       case 3:
3208                         p = strrchr(star_match[1], '(');
3209                         if (p == NULL) {
3210                           p = star_match[1];
3211                         } else {
3212                           p++;
3213                         }
3214                         if (atoi(p) == 1) {
3215                           Colorize(ColorChannel1, FALSE);
3216                           curColor = ColorChannel1;
3217                         } else {
3218                           Colorize(ColorChannel, FALSE);
3219                           curColor = ColorChannel;
3220                         }
3221                         break;
3222                       case 5:
3223                         curColor = ColorNormal;
3224                         break;
3225                       }
3226                     }
3227                     if (started == STARTED_NONE && appData.autoComment &&
3228                         (gameMode == IcsObserving ||
3229                          gameMode == IcsPlayingWhite ||
3230                          gameMode == IcsPlayingBlack)) {
3231                       parse_pos = i - oldi;
3232                       memcpy(parse, &buf[oldi], parse_pos);
3233                       parse[parse_pos] = NULLCHAR;
3234                       started = STARTED_COMMENT;
3235                       savingComment = TRUE;
3236                     } else {
3237                       started = STARTED_CHATTER;
3238                       savingComment = FALSE;
3239                     }
3240                     loggedOn = TRUE;
3241                     continue;
3242                   }
3243                 }
3244
3245                 if (looking_at(buf, &i, "* s-shouts: ") ||
3246                     looking_at(buf, &i, "* c-shouts: ")) {
3247                     if (appData.colorize) {
3248                         if (oldi > next_out) {
3249                             SendToPlayer(&buf[next_out], oldi - next_out);
3250                             next_out = oldi;
3251                         }
3252                         Colorize(ColorSShout, FALSE);
3253                         curColor = ColorSShout;
3254                     }
3255                     loggedOn = TRUE;
3256                     started = STARTED_CHATTER;
3257                     continue;
3258                 }
3259
3260                 if (looking_at(buf, &i, "--->")) {
3261                     loggedOn = TRUE;
3262                     continue;
3263                 }
3264
3265                 if (looking_at(buf, &i, "* shouts: ") ||
3266                     looking_at(buf, &i, "--> ")) {
3267                     if (appData.colorize) {
3268                         if (oldi > next_out) {
3269                             SendToPlayer(&buf[next_out], oldi - next_out);
3270                             next_out = oldi;
3271                         }
3272                         Colorize(ColorShout, FALSE);
3273                         curColor = ColorShout;
3274                     }
3275                     loggedOn = TRUE;
3276                     started = STARTED_CHATTER;
3277                     continue;
3278                 }
3279
3280                 if (looking_at( buf, &i, "Challenge:")) {
3281                     if (appData.colorize) {
3282                         if (oldi > next_out) {
3283                             SendToPlayer(&buf[next_out], oldi - next_out);
3284                             next_out = oldi;
3285                         }
3286                         Colorize(ColorChallenge, FALSE);
3287                         curColor = ColorChallenge;
3288                     }
3289                     loggedOn = TRUE;
3290                     continue;
3291                 }
3292
3293                 if (looking_at(buf, &i, "* offers you") ||
3294                     looking_at(buf, &i, "* offers to be") ||
3295                     looking_at(buf, &i, "* would like to") ||
3296                     looking_at(buf, &i, "* requests to") ||
3297                     looking_at(buf, &i, "Your opponent offers") ||
3298                     looking_at(buf, &i, "Your opponent requests")) {
3299
3300                     if (appData.colorize) {
3301                         if (oldi > next_out) {
3302                             SendToPlayer(&buf[next_out], oldi - next_out);
3303                             next_out = oldi;
3304                         }
3305                         Colorize(ColorRequest, FALSE);
3306                         curColor = ColorRequest;
3307                     }
3308                     continue;
3309                 }
3310
3311                 if (looking_at(buf, &i, "* (*) seeking")) {
3312                     if (appData.colorize) {
3313                         if (oldi > next_out) {
3314                             SendToPlayer(&buf[next_out], oldi - next_out);
3315                             next_out = oldi;
3316                         }
3317                         Colorize(ColorSeek, FALSE);
3318                         curColor = ColorSeek;
3319                     }
3320                     continue;
3321             }
3322
3323           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3324
3325             if (looking_at(buf, &i, "\\   ")) {
3326                 if (prevColor != ColorNormal) {
3327                     if (oldi > next_out) {
3328                         SendToPlayer(&buf[next_out], oldi - next_out);
3329                         next_out = oldi;
3330                     }
3331                     Colorize(prevColor, TRUE);
3332                     curColor = prevColor;
3333                 }
3334                 if (savingComment) {
3335                     parse_pos = i - oldi;
3336                     memcpy(parse, &buf[oldi], parse_pos);
3337                     parse[parse_pos] = NULLCHAR;
3338                     started = STARTED_COMMENT;
3339                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3340                         chattingPartner = savingComment - 3; // kludge to remember the box
3341                 } else {
3342                     started = STARTED_CHATTER;
3343                 }
3344                 continue;
3345             }
3346
3347             if (looking_at(buf, &i, "Black Strength :") ||
3348                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3349                 looking_at(buf, &i, "<10>") ||
3350                 looking_at(buf, &i, "#@#")) {
3351                 /* Wrong board style */
3352                 loggedOn = TRUE;
3353                 SendToICS(ics_prefix);
3354                 SendToICS("set style 12\n");
3355                 SendToICS(ics_prefix);
3356                 SendToICS("refresh\n");
3357                 continue;
3358             }
3359
3360             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3361                 ICSInitScript();
3362                 have_sent_ICS_logon = 1;
3363                 continue;
3364             }
3365
3366             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3367                 (looking_at(buf, &i, "\n<12> ") ||
3368                  looking_at(buf, &i, "<12> "))) {
3369                 loggedOn = TRUE;
3370                 if (oldi > next_out) {
3371                     SendToPlayer(&buf[next_out], oldi - next_out);
3372                 }
3373                 next_out = i;
3374                 started = STARTED_BOARD;
3375                 parse_pos = 0;
3376                 continue;
3377             }
3378
3379             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3380                 looking_at(buf, &i, "<b1> ")) {
3381                 if (oldi > next_out) {
3382                     SendToPlayer(&buf[next_out], oldi - next_out);
3383                 }
3384                 next_out = i;
3385                 started = STARTED_HOLDINGS;
3386                 parse_pos = 0;
3387                 continue;
3388             }
3389
3390             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3391                 loggedOn = TRUE;
3392                 /* Header for a move list -- first line */
3393
3394                 switch (ics_getting_history) {
3395                   case H_FALSE:
3396                     switch (gameMode) {
3397                       case IcsIdle:
3398                       case BeginningOfGame:
3399                         /* User typed "moves" or "oldmoves" while we
3400                            were idle.  Pretend we asked for these
3401                            moves and soak them up so user can step
3402                            through them and/or save them.
3403                            */
3404                         Reset(FALSE, TRUE);
3405                         gameMode = IcsObserving;
3406                         ModeHighlight();
3407                         ics_gamenum = -1;
3408                         ics_getting_history = H_GOT_UNREQ_HEADER;
3409                         break;
3410                       case EditGame: /*?*/
3411                       case EditPosition: /*?*/
3412                         /* Should above feature work in these modes too? */
3413                         /* For now it doesn't */
3414                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3415                         break;
3416                       default:
3417                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3418                         break;
3419                     }
3420                     break;
3421                   case H_REQUESTED:
3422                     /* Is this the right one? */
3423                     if (gameInfo.white && gameInfo.black &&
3424                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3425                         strcmp(gameInfo.black, star_match[2]) == 0) {
3426                         /* All is well */
3427                         ics_getting_history = H_GOT_REQ_HEADER;
3428                     }
3429                     break;
3430                   case H_GOT_REQ_HEADER:
3431                   case H_GOT_UNREQ_HEADER:
3432                   case H_GOT_UNWANTED_HEADER:
3433                   case H_GETTING_MOVES:
3434                     /* Should not happen */
3435                     DisplayError(_("Error gathering move list: two headers"), 0);
3436                     ics_getting_history = H_FALSE;
3437                     break;
3438                 }
3439
3440                 /* Save player ratings into gameInfo if needed */
3441                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3442                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3443                     (gameInfo.whiteRating == -1 ||
3444                      gameInfo.blackRating == -1)) {
3445
3446                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3447                     gameInfo.blackRating = string_to_rating(star_match[3]);
3448                     if (appData.debugMode)
3449                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3450                               gameInfo.whiteRating, gameInfo.blackRating);
3451                 }
3452                 continue;
3453             }
3454
3455             if (looking_at(buf, &i,
3456               "* * match, initial time: * minute*, increment: * second")) {
3457                 /* Header for a move list -- second line */
3458                 /* Initial board will follow if this is a wild game */
3459                 if (gameInfo.event != NULL) free(gameInfo.event);
3460                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3461                 gameInfo.event = StrSave(str);
3462                 /* [HGM] we switched variant. Translate boards if needed. */
3463                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3464                 continue;
3465             }
3466
3467             if (looking_at(buf, &i, "Move  ")) {
3468                 /* Beginning of a move list */
3469                 switch (ics_getting_history) {
3470                   case H_FALSE:
3471                     /* Normally should not happen */
3472                     /* Maybe user hit reset while we were parsing */
3473                     break;
3474                   case H_REQUESTED:
3475                     /* Happens if we are ignoring a move list that is not
3476                      * the one we just requested.  Common if the user
3477                      * tries to observe two games without turning off
3478                      * getMoveList */
3479                     break;
3480                   case H_GETTING_MOVES:
3481                     /* Should not happen */
3482                     DisplayError(_("Error gathering move list: nested"), 0);
3483                     ics_getting_history = H_FALSE;
3484                     break;
3485                   case H_GOT_REQ_HEADER:
3486                     ics_getting_history = H_GETTING_MOVES;
3487                     started = STARTED_MOVES;
3488                     parse_pos = 0;
3489                     if (oldi > next_out) {
3490                         SendToPlayer(&buf[next_out], oldi - next_out);
3491                     }
3492                     break;
3493                   case H_GOT_UNREQ_HEADER:
3494                     ics_getting_history = H_GETTING_MOVES;
3495                     started = STARTED_MOVES_NOHIDE;
3496                     parse_pos = 0;
3497                     break;
3498                   case H_GOT_UNWANTED_HEADER:
3499                     ics_getting_history = H_FALSE;
3500                     break;
3501                 }
3502                 continue;
3503             }
3504
3505             if (looking_at(buf, &i, "% ") ||
3506                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3507                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3508                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3509                     soughtPending = FALSE;
3510                     seekGraphUp = TRUE;
3511                     DrawSeekGraph();
3512                 }
3513                 if(suppressKibitz) next_out = i;
3514                 savingComment = FALSE;
3515                 suppressKibitz = 0;
3516                 switch (started) {
3517                   case STARTED_MOVES:
3518                   case STARTED_MOVES_NOHIDE:
3519                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3520                     parse[parse_pos + i - oldi] = NULLCHAR;
3521                     ParseGameHistory(parse);
3522 #if ZIPPY
3523                     if (appData.zippyPlay && first.initDone) {
3524                         FeedMovesToProgram(&first, forwardMostMove);
3525                         if (gameMode == IcsPlayingWhite) {
3526                             if (WhiteOnMove(forwardMostMove)) {
3527                                 if (first.sendTime) {
3528                                   if (first.useColors) {
3529                                     SendToProgram("black\n", &first);
3530                                   }
3531                                   SendTimeRemaining(&first, TRUE);
3532                                 }
3533                                 if (first.useColors) {
3534                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3535                                 }
3536                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3537                                 first.maybeThinking = TRUE;
3538                             } else {
3539                                 if (first.usePlayother) {
3540                                   if (first.sendTime) {
3541                                     SendTimeRemaining(&first, TRUE);
3542                                   }
3543                                   SendToProgram("playother\n", &first);
3544                                   firstMove = FALSE;
3545                                 } else {
3546                                   firstMove = TRUE;
3547                                 }
3548                             }
3549                         } else if (gameMode == IcsPlayingBlack) {
3550                             if (!WhiteOnMove(forwardMostMove)) {
3551                                 if (first.sendTime) {
3552                                   if (first.useColors) {
3553                                     SendToProgram("white\n", &first);
3554                                   }
3555                                   SendTimeRemaining(&first, FALSE);
3556                                 }
3557                                 if (first.useColors) {
3558                                   SendToProgram("black\n", &first);
3559                                 }
3560                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3561                                 first.maybeThinking = TRUE;
3562                             } else {
3563                                 if (first.usePlayother) {
3564                                   if (first.sendTime) {
3565                                     SendTimeRemaining(&first, FALSE);
3566                                   }
3567                                   SendToProgram("playother\n", &first);
3568                                   firstMove = FALSE;
3569                                 } else {
3570                                   firstMove = TRUE;
3571                                 }
3572                             }
3573                         }
3574                     }
3575 #endif
3576                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3577                         /* Moves came from oldmoves or moves command
3578                            while we weren't doing anything else.
3579                            */
3580                         currentMove = forwardMostMove;
3581                         ClearHighlights();/*!!could figure this out*/
3582                         flipView = appData.flipView;
3583                         DrawPosition(TRUE, boards[currentMove]);
3584                         DisplayBothClocks();
3585                         snprintf(str, MSG_SIZ, "%s vs. %s",
3586                                 gameInfo.white, gameInfo.black);
3587                         DisplayTitle(str);
3588                         gameMode = IcsIdle;
3589                     } else {
3590                         /* Moves were history of an active game */
3591                         if (gameInfo.resultDetails != NULL) {
3592                             free(gameInfo.resultDetails);
3593                             gameInfo.resultDetails = NULL;
3594                         }
3595                     }
3596                     HistorySet(parseList, backwardMostMove,
3597                                forwardMostMove, currentMove-1);
3598                     DisplayMove(currentMove - 1);
3599                     if (started == STARTED_MOVES) next_out = i;
3600                     started = STARTED_NONE;
3601                     ics_getting_history = H_FALSE;
3602                     break;
3603
3604                   case STARTED_OBSERVE:
3605                     started = STARTED_NONE;
3606                     SendToICS(ics_prefix);
3607                     SendToICS("refresh\n");
3608                     break;
3609
3610                   default:
3611                     break;
3612                 }
3613                 if(bookHit) { // [HGM] book: simulate book reply
3614                     static char bookMove[MSG_SIZ]; // a bit generous?
3615
3616                     programStats.nodes = programStats.depth = programStats.time =
3617                     programStats.score = programStats.got_only_move = 0;
3618                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3619
3620                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3621                     strcat(bookMove, bookHit);
3622                     HandleMachineMove(bookMove, &first);
3623                 }
3624                 continue;
3625             }
3626
3627             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3628                  started == STARTED_HOLDINGS ||
3629                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3630                 /* Accumulate characters in move list or board */
3631                 parse[parse_pos++] = buf[i];
3632             }
3633
3634             /* Start of game messages.  Mostly we detect start of game
3635                when the first board image arrives.  On some versions
3636                of the ICS, though, we need to do a "refresh" after starting
3637                to observe in order to get the current board right away. */
3638             if (looking_at(buf, &i, "Adding game * to observation list")) {
3639                 started = STARTED_OBSERVE;
3640                 continue;
3641             }
3642
3643             /* Handle auto-observe */
3644             if (appData.autoObserve &&
3645                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3646                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3647                 char *player;
3648                 /* Choose the player that was highlighted, if any. */
3649                 if (star_match[0][0] == '\033' ||
3650                     star_match[1][0] != '\033') {
3651                     player = star_match[0];
3652                 } else {
3653                     player = star_match[2];
3654                 }
3655                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3656                         ics_prefix, StripHighlightAndTitle(player));
3657                 SendToICS(str);
3658
3659                 /* Save ratings from notify string */
3660                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3661                 player1Rating = string_to_rating(star_match[1]);
3662                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3663                 player2Rating = string_to_rating(star_match[3]);
3664
3665                 if (appData.debugMode)
3666                   fprintf(debugFP,
3667                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3668                           player1Name, player1Rating,
3669                           player2Name, player2Rating);
3670
3671                 continue;
3672             }
3673
3674             /* Deal with automatic examine mode after a game,
3675                and with IcsObserving -> IcsExamining transition */
3676             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3677                 looking_at(buf, &i, "has made you an examiner of game *")) {
3678
3679                 int gamenum = atoi(star_match[0]);
3680                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3681                     gamenum == ics_gamenum) {
3682                     /* We were already playing or observing this game;
3683                        no need to refetch history */
3684                     gameMode = IcsExamining;
3685                     if (pausing) {
3686                         pauseExamForwardMostMove = forwardMostMove;
3687                     } else if (currentMove < forwardMostMove) {
3688                         ForwardInner(forwardMostMove);
3689                     }
3690                 } else {
3691                     /* I don't think this case really can happen */
3692                     SendToICS(ics_prefix);
3693                     SendToICS("refresh\n");
3694                 }
3695                 continue;
3696             }
3697
3698             /* Error messages */
3699 //          if (ics_user_moved) {
3700             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3701                 if (looking_at(buf, &i, "Illegal move") ||
3702                     looking_at(buf, &i, "Not a legal move") ||
3703                     looking_at(buf, &i, "Your king is in check") ||
3704                     looking_at(buf, &i, "It isn't your turn") ||
3705                     looking_at(buf, &i, "It is not your move")) {
3706                     /* Illegal move */
3707                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3708                         currentMove = forwardMostMove-1;
3709                         DisplayMove(currentMove - 1); /* before DMError */
3710                         DrawPosition(FALSE, boards[currentMove]);
3711                         SwitchClocks(forwardMostMove-1); // [HGM] race
3712                         DisplayBothClocks();
3713                     }
3714                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3715                     ics_user_moved = 0;
3716                     continue;
3717                 }
3718             }
3719
3720             if (looking_at(buf, &i, "still have time") ||
3721                 looking_at(buf, &i, "not out of time") ||
3722                 looking_at(buf, &i, "either player is out of time") ||
3723                 looking_at(buf, &i, "has timeseal; checking")) {
3724                 /* We must have called his flag a little too soon */
3725                 whiteFlag = blackFlag = FALSE;
3726                 continue;
3727             }
3728
3729             if (looking_at(buf, &i, "added * seconds to") ||
3730                 looking_at(buf, &i, "seconds were added to")) {
3731                 /* Update the clocks */
3732                 SendToICS(ics_prefix);
3733                 SendToICS("refresh\n");
3734                 continue;
3735             }
3736
3737             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3738                 ics_clock_paused = TRUE;
3739                 StopClocks();
3740                 continue;
3741             }
3742
3743             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3744                 ics_clock_paused = FALSE;
3745                 StartClocks();
3746                 continue;
3747             }
3748
3749             /* Grab player ratings from the Creating: message.
3750                Note we have to check for the special case when
3751                the ICS inserts things like [white] or [black]. */
3752             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3753                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3754                 /* star_matches:
3755                    0    player 1 name (not necessarily white)
3756                    1    player 1 rating
3757                    2    empty, white, or black (IGNORED)
3758                    3    player 2 name (not necessarily black)
3759                    4    player 2 rating
3760
3761                    The names/ratings are sorted out when the game
3762                    actually starts (below).
3763                 */
3764                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3765                 player1Rating = string_to_rating(star_match[1]);
3766                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3767                 player2Rating = string_to_rating(star_match[4]);
3768
3769                 if (appData.debugMode)
3770                   fprintf(debugFP,
3771                           "Ratings from 'Creating:' %s %d, %s %d\n",
3772                           player1Name, player1Rating,
3773                           player2Name, player2Rating);
3774
3775                 continue;
3776             }
3777
3778             /* Improved generic start/end-of-game messages */
3779             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3780                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3781                 /* If tkind == 0: */
3782                 /* star_match[0] is the game number */
3783                 /*           [1] is the white player's name */
3784                 /*           [2] is the black player's name */
3785                 /* For end-of-game: */
3786                 /*           [3] is the reason for the game end */
3787                 /*           [4] is a PGN end game-token, preceded by " " */
3788                 /* For start-of-game: */
3789                 /*           [3] begins with "Creating" or "Continuing" */
3790                 /*           [4] is " *" or empty (don't care). */
3791                 int gamenum = atoi(star_match[0]);
3792                 char *whitename, *blackname, *why, *endtoken;
3793                 ChessMove endtype = EndOfFile;
3794
3795                 if (tkind == 0) {
3796                   whitename = star_match[1];
3797                   blackname = star_match[2];
3798                   why = star_match[3];
3799                   endtoken = star_match[4];
3800                 } else {
3801                   whitename = star_match[1];
3802                   blackname = star_match[3];
3803                   why = star_match[5];
3804                   endtoken = star_match[6];
3805                 }
3806
3807                 /* Game start messages */
3808                 if (strncmp(why, "Creating ", 9) == 0 ||
3809                     strncmp(why, "Continuing ", 11) == 0) {
3810                     gs_gamenum = gamenum;
3811                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3812                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3813 #if ZIPPY
3814                     if (appData.zippyPlay) {
3815                         ZippyGameStart(whitename, blackname);
3816                     }
3817 #endif /*ZIPPY*/
3818                     partnerBoardValid = FALSE; // [HGM] bughouse
3819                     continue;
3820                 }
3821
3822                 /* Game end messages */
3823                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3824                     ics_gamenum != gamenum) {
3825                     continue;
3826                 }
3827                 while (endtoken[0] == ' ') endtoken++;
3828                 switch (endtoken[0]) {
3829                   case '*':
3830                   default:
3831                     endtype = GameUnfinished;
3832                     break;
3833                   case '0':
3834                     endtype = BlackWins;
3835                     break;
3836                   case '1':
3837                     if (endtoken[1] == '/')
3838                       endtype = GameIsDrawn;
3839                     else
3840                       endtype = WhiteWins;
3841                     break;
3842                 }
3843                 GameEnds(endtype, why, GE_ICS);
3844 #if ZIPPY
3845                 if (appData.zippyPlay && first.initDone) {
3846                     ZippyGameEnd(endtype, why);
3847                     if (first.pr == NULL) {
3848                       /* Start the next process early so that we'll
3849                          be ready for the next challenge */
3850                       StartChessProgram(&first);
3851                     }
3852                     /* Send "new" early, in case this command takes
3853                        a long time to finish, so that we'll be ready
3854                        for the next challenge. */
3855                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3856                     Reset(TRUE, TRUE);
3857                 }
3858 #endif /*ZIPPY*/
3859                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3860                 continue;
3861             }
3862
3863             if (looking_at(buf, &i, "Removing game * from observation") ||
3864                 looking_at(buf, &i, "no longer observing game *") ||
3865                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3866                 if (gameMode == IcsObserving &&
3867                     atoi(star_match[0]) == ics_gamenum)
3868                   {
3869                       /* icsEngineAnalyze */
3870                       if (appData.icsEngineAnalyze) {
3871                             ExitAnalyzeMode();
3872                             ModeHighlight();
3873                       }
3874                       StopClocks();
3875                       gameMode = IcsIdle;
3876                       ics_gamenum = -1;
3877                       ics_user_moved = FALSE;
3878                   }
3879                 continue;
3880             }
3881
3882             if (looking_at(buf, &i, "no longer examining game *")) {
3883                 if (gameMode == IcsExamining &&
3884                     atoi(star_match[0]) == ics_gamenum)
3885                   {
3886                       gameMode = IcsIdle;
3887                       ics_gamenum = -1;
3888                       ics_user_moved = FALSE;
3889                   }
3890                 continue;
3891             }
3892
3893             /* Advance leftover_start past any newlines we find,
3894                so only partial lines can get reparsed */
3895             if (looking_at(buf, &i, "\n")) {
3896                 prevColor = curColor;
3897                 if (curColor != ColorNormal) {
3898                     if (oldi > next_out) {
3899                         SendToPlayer(&buf[next_out], oldi - next_out);
3900                         next_out = oldi;
3901                     }
3902                     Colorize(ColorNormal, FALSE);
3903                     curColor = ColorNormal;
3904                 }
3905                 if (started == STARTED_BOARD) {
3906                     started = STARTED_NONE;
3907                     parse[parse_pos] = NULLCHAR;
3908                     ParseBoard12(parse);
3909                     ics_user_moved = 0;
3910
3911                     /* Send premove here */
3912                     if (appData.premove) {
3913                       char str[MSG_SIZ];
3914                       if (currentMove == 0 &&
3915                           gameMode == IcsPlayingWhite &&
3916                           appData.premoveWhite) {
3917                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3918                         if (appData.debugMode)
3919                           fprintf(debugFP, "Sending premove:\n");
3920                         SendToICS(str);
3921                       } else if (currentMove == 1 &&
3922                                  gameMode == IcsPlayingBlack &&
3923                                  appData.premoveBlack) {
3924                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3925                         if (appData.debugMode)
3926                           fprintf(debugFP, "Sending premove:\n");
3927                         SendToICS(str);
3928                       } else if (gotPremove) {
3929                         gotPremove = 0;
3930                         ClearPremoveHighlights();
3931                         if (appData.debugMode)
3932                           fprintf(debugFP, "Sending premove:\n");
3933                           UserMoveEvent(premoveFromX, premoveFromY,
3934                                         premoveToX, premoveToY,
3935                                         premovePromoChar);
3936                       }
3937                     }
3938
3939                     /* Usually suppress following prompt */
3940                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3941                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3942                         if (looking_at(buf, &i, "*% ")) {
3943                             savingComment = FALSE;
3944                             suppressKibitz = 0;
3945                         }
3946                     }
3947                     next_out = i;
3948                 } else if (started == STARTED_HOLDINGS) {
3949                     int gamenum;
3950                     char new_piece[MSG_SIZ];
3951                     started = STARTED_NONE;
3952                     parse[parse_pos] = NULLCHAR;
3953                     if (appData.debugMode)
3954                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3955                                                         parse, currentMove);
3956                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3957                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3958                         if (gameInfo.variant == VariantNormal) {
3959                           /* [HGM] We seem to switch variant during a game!
3960                            * Presumably no holdings were displayed, so we have
3961                            * to move the position two files to the right to
3962                            * create room for them!
3963                            */
3964                           VariantClass newVariant;
3965                           switch(gameInfo.boardWidth) { // base guess on board width
3966                                 case 9:  newVariant = VariantShogi; break;
3967                                 case 10: newVariant = VariantGreat; break;
3968                                 default: newVariant = VariantCrazyhouse; break;
3969                           }
3970                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3971                           /* Get a move list just to see the header, which
3972                              will tell us whether this is really bug or zh */
3973                           if (ics_getting_history == H_FALSE) {
3974                             ics_getting_history = H_REQUESTED;
3975                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3976                             SendToICS(str);
3977                           }
3978                         }
3979                         new_piece[0] = NULLCHAR;
3980                         sscanf(parse, "game %d white [%s black [%s <- %s",
3981                                &gamenum, white_holding, black_holding,
3982                                new_piece);
3983                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3984                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3985                         /* [HGM] copy holdings to board holdings area */
3986                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3987                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3988                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3989 #if ZIPPY
3990                         if (appData.zippyPlay && first.initDone) {
3991                             ZippyHoldings(white_holding, black_holding,
3992                                           new_piece);
3993                         }
3994 #endif /*ZIPPY*/
3995                         if (tinyLayout || smallLayout) {
3996                             char wh[16], bh[16];
3997                             PackHolding(wh, white_holding);
3998                             PackHolding(bh, black_holding);
3999                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4000                                     gameInfo.white, gameInfo.black);
4001                         } else {
4002                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4003                                     gameInfo.white, white_holding,
4004                                     gameInfo.black, black_holding);
4005                         }
4006                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4007                         DrawPosition(FALSE, boards[currentMove]);
4008                         DisplayTitle(str);
4009                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4010                         sscanf(parse, "game %d white [%s black [%s <- %s",
4011                                &gamenum, white_holding, black_holding,
4012                                new_piece);
4013                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4014                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4015                         /* [HGM] copy holdings to partner-board holdings area */
4016                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4017                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4018                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4019                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4020                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4021                       }
4022                     }
4023                     /* Suppress following prompt */
4024                     if (looking_at(buf, &i, "*% ")) {
4025                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4026                         savingComment = FALSE;
4027                         suppressKibitz = 0;
4028                     }
4029                     next_out = i;
4030                 }
4031                 continue;
4032             }
4033
4034             i++;                /* skip unparsed character and loop back */
4035         }
4036
4037         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4038 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4039 //          SendToPlayer(&buf[next_out], i - next_out);
4040             started != STARTED_HOLDINGS && leftover_start > next_out) {
4041             SendToPlayer(&buf[next_out], leftover_start - next_out);
4042             next_out = i;
4043         }
4044
4045         leftover_len = buf_len - leftover_start;
4046         /* if buffer ends with something we couldn't parse,
4047            reparse it after appending the next read */
4048
4049     } else if (count == 0) {
4050         RemoveInputSource(isr);
4051         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4052     } else {
4053         DisplayFatalError(_("Error reading from ICS"), error, 1);
4054     }
4055 }
4056
4057
4058 /* Board style 12 looks like this:
4059
4060    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4061
4062  * The "<12> " is stripped before it gets to this routine.  The two
4063  * trailing 0's (flip state and clock ticking) are later addition, and
4064  * some chess servers may not have them, or may have only the first.
4065  * Additional trailing fields may be added in the future.
4066  */
4067
4068 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4069
4070 #define RELATION_OBSERVING_PLAYED    0
4071 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4072 #define RELATION_PLAYING_MYMOVE      1
4073 #define RELATION_PLAYING_NOTMYMOVE  -1
4074 #define RELATION_EXAMINING           2
4075 #define RELATION_ISOLATED_BOARD     -3
4076 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4077
4078 void
4079 ParseBoard12(string)
4080      char *string;
4081 {
4082     GameMode newGameMode;
4083     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4084     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4085     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4086     char to_play, board_chars[200];
4087     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4088     char black[32], white[32];
4089     Board board;
4090     int prevMove = currentMove;
4091     int ticking = 2;
4092     ChessMove moveType;
4093     int fromX, fromY, toX, toY;
4094     char promoChar;
4095     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4096     char *bookHit = NULL; // [HGM] book
4097     Boolean weird = FALSE, reqFlag = FALSE;
4098
4099     fromX = fromY = toX = toY = -1;
4100
4101     newGame = FALSE;
4102
4103     if (appData.debugMode)
4104       fprintf(debugFP, _("Parsing board: %s\n"), string);
4105
4106     move_str[0] = NULLCHAR;
4107     elapsed_time[0] = NULLCHAR;
4108     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4109         int  i = 0, j;
4110         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4111             if(string[i] == ' ') { ranks++; files = 0; }
4112             else files++;
4113             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4114             i++;
4115         }
4116         for(j = 0; j <i; j++) board_chars[j] = string[j];
4117         board_chars[i] = '\0';
4118         string += i + 1;
4119     }
4120     n = sscanf(string, PATTERN, &to_play, &double_push,
4121                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4122                &gamenum, white, black, &relation, &basetime, &increment,
4123                &white_stren, &black_stren, &white_time, &black_time,
4124                &moveNum, str, elapsed_time, move_str, &ics_flip,
4125                &ticking);
4126
4127     if (n < 21) {
4128         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4129         DisplayError(str, 0);
4130         return;
4131     }
4132
4133     /* Convert the move number to internal form */
4134     moveNum = (moveNum - 1) * 2;
4135     if (to_play == 'B') moveNum++;
4136     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4137       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4138                         0, 1);
4139       return;
4140     }
4141
4142     switch (relation) {
4143       case RELATION_OBSERVING_PLAYED:
4144       case RELATION_OBSERVING_STATIC:
4145         if (gamenum == -1) {
4146             /* Old ICC buglet */
4147             relation = RELATION_OBSERVING_STATIC;
4148         }
4149         newGameMode = IcsObserving;
4150         break;
4151       case RELATION_PLAYING_MYMOVE:
4152       case RELATION_PLAYING_NOTMYMOVE:
4153         newGameMode =
4154           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4155             IcsPlayingWhite : IcsPlayingBlack;
4156         break;
4157       case RELATION_EXAMINING:
4158         newGameMode = IcsExamining;
4159         break;
4160       case RELATION_ISOLATED_BOARD:
4161       default:
4162         /* Just display this board.  If user was doing something else,
4163            we will forget about it until the next board comes. */
4164         newGameMode = IcsIdle;
4165         break;
4166       case RELATION_STARTING_POSITION:
4167         newGameMode = gameMode;
4168         break;
4169     }
4170
4171     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4172          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4173       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4174       char *toSqr;
4175       for (k = 0; k < ranks; k++) {
4176         for (j = 0; j < files; j++)
4177           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4178         if(gameInfo.holdingsWidth > 1) {
4179              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4180              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4181         }
4182       }
4183       CopyBoard(partnerBoard, board);
4184       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4185         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4186         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4187       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4188       if(toSqr = strchr(str, '-')) {
4189         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4190         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4191       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4192       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4193       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4194       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4195       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4196       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4197                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4198       DisplayMessage(partnerStatus, "");
4199         partnerBoardValid = TRUE;
4200       return;
4201     }
4202
4203     /* Modify behavior for initial board display on move listing
4204        of wild games.
4205        */
4206     switch (ics_getting_history) {
4207       case H_FALSE:
4208       case H_REQUESTED:
4209         break;
4210       case H_GOT_REQ_HEADER:
4211       case H_GOT_UNREQ_HEADER:
4212         /* This is the initial position of the current game */
4213         gamenum = ics_gamenum;
4214         moveNum = 0;            /* old ICS bug workaround */
4215         if (to_play == 'B') {
4216           startedFromSetupPosition = TRUE;
4217           blackPlaysFirst = TRUE;
4218           moveNum = 1;
4219           if (forwardMostMove == 0) forwardMostMove = 1;
4220           if (backwardMostMove == 0) backwardMostMove = 1;
4221           if (currentMove == 0) currentMove = 1;
4222         }
4223         newGameMode = gameMode;
4224         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4225         break;
4226       case H_GOT_UNWANTED_HEADER:
4227         /* This is an initial board that we don't want */
4228         return;
4229       case H_GETTING_MOVES:
4230         /* Should not happen */
4231         DisplayError(_("Error gathering move list: extra board"), 0);
4232         ics_getting_history = H_FALSE;
4233         return;
4234     }
4235
4236    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4237                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4238      /* [HGM] We seem to have switched variant unexpectedly
4239       * Try to guess new variant from board size
4240       */
4241           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4242           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4243           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4244           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4245           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4246           if(!weird) newVariant = VariantNormal;
4247           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4248           /* Get a move list just to see the header, which
4249              will tell us whether this is really bug or zh */
4250           if (ics_getting_history == H_FALSE) {
4251             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4252             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4253             SendToICS(str);
4254           }
4255     }
4256
4257     /* Take action if this is the first board of a new game, or of a
4258        different game than is currently being displayed.  */
4259     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4260         relation == RELATION_ISOLATED_BOARD) {
4261
4262         /* Forget the old game and get the history (if any) of the new one */
4263         if (gameMode != BeginningOfGame) {
4264           Reset(TRUE, TRUE);
4265         }
4266         newGame = TRUE;
4267         if (appData.autoRaiseBoard) BoardToTop();
4268         prevMove = -3;
4269         if (gamenum == -1) {
4270             newGameMode = IcsIdle;
4271         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4272                    appData.getMoveList && !reqFlag) {
4273             /* Need to get game history */
4274             ics_getting_history = H_REQUESTED;
4275             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4276             SendToICS(str);
4277         }
4278
4279         /* Initially flip the board to have black on the bottom if playing
4280            black or if the ICS flip flag is set, but let the user change
4281            it with the Flip View button. */
4282         flipView = appData.autoFlipView ?
4283           (newGameMode == IcsPlayingBlack) || ics_flip :
4284           appData.flipView;
4285
4286         /* Done with values from previous mode; copy in new ones */
4287         gameMode = newGameMode;
4288         ModeHighlight();
4289         ics_gamenum = gamenum;
4290         if (gamenum == gs_gamenum) {
4291             int klen = strlen(gs_kind);
4292             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4293             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4294             gameInfo.event = StrSave(str);
4295         } else {
4296             gameInfo.event = StrSave("ICS game");
4297         }
4298         gameInfo.site = StrSave(appData.icsHost);
4299         gameInfo.date = PGNDate();
4300         gameInfo.round = StrSave("-");
4301         gameInfo.white = StrSave(white);
4302         gameInfo.black = StrSave(black);
4303         timeControl = basetime * 60 * 1000;
4304         timeControl_2 = 0;
4305         timeIncrement = increment * 1000;
4306         movesPerSession = 0;
4307         gameInfo.timeControl = TimeControlTagValue();
4308         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4309   if (appData.debugMode) {
4310     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4311     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4312     setbuf(debugFP, NULL);
4313   }
4314
4315         gameInfo.outOfBook = NULL;
4316
4317         /* Do we have the ratings? */
4318         if (strcmp(player1Name, white) == 0 &&
4319             strcmp(player2Name, black) == 0) {
4320             if (appData.debugMode)
4321               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4322                       player1Rating, player2Rating);
4323             gameInfo.whiteRating = player1Rating;
4324             gameInfo.blackRating = player2Rating;
4325         } else if (strcmp(player2Name, white) == 0 &&
4326                    strcmp(player1Name, black) == 0) {
4327             if (appData.debugMode)
4328               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4329                       player2Rating, player1Rating);
4330             gameInfo.whiteRating = player2Rating;
4331             gameInfo.blackRating = player1Rating;
4332         }
4333         player1Name[0] = player2Name[0] = NULLCHAR;
4334
4335         /* Silence shouts if requested */
4336         if (appData.quietPlay &&
4337             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4338             SendToICS(ics_prefix);
4339             SendToICS("set shout 0\n");
4340         }
4341     }
4342
4343     /* Deal with midgame name changes */
4344     if (!newGame) {
4345         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4346             if (gameInfo.white) free(gameInfo.white);
4347             gameInfo.white = StrSave(white);
4348         }
4349         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4350             if (gameInfo.black) free(gameInfo.black);
4351             gameInfo.black = StrSave(black);
4352         }
4353     }
4354
4355     /* Throw away game result if anything actually changes in examine mode */
4356     if (gameMode == IcsExamining && !newGame) {
4357         gameInfo.result = GameUnfinished;
4358         if (gameInfo.resultDetails != NULL) {
4359             free(gameInfo.resultDetails);
4360             gameInfo.resultDetails = NULL;
4361         }
4362     }
4363
4364     /* In pausing && IcsExamining mode, we ignore boards coming
4365        in if they are in a different variation than we are. */
4366     if (pauseExamInvalid) return;
4367     if (pausing && gameMode == IcsExamining) {
4368         if (moveNum <= pauseExamForwardMostMove) {
4369             pauseExamInvalid = TRUE;
4370             forwardMostMove = pauseExamForwardMostMove;
4371             return;
4372         }
4373     }
4374
4375   if (appData.debugMode) {
4376     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4377   }
4378     /* Parse the board */
4379     for (k = 0; k < ranks; k++) {
4380       for (j = 0; j < files; j++)
4381         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4382       if(gameInfo.holdingsWidth > 1) {
4383            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4384            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4385       }
4386     }
4387     CopyBoard(boards[moveNum], board);
4388     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4389     if (moveNum == 0) {
4390         startedFromSetupPosition =
4391           !CompareBoards(board, initialPosition);
4392         if(startedFromSetupPosition)
4393             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4394     }
4395
4396     /* [HGM] Set castling rights. Take the outermost Rooks,
4397        to make it also work for FRC opening positions. Note that board12
4398        is really defective for later FRC positions, as it has no way to
4399        indicate which Rook can castle if they are on the same side of King.
4400        For the initial position we grant rights to the outermost Rooks,
4401        and remember thos rights, and we then copy them on positions
4402        later in an FRC game. This means WB might not recognize castlings with
4403        Rooks that have moved back to their original position as illegal,
4404        but in ICS mode that is not its job anyway.
4405     */
4406     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4407     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4408
4409         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4410             if(board[0][i] == WhiteRook) j = i;
4411         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4412         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4413             if(board[0][i] == WhiteRook) j = i;
4414         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4415         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4416             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4417         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4418         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4419             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4420         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4421
4422         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4423         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4424             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4425         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4426             if(board[BOARD_HEIGHT-1][k] == bKing)
4427                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4428         if(gameInfo.variant == VariantTwoKings) {
4429             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4430             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4431             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4432         }
4433     } else { int r;
4434         r = boards[moveNum][CASTLING][0] = initialRights[0];
4435         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4436         r = boards[moveNum][CASTLING][1] = initialRights[1];
4437         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4438         r = boards[moveNum][CASTLING][3] = initialRights[3];
4439         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4440         r = boards[moveNum][CASTLING][4] = initialRights[4];
4441         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4442         /* wildcastle kludge: always assume King has rights */
4443         r = boards[moveNum][CASTLING][2] = initialRights[2];
4444         r = boards[moveNum][CASTLING][5] = initialRights[5];
4445     }
4446     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4447     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4448
4449
4450     if (ics_getting_history == H_GOT_REQ_HEADER ||
4451         ics_getting_history == H_GOT_UNREQ_HEADER) {
4452         /* This was an initial position from a move list, not
4453            the current position */
4454         return;
4455     }
4456
4457     /* Update currentMove and known move number limits */
4458     newMove = newGame || moveNum > forwardMostMove;
4459
4460     if (newGame) {
4461         forwardMostMove = backwardMostMove = currentMove = moveNum;
4462         if (gameMode == IcsExamining && moveNum == 0) {
4463           /* Workaround for ICS limitation: we are not told the wild
4464              type when starting to examine a game.  But if we ask for
4465              the move list, the move list header will tell us */
4466             ics_getting_history = H_REQUESTED;
4467             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4468             SendToICS(str);
4469         }
4470     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4471                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4472 #if ZIPPY
4473         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4474         /* [HGM] applied this also to an engine that is silently watching        */
4475         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4476             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4477             gameInfo.variant == currentlyInitializedVariant) {
4478           takeback = forwardMostMove - moveNum;
4479           for (i = 0; i < takeback; i++) {
4480             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4481             SendToProgram("undo\n", &first);
4482           }
4483         }
4484 #endif
4485
4486         forwardMostMove = moveNum;
4487         if (!pausing || currentMove > forwardMostMove)
4488           currentMove = forwardMostMove;
4489     } else {
4490         /* New part of history that is not contiguous with old part */
4491         if (pausing && gameMode == IcsExamining) {
4492             pauseExamInvalid = TRUE;
4493             forwardMostMove = pauseExamForwardMostMove;
4494             return;
4495         }
4496         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4497 #if ZIPPY
4498             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4499                 // [HGM] when we will receive the move list we now request, it will be
4500                 // fed to the engine from the first move on. So if the engine is not
4501                 // in the initial position now, bring it there.
4502                 InitChessProgram(&first, 0);
4503             }
4504 #endif
4505             ics_getting_history = H_REQUESTED;
4506             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4507             SendToICS(str);
4508         }
4509         forwardMostMove = backwardMostMove = currentMove = moveNum;
4510     }
4511
4512     /* Update the clocks */
4513     if (strchr(elapsed_time, '.')) {
4514       /* Time is in ms */
4515       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4516       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4517     } else {
4518       /* Time is in seconds */
4519       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4520       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4521     }
4522
4523
4524 #if ZIPPY
4525     if (appData.zippyPlay && newGame &&
4526         gameMode != IcsObserving && gameMode != IcsIdle &&
4527         gameMode != IcsExamining)
4528       ZippyFirstBoard(moveNum, basetime, increment);
4529 #endif
4530
4531     /* Put the move on the move list, first converting
4532        to canonical algebraic form. */
4533     if (moveNum > 0) {
4534   if (appData.debugMode) {
4535     if (appData.debugMode) { int f = forwardMostMove;
4536         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4537                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4538                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4539     }
4540     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4541     fprintf(debugFP, "moveNum = %d\n", moveNum);
4542     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4543     setbuf(debugFP, NULL);
4544   }
4545         if (moveNum <= backwardMostMove) {
4546             /* We don't know what the board looked like before
4547                this move.  Punt. */
4548           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4549             strcat(parseList[moveNum - 1], " ");
4550             strcat(parseList[moveNum - 1], elapsed_time);
4551             moveList[moveNum - 1][0] = NULLCHAR;
4552         } else if (strcmp(move_str, "none") == 0) {
4553             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4554             /* Again, we don't know what the board looked like;
4555                this is really the start of the game. */
4556             parseList[moveNum - 1][0] = NULLCHAR;
4557             moveList[moveNum - 1][0] = NULLCHAR;
4558             backwardMostMove = moveNum;
4559             startedFromSetupPosition = TRUE;
4560             fromX = fromY = toX = toY = -1;
4561         } else {
4562           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4563           //                 So we parse the long-algebraic move string in stead of the SAN move
4564           int valid; char buf[MSG_SIZ], *prom;
4565
4566           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4567                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4568           // str looks something like "Q/a1-a2"; kill the slash
4569           if(str[1] == '/')
4570             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4571           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4572           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4573                 strcat(buf, prom); // long move lacks promo specification!
4574           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4575                 if(appData.debugMode)
4576                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4577                 safeStrCpy(move_str, buf, MSG_SIZ);
4578           }
4579           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4580                                 &fromX, &fromY, &toX, &toY, &promoChar)
4581                || ParseOneMove(buf, moveNum - 1, &moveType,
4582                                 &fromX, &fromY, &toX, &toY, &promoChar);
4583           // end of long SAN patch
4584           if (valid) {
4585             (void) CoordsToAlgebraic(boards[moveNum - 1],
4586                                      PosFlags(moveNum - 1),
4587                                      fromY, fromX, toY, toX, promoChar,
4588                                      parseList[moveNum-1]);
4589             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4590               case MT_NONE:
4591               case MT_STALEMATE:
4592               default:
4593                 break;
4594               case MT_CHECK:
4595                 if(gameInfo.variant != VariantShogi)
4596                     strcat(parseList[moveNum - 1], "+");
4597                 break;
4598               case MT_CHECKMATE:
4599               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4600                 strcat(parseList[moveNum - 1], "#");
4601                 break;
4602             }
4603             strcat(parseList[moveNum - 1], " ");
4604             strcat(parseList[moveNum - 1], elapsed_time);
4605             /* currentMoveString is set as a side-effect of ParseOneMove */
4606             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4607             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4608             strcat(moveList[moveNum - 1], "\n");
4609
4610             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4611                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4612               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4613                 ChessSquare old, new = boards[moveNum][k][j];
4614                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4615                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4616                   if(old == new) continue;
4617                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4618                   else if(new == WhiteWazir || new == BlackWazir) {
4619                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4620                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4621                       else boards[moveNum][k][j] = old; // preserve type of Gold
4622                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4623                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4624               }
4625           } else {
4626             /* Move from ICS was illegal!?  Punt. */
4627             if (appData.debugMode) {
4628               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4629               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4630             }
4631             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4632             strcat(parseList[moveNum - 1], " ");
4633             strcat(parseList[moveNum - 1], elapsed_time);
4634             moveList[moveNum - 1][0] = NULLCHAR;
4635             fromX = fromY = toX = toY = -1;
4636           }
4637         }
4638   if (appData.debugMode) {
4639     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4640     setbuf(debugFP, NULL);
4641   }
4642
4643 #if ZIPPY
4644         /* Send move to chess program (BEFORE animating it). */
4645         if (appData.zippyPlay && !newGame && newMove &&
4646            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4647
4648             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4649                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4650                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4651                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4652                             move_str);
4653                     DisplayError(str, 0);
4654                 } else {
4655                     if (first.sendTime) {
4656                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4657                     }
4658                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4659                     if (firstMove && !bookHit) {
4660                         firstMove = FALSE;
4661                         if (first.useColors) {
4662                           SendToProgram(gameMode == IcsPlayingWhite ?
4663                                         "white\ngo\n" :
4664                                         "black\ngo\n", &first);
4665                         } else {
4666                           SendToProgram("go\n", &first);
4667                         }
4668                         first.maybeThinking = TRUE;
4669                     }
4670                 }
4671             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4672               if (moveList[moveNum - 1][0] == NULLCHAR) {
4673                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4674                 DisplayError(str, 0);
4675               } else {
4676                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4677                 SendMoveToProgram(moveNum - 1, &first);
4678               }
4679             }
4680         }
4681 #endif
4682     }
4683
4684     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4685         /* If move comes from a remote source, animate it.  If it
4686            isn't remote, it will have already been animated. */
4687         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4688             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4689         }
4690         if (!pausing && appData.highlightLastMove) {
4691             SetHighlights(fromX, fromY, toX, toY);
4692         }
4693     }
4694
4695     /* Start the clocks */
4696     whiteFlag = blackFlag = FALSE;
4697     appData.clockMode = !(basetime == 0 && increment == 0);
4698     if (ticking == 0) {
4699       ics_clock_paused = TRUE;
4700       StopClocks();
4701     } else if (ticking == 1) {
4702       ics_clock_paused = FALSE;
4703     }
4704     if (gameMode == IcsIdle ||
4705         relation == RELATION_OBSERVING_STATIC ||
4706         relation == RELATION_EXAMINING ||
4707         ics_clock_paused)
4708       DisplayBothClocks();
4709     else
4710       StartClocks();
4711
4712     /* Display opponents and material strengths */
4713     if (gameInfo.variant != VariantBughouse &&
4714         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4715         if (tinyLayout || smallLayout) {
4716             if(gameInfo.variant == VariantNormal)
4717               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4718                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4719                     basetime, increment);
4720             else
4721               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4722                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4723                     basetime, increment, (int) gameInfo.variant);
4724         } else {
4725             if(gameInfo.variant == VariantNormal)
4726               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4727                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4728                     basetime, increment);
4729             else
4730               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4731                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4732                     basetime, increment, VariantName(gameInfo.variant));
4733         }
4734         DisplayTitle(str);
4735   if (appData.debugMode) {
4736     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4737   }
4738     }
4739
4740
4741     /* Display the board */
4742     if (!pausing && !appData.noGUI) {
4743
4744       if (appData.premove)
4745           if (!gotPremove ||
4746              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4747              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4748               ClearPremoveHighlights();
4749
4750       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4751         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4752       DrawPosition(j, boards[currentMove]);
4753
4754       DisplayMove(moveNum - 1);
4755       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4756             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4757               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4758         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4759       }
4760     }
4761
4762     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4763 #if ZIPPY
4764     if(bookHit) { // [HGM] book: simulate book reply
4765         static char bookMove[MSG_SIZ]; // a bit generous?
4766
4767         programStats.nodes = programStats.depth = programStats.time =
4768         programStats.score = programStats.got_only_move = 0;
4769         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4770
4771         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4772         strcat(bookMove, bookHit);
4773         HandleMachineMove(bookMove, &first);
4774     }
4775 #endif
4776 }
4777
4778 void
4779 GetMoveListEvent()
4780 {
4781     char buf[MSG_SIZ];
4782     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4783         ics_getting_history = H_REQUESTED;
4784         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4785         SendToICS(buf);
4786     }
4787 }
4788
4789 void
4790 AnalysisPeriodicEvent(force)
4791      int force;
4792 {
4793     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4794          && !force) || !appData.periodicUpdates)
4795       return;
4796
4797     /* Send . command to Crafty to collect stats */
4798     SendToProgram(".\n", &first);
4799
4800     /* Don't send another until we get a response (this makes
4801        us stop sending to old Crafty's which don't understand
4802        the "." command (sending illegal cmds resets node count & time,
4803        which looks bad)) */
4804     programStats.ok_to_send = 0;
4805 }
4806
4807 void ics_update_width(new_width)
4808         int new_width;
4809 {
4810         ics_printf("set width %d\n", new_width);
4811 }
4812
4813 void
4814 SendMoveToProgram(moveNum, cps)
4815      int moveNum;
4816      ChessProgramState *cps;
4817 {
4818     char buf[MSG_SIZ];
4819
4820     if (cps->useUsermove) {
4821       SendToProgram("usermove ", cps);
4822     }
4823     if (cps->useSAN) {
4824       char *space;
4825       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4826         int len = space - parseList[moveNum];
4827         memcpy(buf, parseList[moveNum], len);
4828         buf[len++] = '\n';
4829         buf[len] = NULLCHAR;
4830       } else {
4831         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4832       }
4833       SendToProgram(buf, cps);
4834     } else {
4835       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4836         AlphaRank(moveList[moveNum], 4);
4837         SendToProgram(moveList[moveNum], cps);
4838         AlphaRank(moveList[moveNum], 4); // and back
4839       } else
4840       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4841        * the engine. It would be nice to have a better way to identify castle
4842        * moves here. */
4843       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4844                                                                          && cps->useOOCastle) {
4845         int fromX = moveList[moveNum][0] - AAA;
4846         int fromY = moveList[moveNum][1] - ONE;
4847         int toX = moveList[moveNum][2] - AAA;
4848         int toY = moveList[moveNum][3] - ONE;
4849         if((boards[moveNum][fromY][fromX] == WhiteKing
4850             && boards[moveNum][toY][toX] == WhiteRook)
4851            || (boards[moveNum][fromY][fromX] == BlackKing
4852                && boards[moveNum][toY][toX] == BlackRook)) {
4853           if(toX > fromX) SendToProgram("O-O\n", cps);
4854           else SendToProgram("O-O-O\n", cps);
4855         }
4856         else SendToProgram(moveList[moveNum], cps);
4857       }
4858       else SendToProgram(moveList[moveNum], cps);
4859       /* End of additions by Tord */
4860     }
4861
4862     /* [HGM] setting up the opening has brought engine in force mode! */
4863     /*       Send 'go' if we are in a mode where machine should play. */
4864     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4865         (gameMode == TwoMachinesPlay   ||
4866 #if ZIPPY
4867          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4868 #endif
4869          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4870         SendToProgram("go\n", cps);
4871   if (appData.debugMode) {
4872     fprintf(debugFP, "(extra)\n");
4873   }
4874     }
4875     setboardSpoiledMachineBlack = 0;
4876 }
4877
4878 void
4879 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4880      ChessMove moveType;
4881      int fromX, fromY, toX, toY;
4882      char promoChar;
4883 {
4884     char user_move[MSG_SIZ];
4885
4886     switch (moveType) {
4887       default:
4888         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4889                 (int)moveType, fromX, fromY, toX, toY);
4890         DisplayError(user_move + strlen("say "), 0);
4891         break;
4892       case WhiteKingSideCastle:
4893       case BlackKingSideCastle:
4894       case WhiteQueenSideCastleWild:
4895       case BlackQueenSideCastleWild:
4896       /* PUSH Fabien */
4897       case WhiteHSideCastleFR:
4898       case BlackHSideCastleFR:
4899       /* POP Fabien */
4900         snprintf(user_move, MSG_SIZ, "o-o\n");
4901         break;
4902       case WhiteQueenSideCastle:
4903       case BlackQueenSideCastle:
4904       case WhiteKingSideCastleWild:
4905       case BlackKingSideCastleWild:
4906       /* PUSH Fabien */
4907       case WhiteASideCastleFR:
4908       case BlackASideCastleFR:
4909       /* POP Fabien */
4910         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4911         break;
4912       case WhiteNonPromotion:
4913       case BlackNonPromotion:
4914         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4915         break;
4916       case WhitePromotion:
4917       case BlackPromotion:
4918         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4919           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4920                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4921                 PieceToChar(WhiteFerz));
4922         else if(gameInfo.variant == VariantGreat)
4923           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4924                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4925                 PieceToChar(WhiteMan));
4926         else
4927           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4928                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4929                 promoChar);
4930         break;
4931       case WhiteDrop:
4932       case BlackDrop:
4933       drop:
4934         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4935                  ToUpper(PieceToChar((ChessSquare) fromX)),
4936                  AAA + toX, ONE + toY);
4937         break;
4938       case IllegalMove:  /* could be a variant we don't quite understand */
4939         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4940       case NormalMove:
4941       case WhiteCapturesEnPassant:
4942       case BlackCapturesEnPassant:
4943         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4944                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4945         break;
4946     }
4947     SendToICS(user_move);
4948     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4949         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4950 }
4951
4952 void
4953 UploadGameEvent()
4954 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4955     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4956     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4957     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4958         DisplayError("You cannot do this while you are playing or observing", 0);
4959         return;
4960     }
4961     if(gameMode != IcsExamining) { // is this ever not the case?
4962         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4963
4964         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4965           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4966         } else { // on FICS we must first go to general examine mode
4967           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4968         }
4969         if(gameInfo.variant != VariantNormal) {
4970             // try figure out wild number, as xboard names are not always valid on ICS
4971             for(i=1; i<=36; i++) {
4972               snprintf(buf, MSG_SIZ, "wild/%d", i);
4973                 if(StringToVariant(buf) == gameInfo.variant) break;
4974             }
4975             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4976             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4977             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4978         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4979         SendToICS(ics_prefix);
4980         SendToICS(buf);
4981         if(startedFromSetupPosition || backwardMostMove != 0) {
4982           fen = PositionToFEN(backwardMostMove, NULL);
4983           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4984             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4985             SendToICS(buf);
4986           } else { // FICS: everything has to set by separate bsetup commands
4987             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4988             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4989             SendToICS(buf);
4990             if(!WhiteOnMove(backwardMostMove)) {
4991                 SendToICS("bsetup tomove black\n");
4992             }
4993             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4994             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4995             SendToICS(buf);
4996             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4997             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4998             SendToICS(buf);
4999             i = boards[backwardMostMove][EP_STATUS];
5000             if(i >= 0) { // set e.p.
5001               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5002                 SendToICS(buf);
5003             }
5004             bsetup++;
5005           }
5006         }
5007       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5008             SendToICS("bsetup done\n"); // switch to normal examining.
5009     }
5010     for(i = backwardMostMove; i<last; i++) {
5011         char buf[20];
5012         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5013         SendToICS(buf);
5014     }
5015     SendToICS(ics_prefix);
5016     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5017 }
5018
5019 void
5020 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5021      int rf, ff, rt, ft;
5022      char promoChar;
5023      char move[7];
5024 {
5025     if (rf == DROP_RANK) {
5026       sprintf(move, "%c@%c%c\n",
5027                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5028     } else {
5029         if (promoChar == 'x' || promoChar == NULLCHAR) {
5030           sprintf(move, "%c%c%c%c\n",
5031                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5032         } else {
5033             sprintf(move, "%c%c%c%c%c\n",
5034                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5035         }
5036     }
5037 }
5038
5039 void
5040 ProcessICSInitScript(f)
5041      FILE *f;
5042 {
5043     char buf[MSG_SIZ];
5044
5045     while (fgets(buf, MSG_SIZ, f)) {
5046         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5047     }
5048
5049     fclose(f);
5050 }
5051
5052
5053 static int lastX, lastY, selectFlag, dragging;
5054
5055 void
5056 Sweep(int step)
5057 {
5058     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5059     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5060     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5061     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5062     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5063     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5064     do {
5065         promoSweep -= step;
5066         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5067         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5068         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5069         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5070         if(!step) step = 1;
5071     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5072             appData.testLegality && (promoSweep == king ||
5073             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5074     ChangeDragPiece(promoSweep);
5075 }
5076
5077 int PromoScroll(int x, int y)
5078 {
5079   int step = 0;
5080
5081   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5082   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5083   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5084   if(!step) return FALSE;
5085   lastX = x; lastY = y;
5086   if((promoSweep < BlackPawn) == flipView) step = -step;
5087   if(step > 0) selectFlag = 1;
5088   if(!selectFlag) Sweep(step);
5089   return FALSE;
5090 }
5091
5092 void
5093 NextPiece(int step)
5094 {
5095     ChessSquare piece = boards[currentMove][toY][toX];
5096     do {
5097         pieceSweep -= step;
5098         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5099         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5100         if(!step) step = -1;
5101     } while(PieceToChar(pieceSweep) == '.');
5102     boards[currentMove][toY][toX] = pieceSweep;
5103     DrawPosition(FALSE, boards[currentMove]);
5104     boards[currentMove][toY][toX] = piece;
5105 }
5106 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5107 void
5108 AlphaRank(char *move, int n)
5109 {
5110 //    char *p = move, c; int x, y;
5111
5112     if (appData.debugMode) {
5113         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5114     }
5115
5116     if(move[1]=='*' &&
5117        move[2]>='0' && move[2]<='9' &&
5118        move[3]>='a' && move[3]<='x'    ) {
5119         move[1] = '@';
5120         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5121         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5122     } else
5123     if(move[0]>='0' && move[0]<='9' &&
5124        move[1]>='a' && move[1]<='x' &&
5125        move[2]>='0' && move[2]<='9' &&
5126        move[3]>='a' && move[3]<='x'    ) {
5127         /* input move, Shogi -> normal */
5128         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5129         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5130         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5131         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5132     } else
5133     if(move[1]=='@' &&
5134        move[3]>='0' && move[3]<='9' &&
5135        move[2]>='a' && move[2]<='x'    ) {
5136         move[1] = '*';
5137         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5138         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5139     } else
5140     if(
5141        move[0]>='a' && move[0]<='x' &&
5142        move[3]>='0' && move[3]<='9' &&
5143        move[2]>='a' && move[2]<='x'    ) {
5144          /* output move, normal -> Shogi */
5145         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5146         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5147         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5148         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5149         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5150     }
5151     if (appData.debugMode) {
5152         fprintf(debugFP, "   out = '%s'\n", move);
5153     }
5154 }
5155
5156 char yy_textstr[8000];
5157
5158 /* Parser for moves from gnuchess, ICS, or user typein box */
5159 Boolean
5160 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5161      char *move;
5162      int moveNum;
5163      ChessMove *moveType;
5164      int *fromX, *fromY, *toX, *toY;
5165      char *promoChar;
5166 {
5167     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5168
5169     switch (*moveType) {
5170       case WhitePromotion:
5171       case BlackPromotion:
5172       case WhiteNonPromotion:
5173       case BlackNonPromotion:
5174       case NormalMove:
5175       case WhiteCapturesEnPassant:
5176       case BlackCapturesEnPassant:
5177       case WhiteKingSideCastle:
5178       case WhiteQueenSideCastle:
5179       case BlackKingSideCastle:
5180       case BlackQueenSideCastle:
5181       case WhiteKingSideCastleWild:
5182       case WhiteQueenSideCastleWild:
5183       case BlackKingSideCastleWild:
5184       case BlackQueenSideCastleWild:
5185       /* Code added by Tord: */
5186       case WhiteHSideCastleFR:
5187       case WhiteASideCastleFR:
5188       case BlackHSideCastleFR:
5189       case BlackASideCastleFR:
5190       /* End of code added by Tord */
5191       case IllegalMove:         /* bug or odd chess variant */
5192         *fromX = currentMoveString[0] - AAA;
5193         *fromY = currentMoveString[1] - ONE;
5194         *toX = currentMoveString[2] - AAA;
5195         *toY = currentMoveString[3] - ONE;
5196         *promoChar = currentMoveString[4];
5197         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5198             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5199     if (appData.debugMode) {
5200         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5201     }
5202             *fromX = *fromY = *toX = *toY = 0;
5203             return FALSE;
5204         }
5205         if (appData.testLegality) {
5206           return (*moveType != IllegalMove);
5207         } else {
5208           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5209                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5210         }
5211
5212       case WhiteDrop:
5213       case BlackDrop:
5214         *fromX = *moveType == WhiteDrop ?
5215           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5216           (int) CharToPiece(ToLower(currentMoveString[0]));
5217         *fromY = DROP_RANK;
5218         *toX = currentMoveString[2] - AAA;
5219         *toY = currentMoveString[3] - ONE;
5220         *promoChar = NULLCHAR;
5221         return TRUE;
5222
5223       case AmbiguousMove:
5224       case ImpossibleMove:
5225       case EndOfFile:
5226       case ElapsedTime:
5227       case Comment:
5228       case PGNTag:
5229       case NAG:
5230       case WhiteWins:
5231       case BlackWins:
5232       case GameIsDrawn:
5233       default:
5234     if (appData.debugMode) {
5235         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5236     }
5237         /* bug? */
5238         *fromX = *fromY = *toX = *toY = 0;
5239         *promoChar = NULLCHAR;
5240         return FALSE;
5241     }
5242 }
5243
5244 Boolean pushed = FALSE;
5245
5246 void
5247 ParsePV(char *pv, Boolean storeComments)
5248 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5249   int fromX, fromY, toX, toY; char promoChar;
5250   ChessMove moveType;
5251   Boolean valid;
5252   int nr = 0;
5253
5254   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5255     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5256     pushed = TRUE;
5257   }
5258   endPV = forwardMostMove;
5259   do {
5260     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5261     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5262     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5263 if(appData.debugMode){
5264 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5265 }
5266     if(!valid && nr == 0 &&
5267        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5268         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5269         // Hande case where played move is different from leading PV move
5270         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5271         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5272         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5273         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5274           endPV += 2; // if position different, keep this
5275           moveList[endPV-1][0] = fromX + AAA;
5276           moveList[endPV-1][1] = fromY + ONE;
5277           moveList[endPV-1][2] = toX + AAA;
5278           moveList[endPV-1][3] = toY + ONE;
5279           parseList[endPV-1][0] = NULLCHAR;
5280           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5281         }
5282       }
5283     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5284     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5285     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5286     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5287         valid++; // allow comments in PV
5288         continue;
5289     }
5290     nr++;
5291     if(endPV+1 > framePtr) break; // no space, truncate
5292     if(!valid) break;
5293     endPV++;
5294     CopyBoard(boards[endPV], boards[endPV-1]);
5295     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5296     moveList[endPV-1][0] = fromX + AAA;
5297     moveList[endPV-1][1] = fromY + ONE;
5298     moveList[endPV-1][2] = toX + AAA;
5299     moveList[endPV-1][3] = toY + ONE;
5300     moveList[endPV-1][4] = promoChar;
5301     moveList[endPV-1][5] = NULLCHAR;
5302     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5303     if(storeComments)
5304         CoordsToAlgebraic(boards[endPV - 1],
5305                              PosFlags(endPV - 1),
5306                              fromY, fromX, toY, toX, promoChar,
5307                              parseList[endPV - 1]);
5308     else
5309         parseList[endPV-1][0] = NULLCHAR;
5310   } while(valid);
5311   currentMove = endPV;
5312   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5313   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5314                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5315   DrawPosition(TRUE, boards[currentMove]);
5316 }
5317
5318 Boolean
5319 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5320 {
5321         int startPV;
5322         char *p;
5323
5324         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5325         lastX = x; lastY = y;
5326         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5327         startPV = index;
5328         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5329         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5330         index = startPV;
5331         do{ while(buf[index] && buf[index] != '\n') index++;
5332         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5333         buf[index] = 0;
5334         ParsePV(buf+startPV, FALSE);
5335         *start = startPV; *end = index-1;
5336         return TRUE;
5337 }
5338
5339 Boolean
5340 LoadPV(int x, int y)
5341 { // called on right mouse click to load PV
5342   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5343   lastX = x; lastY = y;
5344   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5345   return TRUE;
5346 }
5347
5348 void
5349 UnLoadPV()
5350 {
5351   if(endPV < 0) return;
5352   endPV = -1;
5353   currentMove = forwardMostMove;
5354   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5355   ClearPremoveHighlights();
5356   DrawPosition(TRUE, boards[currentMove]);
5357 }
5358
5359 void
5360 MovePV(int x, int y, int h)
5361 { // step through PV based on mouse coordinates (called on mouse move)
5362   int margin = h>>3, step = 0;
5363
5364   // we must somehow check if right button is still down (might be released off board!)
5365   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5366   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5367   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5368   if(!step) return;
5369   lastX = x; lastY = y;
5370
5371   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5372   if(endPV < 0) return;
5373   if(y < margin) step = 1; else
5374   if(y > h - margin) step = -1;
5375   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5376   currentMove += step;
5377   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5378   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5379                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5380   DrawPosition(FALSE, boards[currentMove]);
5381 }
5382
5383
5384 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5385 // All positions will have equal probability, but the current method will not provide a unique
5386 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5387 #define DARK 1
5388 #define LITE 2
5389 #define ANY 3
5390
5391 int squaresLeft[4];
5392 int piecesLeft[(int)BlackPawn];
5393 int seed, nrOfShuffles;
5394
5395 void GetPositionNumber()
5396 {       // sets global variable seed
5397         int i;
5398
5399         seed = appData.defaultFrcPosition;
5400         if(seed < 0) { // randomize based on time for negative FRC position numbers
5401                 for(i=0; i<50; i++) seed += random();
5402                 seed = random() ^ random() >> 8 ^ random() << 8;
5403                 if(seed<0) seed = -seed;
5404         }
5405 }
5406
5407 int put(Board board, int pieceType, int rank, int n, int shade)
5408 // put the piece on the (n-1)-th empty squares of the given shade
5409 {
5410         int i;
5411
5412         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5413                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5414                         board[rank][i] = (ChessSquare) pieceType;
5415                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5416                         squaresLeft[ANY]--;
5417                         piecesLeft[pieceType]--;
5418                         return i;
5419                 }
5420         }
5421         return -1;
5422 }
5423
5424
5425 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5426 // calculate where the next piece goes, (any empty square), and put it there
5427 {
5428         int i;
5429
5430         i = seed % squaresLeft[shade];
5431         nrOfShuffles *= squaresLeft[shade];
5432         seed /= squaresLeft[shade];
5433         put(board, pieceType, rank, i, shade);
5434 }
5435
5436 void AddTwoPieces(Board board, int pieceType, int rank)
5437 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5438 {
5439         int i, n=squaresLeft[ANY], j=n-1, k;
5440
5441         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5442         i = seed % k;  // pick one
5443         nrOfShuffles *= k;
5444         seed /= k;
5445         while(i >= j) i -= j--;
5446         j = n - 1 - j; i += j;
5447         put(board, pieceType, rank, j, ANY);
5448         put(board, pieceType, rank, i, ANY);
5449 }
5450
5451 void SetUpShuffle(Board board, int number)
5452 {
5453         int i, p, first=1;
5454
5455         GetPositionNumber(); nrOfShuffles = 1;
5456
5457         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5458         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5459         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5460
5461         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5462
5463         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5464             p = (int) board[0][i];
5465             if(p < (int) BlackPawn) piecesLeft[p] ++;
5466             board[0][i] = EmptySquare;
5467         }
5468
5469         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5470             // shuffles restricted to allow normal castling put KRR first
5471             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5472                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5473             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5474                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5475             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5476                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5477             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5478                 put(board, WhiteRook, 0, 0, ANY);
5479             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5480         }
5481
5482         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5483             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5484             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5485                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5486                 while(piecesLeft[p] >= 2) {
5487                     AddOnePiece(board, p, 0, LITE);
5488                     AddOnePiece(board, p, 0, DARK);
5489                 }
5490                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5491             }
5492
5493         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5494             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5495             // but we leave King and Rooks for last, to possibly obey FRC restriction
5496             if(p == (int)WhiteRook) continue;
5497             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5498             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5499         }
5500
5501         // now everything is placed, except perhaps King (Unicorn) and Rooks
5502
5503         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5504             // Last King gets castling rights
5505             while(piecesLeft[(int)WhiteUnicorn]) {
5506                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5507                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5508             }
5509
5510             while(piecesLeft[(int)WhiteKing]) {
5511                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5512                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5513             }
5514
5515
5516         } else {
5517             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5518             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5519         }
5520
5521         // Only Rooks can be left; simply place them all
5522         while(piecesLeft[(int)WhiteRook]) {
5523                 i = put(board, WhiteRook, 0, 0, ANY);
5524                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5525                         if(first) {
5526                                 first=0;
5527                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5528                         }
5529                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5530                 }
5531         }
5532         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5533             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5534         }
5535
5536         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5537 }
5538
5539 int SetCharTable( char *table, const char * map )
5540 /* [HGM] moved here from winboard.c because of its general usefulness */
5541 /*       Basically a safe strcpy that uses the last character as King */
5542 {
5543     int result = FALSE; int NrPieces;
5544
5545     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5546                     && NrPieces >= 12 && !(NrPieces&1)) {
5547         int i; /* [HGM] Accept even length from 12 to 34 */
5548
5549         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5550         for( i=0; i<NrPieces/2-1; i++ ) {
5551             table[i] = map[i];
5552             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5553         }
5554         table[(int) WhiteKing]  = map[NrPieces/2-1];
5555         table[(int) BlackKing]  = map[NrPieces-1];
5556
5557         result = TRUE;
5558     }
5559
5560     return result;
5561 }
5562
5563 void Prelude(Board board)
5564 {       // [HGM] superchess: random selection of exo-pieces
5565         int i, j, k; ChessSquare p;
5566         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5567
5568         GetPositionNumber(); // use FRC position number
5569
5570         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5571             SetCharTable(pieceToChar, appData.pieceToCharTable);
5572             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5573                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5574         }
5575
5576         j = seed%4;                 seed /= 4;
5577         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5578         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5579         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5580         j = seed%3 + (seed%3 >= j); seed /= 3;
5581         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5582         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5583         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5584         j = seed%3;                 seed /= 3;
5585         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5586         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5587         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5588         j = seed%2 + (seed%2 >= j); seed /= 2;
5589         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5590         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5591         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5592         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5593         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5594         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5595         put(board, exoPieces[0],    0, 0, ANY);
5596         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5597 }
5598
5599 void
5600 InitPosition(redraw)
5601      int redraw;
5602 {
5603     ChessSquare (* pieces)[BOARD_FILES];
5604     int i, j, pawnRow, overrule,
5605     oldx = gameInfo.boardWidth,
5606     oldy = gameInfo.boardHeight,
5607     oldh = gameInfo.holdingsWidth;
5608     static int oldv;
5609
5610     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5611
5612     /* [AS] Initialize pv info list [HGM] and game status */
5613     {
5614         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5615             pvInfoList[i].depth = 0;
5616             boards[i][EP_STATUS] = EP_NONE;
5617             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5618         }
5619
5620         initialRulePlies = 0; /* 50-move counter start */
5621
5622         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5623         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5624     }
5625
5626
5627     /* [HGM] logic here is completely changed. In stead of full positions */
5628     /* the initialized data only consist of the two backranks. The switch */
5629     /* selects which one we will use, which is than copied to the Board   */
5630     /* initialPosition, which for the rest is initialized by Pawns and    */
5631     /* empty squares. This initial position is then copied to boards[0],  */
5632     /* possibly after shuffling, so that it remains available.            */
5633
5634     gameInfo.holdingsWidth = 0; /* default board sizes */
5635     gameInfo.boardWidth    = 8;
5636     gameInfo.boardHeight   = 8;
5637     gameInfo.holdingsSize  = 0;
5638     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5639     for(i=0; i<BOARD_FILES-2; i++)
5640       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5641     initialPosition[EP_STATUS] = EP_NONE;
5642     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5643     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5644          SetCharTable(pieceNickName, appData.pieceNickNames);
5645     else SetCharTable(pieceNickName, "............");
5646     pieces = FIDEArray;
5647
5648     switch (gameInfo.variant) {
5649     case VariantFischeRandom:
5650       shuffleOpenings = TRUE;
5651     default:
5652       break;
5653     case VariantShatranj:
5654       pieces = ShatranjArray;
5655       nrCastlingRights = 0;
5656       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5657       break;
5658     case VariantMakruk:
5659       pieces = makrukArray;
5660       nrCastlingRights = 0;
5661       startedFromSetupPosition = TRUE;
5662       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5663       break;
5664     case VariantTwoKings:
5665       pieces = twoKingsArray;
5666       break;
5667     case VariantCapaRandom:
5668       shuffleOpenings = TRUE;
5669     case VariantCapablanca:
5670       pieces = CapablancaArray;
5671       gameInfo.boardWidth = 10;
5672       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5673       break;
5674     case VariantGothic:
5675       pieces = GothicArray;
5676       gameInfo.boardWidth = 10;
5677       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5678       break;
5679     case VariantSChess:
5680       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5681       gameInfo.holdingsSize = 7;
5682       break;
5683     case VariantJanus:
5684       pieces = JanusArray;
5685       gameInfo.boardWidth = 10;
5686       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5687       nrCastlingRights = 6;
5688         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5689         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5690         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5691         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5692         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5693         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5694       break;
5695     case VariantFalcon:
5696       pieces = FalconArray;
5697       gameInfo.boardWidth = 10;
5698       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5699       break;
5700     case VariantXiangqi:
5701       pieces = XiangqiArray;
5702       gameInfo.boardWidth  = 9;
5703       gameInfo.boardHeight = 10;
5704       nrCastlingRights = 0;
5705       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5706       break;
5707     case VariantShogi:
5708       pieces = ShogiArray;
5709       gameInfo.boardWidth  = 9;
5710       gameInfo.boardHeight = 9;
5711       gameInfo.holdingsSize = 7;
5712       nrCastlingRights = 0;
5713       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5714       break;
5715     case VariantCourier:
5716       pieces = CourierArray;
5717       gameInfo.boardWidth  = 12;
5718       nrCastlingRights = 0;
5719       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5720       break;
5721     case VariantKnightmate:
5722       pieces = KnightmateArray;
5723       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5724       break;
5725     case VariantSpartan:
5726       pieces = SpartanArray;
5727       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5728       break;
5729     case VariantFairy:
5730       pieces = fairyArray;
5731       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5732       break;
5733     case VariantGreat:
5734       pieces = GreatArray;
5735       gameInfo.boardWidth = 10;
5736       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5737       gameInfo.holdingsSize = 8;
5738       break;
5739     case VariantSuper:
5740       pieces = FIDEArray;
5741       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5742       gameInfo.holdingsSize = 8;
5743       startedFromSetupPosition = TRUE;
5744       break;
5745     case VariantCrazyhouse:
5746     case VariantBughouse:
5747       pieces = FIDEArray;
5748       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5749       gameInfo.holdingsSize = 5;
5750       break;
5751     case VariantWildCastle:
5752       pieces = FIDEArray;
5753       /* !!?shuffle with kings guaranteed to be on d or e file */
5754       shuffleOpenings = 1;
5755       break;
5756     case VariantNoCastle:
5757       pieces = FIDEArray;
5758       nrCastlingRights = 0;
5759       /* !!?unconstrained back-rank shuffle */
5760       shuffleOpenings = 1;
5761       break;
5762     }
5763
5764     overrule = 0;
5765     if(appData.NrFiles >= 0) {
5766         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5767         gameInfo.boardWidth = appData.NrFiles;
5768     }
5769     if(appData.NrRanks >= 0) {
5770         gameInfo.boardHeight = appData.NrRanks;
5771     }
5772     if(appData.holdingsSize >= 0) {
5773         i = appData.holdingsSize;
5774         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5775         gameInfo.holdingsSize = i;
5776     }
5777     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5778     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5779         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5780
5781     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5782     if(pawnRow < 1) pawnRow = 1;
5783     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5784
5785     /* User pieceToChar list overrules defaults */
5786     if(appData.pieceToCharTable != NULL)
5787         SetCharTable(pieceToChar, appData.pieceToCharTable);
5788
5789     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5790
5791         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5792             s = (ChessSquare) 0; /* account holding counts in guard band */
5793         for( i=0; i<BOARD_HEIGHT; i++ )
5794             initialPosition[i][j] = s;
5795
5796         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5797         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5798         initialPosition[pawnRow][j] = WhitePawn;
5799         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5800         if(gameInfo.variant == VariantXiangqi) {
5801             if(j&1) {
5802                 initialPosition[pawnRow][j] =
5803                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5804                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5805                    initialPosition[2][j] = WhiteCannon;
5806                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5807                 }
5808             }
5809         }
5810         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5811     }
5812     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5813
5814             j=BOARD_LEFT+1;
5815             initialPosition[1][j] = WhiteBishop;
5816             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5817             j=BOARD_RGHT-2;
5818             initialPosition[1][j] = WhiteRook;
5819             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5820     }
5821
5822     if( nrCastlingRights == -1) {
5823         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5824         /*       This sets default castling rights from none to normal corners   */
5825         /* Variants with other castling rights must set them themselves above    */
5826         nrCastlingRights = 6;
5827
5828         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5829         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5830         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5831         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5832         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5833         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5834      }
5835
5836      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5837      if(gameInfo.variant == VariantGreat) { // promotion commoners
5838         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5839         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5840         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5841         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5842      }
5843      if( gameInfo.variant == VariantSChess ) {
5844       initialPosition[1][0] = BlackMarshall;
5845       initialPosition[2][0] = BlackAngel;
5846       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5847       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5848       initialPosition[1][1] = initialPosition[2][1] = 
5849       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5850      }
5851   if (appData.debugMode) {
5852     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5853   }
5854     if(shuffleOpenings) {
5855         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5856         startedFromSetupPosition = TRUE;
5857     }
5858     if(startedFromPositionFile) {
5859       /* [HGM] loadPos: use PositionFile for every new game */
5860       CopyBoard(initialPosition, filePosition);
5861       for(i=0; i<nrCastlingRights; i++)
5862           initialRights[i] = filePosition[CASTLING][i];
5863       startedFromSetupPosition = TRUE;
5864     }
5865
5866     CopyBoard(boards[0], initialPosition);
5867
5868     if(oldx != gameInfo.boardWidth ||
5869        oldy != gameInfo.boardHeight ||
5870        oldv != gameInfo.variant ||
5871        oldh != gameInfo.holdingsWidth
5872                                          )
5873             InitDrawingSizes(-2 ,0);
5874
5875     oldv = gameInfo.variant;
5876     if (redraw)
5877       DrawPosition(TRUE, boards[currentMove]);
5878 }
5879
5880 void
5881 SendBoard(cps, moveNum)
5882      ChessProgramState *cps;
5883      int moveNum;
5884 {
5885     char message[MSG_SIZ];
5886
5887     if (cps->useSetboard) {
5888       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5889       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5890       SendToProgram(message, cps);
5891       free(fen);
5892
5893     } else {
5894       ChessSquare *bp;
5895       int i, j;
5896       /* Kludge to set black to move, avoiding the troublesome and now
5897        * deprecated "black" command.
5898        */
5899       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5900         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5901
5902       SendToProgram("edit\n", cps);
5903       SendToProgram("#\n", cps);
5904       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5905         bp = &boards[moveNum][i][BOARD_LEFT];
5906         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5907           if ((int) *bp < (int) BlackPawn) {
5908             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5909                     AAA + j, ONE + i);
5910             if(message[0] == '+' || message[0] == '~') {
5911               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5912                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5913                         AAA + j, ONE + i);
5914             }
5915             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5916                 message[1] = BOARD_RGHT   - 1 - j + '1';
5917                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5918             }
5919             SendToProgram(message, cps);
5920           }
5921         }
5922       }
5923
5924       SendToProgram("c\n", cps);
5925       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5926         bp = &boards[moveNum][i][BOARD_LEFT];
5927         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5928           if (((int) *bp != (int) EmptySquare)
5929               && ((int) *bp >= (int) BlackPawn)) {
5930             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5931                     AAA + j, ONE + i);
5932             if(message[0] == '+' || message[0] == '~') {
5933               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5934                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5935                         AAA + j, ONE + i);
5936             }
5937             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5938                 message[1] = BOARD_RGHT   - 1 - j + '1';
5939                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5940             }
5941             SendToProgram(message, cps);
5942           }
5943         }
5944       }
5945
5946       SendToProgram(".\n", cps);
5947     }
5948     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5949 }
5950
5951 ChessSquare
5952 DefaultPromoChoice(int white)
5953 {
5954     ChessSquare result;
5955     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5956         result = WhiteFerz; // no choice
5957     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5958         result= WhiteKing; // in Suicide Q is the last thing we want
5959     else if(gameInfo.variant == VariantSpartan)
5960         result = white ? WhiteQueen : WhiteAngel;
5961     else result = WhiteQueen;
5962     if(!white) result = WHITE_TO_BLACK result;
5963     return result;
5964 }
5965
5966 static int autoQueen; // [HGM] oneclick
5967
5968 int
5969 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5970 {
5971     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5972     /* [HGM] add Shogi promotions */
5973     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5974     ChessSquare piece;
5975     ChessMove moveType;
5976     Boolean premove;
5977
5978     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5979     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5980
5981     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5982       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5983         return FALSE;
5984
5985     piece = boards[currentMove][fromY][fromX];
5986     if(gameInfo.variant == VariantShogi) {
5987         promotionZoneSize = BOARD_HEIGHT/3;
5988         highestPromotingPiece = (int)WhiteFerz;
5989     } else if(gameInfo.variant == VariantMakruk) {
5990         promotionZoneSize = 3;
5991     }
5992
5993     // Treat Lance as Pawn when it is not representing Amazon
5994     if(gameInfo.variant != VariantSuper) {
5995         if(piece == WhiteLance) piece = WhitePawn; else
5996         if(piece == BlackLance) piece = BlackPawn;
5997     }
5998
5999     // next weed out all moves that do not touch the promotion zone at all
6000     if((int)piece >= BlackPawn) {
6001         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6002              return FALSE;
6003         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6004     } else {
6005         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6006            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6007     }
6008
6009     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6010
6011     // weed out mandatory Shogi promotions
6012     if(gameInfo.variant == VariantShogi) {
6013         if(piece >= BlackPawn) {
6014             if(toY == 0 && piece == BlackPawn ||
6015                toY == 0 && piece == BlackQueen ||
6016                toY <= 1 && piece == BlackKnight) {
6017                 *promoChoice = '+';
6018                 return FALSE;
6019             }
6020         } else {
6021             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6022                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6023                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6024                 *promoChoice = '+';
6025                 return FALSE;
6026             }
6027         }
6028     }
6029
6030     // weed out obviously illegal Pawn moves
6031     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6032         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6033         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6034         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6035         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6036         // note we are not allowed to test for valid (non-)capture, due to premove
6037     }
6038
6039     // we either have a choice what to promote to, or (in Shogi) whether to promote
6040     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6041         *promoChoice = PieceToChar(BlackFerz);  // no choice
6042         return FALSE;
6043     }
6044     // no sense asking what we must promote to if it is going to explode...
6045     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6046         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6047         return FALSE;
6048     }
6049     // give caller the default choice even if we will not make it
6050     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6051     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6052     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6053                            && gameInfo.variant != VariantShogi
6054                            && gameInfo.variant != VariantSuper) return FALSE;
6055     if(autoQueen) return FALSE; // predetermined
6056
6057     // suppress promotion popup on illegal moves that are not premoves
6058     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6059               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6060     if(appData.testLegality && !premove) {
6061         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6062                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6063         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6064             return FALSE;
6065     }
6066
6067     return TRUE;
6068 }
6069
6070 int
6071 InPalace(row, column)
6072      int row, column;
6073 {   /* [HGM] for Xiangqi */
6074     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6075          column < (BOARD_WIDTH + 4)/2 &&
6076          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6077     return FALSE;
6078 }
6079
6080 int
6081 PieceForSquare (x, y)
6082      int x;
6083      int y;
6084 {
6085   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6086      return -1;
6087   else
6088      return boards[currentMove][y][x];
6089 }
6090
6091 int
6092 OKToStartUserMove(x, y)
6093      int x, y;
6094 {
6095     ChessSquare from_piece;
6096     int white_piece;
6097
6098     if (matchMode) return FALSE;
6099     if (gameMode == EditPosition) return TRUE;
6100
6101     if (x >= 0 && y >= 0)
6102       from_piece = boards[currentMove][y][x];
6103     else
6104       from_piece = EmptySquare;
6105
6106     if (from_piece == EmptySquare) return FALSE;
6107
6108     white_piece = (int)from_piece >= (int)WhitePawn &&
6109       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6110
6111     switch (gameMode) {
6112       case PlayFromGameFile:
6113       case AnalyzeFile:
6114       case TwoMachinesPlay:
6115       case EndOfGame:
6116         return FALSE;
6117
6118       case IcsObserving:
6119       case IcsIdle:
6120         return FALSE;
6121
6122       case MachinePlaysWhite:
6123       case IcsPlayingBlack:
6124         if (appData.zippyPlay) return FALSE;
6125         if (white_piece) {
6126             DisplayMoveError(_("You are playing Black"));
6127             return FALSE;
6128         }
6129         break;
6130
6131       case MachinePlaysBlack:
6132       case IcsPlayingWhite:
6133         if (appData.zippyPlay) return FALSE;
6134         if (!white_piece) {
6135             DisplayMoveError(_("You are playing White"));
6136             return FALSE;
6137         }
6138         break;
6139
6140       case EditGame:
6141         if (!white_piece && WhiteOnMove(currentMove)) {
6142             DisplayMoveError(_("It is White's turn"));
6143             return FALSE;
6144         }
6145         if (white_piece && !WhiteOnMove(currentMove)) {
6146             DisplayMoveError(_("It is Black's turn"));
6147             return FALSE;
6148         }
6149         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6150             /* Editing correspondence game history */
6151             /* Could disallow this or prompt for confirmation */
6152             cmailOldMove = -1;
6153         }
6154         break;
6155
6156       case BeginningOfGame:
6157         if (appData.icsActive) return FALSE;
6158         if (!appData.noChessProgram) {
6159             if (!white_piece) {
6160                 DisplayMoveError(_("You are playing White"));
6161                 return FALSE;
6162             }
6163         }
6164         break;
6165
6166       case Training:
6167         if (!white_piece && WhiteOnMove(currentMove)) {
6168             DisplayMoveError(_("It is White's turn"));
6169             return FALSE;
6170         }
6171         if (white_piece && !WhiteOnMove(currentMove)) {
6172             DisplayMoveError(_("It is Black's turn"));
6173             return FALSE;
6174         }
6175         break;
6176
6177       default:
6178       case IcsExamining:
6179         break;
6180     }
6181     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6182         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6183         && gameMode != AnalyzeFile && gameMode != Training) {
6184         DisplayMoveError(_("Displayed position is not current"));
6185         return FALSE;
6186     }
6187     return TRUE;
6188 }
6189
6190 Boolean
6191 OnlyMove(int *x, int *y, Boolean captures) {
6192     DisambiguateClosure cl;
6193     if (appData.zippyPlay) return FALSE;
6194     switch(gameMode) {
6195       case MachinePlaysBlack:
6196       case IcsPlayingWhite:
6197       case BeginningOfGame:
6198         if(!WhiteOnMove(currentMove)) return FALSE;
6199         break;
6200       case MachinePlaysWhite:
6201       case IcsPlayingBlack:
6202         if(WhiteOnMove(currentMove)) return FALSE;
6203         break;
6204       case EditGame:
6205         break;
6206       default:
6207         return FALSE;
6208     }
6209     cl.pieceIn = EmptySquare;
6210     cl.rfIn = *y;
6211     cl.ffIn = *x;
6212     cl.rtIn = -1;
6213     cl.ftIn = -1;
6214     cl.promoCharIn = NULLCHAR;
6215     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6216     if( cl.kind == NormalMove ||
6217         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6218         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6219         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6220       fromX = cl.ff;
6221       fromY = cl.rf;
6222       *x = cl.ft;
6223       *y = cl.rt;
6224       return TRUE;
6225     }
6226     if(cl.kind != ImpossibleMove) return FALSE;
6227     cl.pieceIn = EmptySquare;
6228     cl.rfIn = -1;
6229     cl.ffIn = -1;
6230     cl.rtIn = *y;
6231     cl.ftIn = *x;
6232     cl.promoCharIn = NULLCHAR;
6233     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6234     if( cl.kind == NormalMove ||
6235         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6236         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6237         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6238       fromX = cl.ff;
6239       fromY = cl.rf;
6240       *x = cl.ft;
6241       *y = cl.rt;
6242       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6243       return TRUE;
6244     }
6245     return FALSE;
6246 }
6247
6248 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6249 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6250 int lastLoadGameUseList = FALSE;
6251 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6252 ChessMove lastLoadGameStart = EndOfFile;
6253
6254 void
6255 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6256      int fromX, fromY, toX, toY;
6257      int promoChar;
6258 {
6259     ChessMove moveType;
6260     ChessSquare pdown, pup;
6261
6262     /* Check if the user is playing in turn.  This is complicated because we
6263        let the user "pick up" a piece before it is his turn.  So the piece he
6264        tried to pick up may have been captured by the time he puts it down!
6265        Therefore we use the color the user is supposed to be playing in this
6266        test, not the color of the piece that is currently on the starting
6267        square---except in EditGame mode, where the user is playing both
6268        sides; fortunately there the capture race can't happen.  (It can
6269        now happen in IcsExamining mode, but that's just too bad.  The user
6270        will get a somewhat confusing message in that case.)
6271        */
6272
6273     switch (gameMode) {
6274       case PlayFromGameFile:
6275       case AnalyzeFile:
6276       case TwoMachinesPlay:
6277       case EndOfGame:
6278       case IcsObserving:
6279       case IcsIdle:
6280         /* We switched into a game mode where moves are not accepted,
6281            perhaps while the mouse button was down. */
6282         return;
6283
6284       case MachinePlaysWhite:
6285         /* User is moving for Black */
6286         if (WhiteOnMove(currentMove)) {
6287             DisplayMoveError(_("It is White's turn"));
6288             return;
6289         }
6290         break;
6291
6292       case MachinePlaysBlack:
6293         /* User is moving for White */
6294         if (!WhiteOnMove(currentMove)) {
6295             DisplayMoveError(_("It is Black's turn"));
6296             return;
6297         }
6298         break;
6299
6300       case EditGame:
6301       case IcsExamining:
6302       case BeginningOfGame:
6303       case AnalyzeMode:
6304       case Training:
6305         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6306         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6307             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6308             /* User is moving for Black */
6309             if (WhiteOnMove(currentMove)) {
6310                 DisplayMoveError(_("It is White's turn"));
6311                 return;
6312             }
6313         } else {
6314             /* User is moving for White */
6315             if (!WhiteOnMove(currentMove)) {
6316                 DisplayMoveError(_("It is Black's turn"));
6317                 return;
6318             }
6319         }
6320         break;
6321
6322       case IcsPlayingBlack:
6323         /* User is moving for Black */
6324         if (WhiteOnMove(currentMove)) {
6325             if (!appData.premove) {
6326                 DisplayMoveError(_("It is White's turn"));
6327             } else if (toX >= 0 && toY >= 0) {
6328                 premoveToX = toX;
6329                 premoveToY = toY;
6330                 premoveFromX = fromX;
6331                 premoveFromY = fromY;
6332                 premovePromoChar = promoChar;
6333                 gotPremove = 1;
6334                 if (appData.debugMode)
6335                     fprintf(debugFP, "Got premove: fromX %d,"
6336                             "fromY %d, toX %d, toY %d\n",
6337                             fromX, fromY, toX, toY);
6338             }
6339             return;
6340         }
6341         break;
6342
6343       case IcsPlayingWhite:
6344         /* User is moving for White */
6345         if (!WhiteOnMove(currentMove)) {
6346             if (!appData.premove) {
6347                 DisplayMoveError(_("It is Black's turn"));
6348             } else if (toX >= 0 && toY >= 0) {
6349                 premoveToX = toX;
6350                 premoveToY = toY;
6351                 premoveFromX = fromX;
6352                 premoveFromY = fromY;
6353                 premovePromoChar = promoChar;
6354                 gotPremove = 1;
6355                 if (appData.debugMode)
6356                     fprintf(debugFP, "Got premove: fromX %d,"
6357                             "fromY %d, toX %d, toY %d\n",
6358                             fromX, fromY, toX, toY);
6359             }
6360             return;
6361         }
6362         break;
6363
6364       default:
6365         break;
6366
6367       case EditPosition:
6368         /* EditPosition, empty square, or different color piece;
6369            click-click move is possible */
6370         if (toX == -2 || toY == -2) {
6371             boards[0][fromY][fromX] = EmptySquare;
6372             DrawPosition(FALSE, boards[currentMove]);
6373             return;
6374         } else if (toX >= 0 && toY >= 0) {
6375             boards[0][toY][toX] = boards[0][fromY][fromX];
6376             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6377                 if(boards[0][fromY][0] != EmptySquare) {
6378                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6379                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6380                 }
6381             } else
6382             if(fromX == BOARD_RGHT+1) {
6383                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6384                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6385                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6386                 }
6387             } else
6388             boards[0][fromY][fromX] = EmptySquare;
6389             DrawPosition(FALSE, boards[currentMove]);
6390             return;
6391         }
6392         return;
6393     }
6394
6395     if(toX < 0 || toY < 0) return;
6396     pdown = boards[currentMove][fromY][fromX];
6397     pup = boards[currentMove][toY][toX];
6398
6399     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6400     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6401          if( pup != EmptySquare ) return;
6402          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6403            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6404                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6405            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6406            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6407            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6408            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6409          fromY = DROP_RANK;
6410     }
6411
6412     /* [HGM] always test for legality, to get promotion info */
6413     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6414                                          fromY, fromX, toY, toX, promoChar);
6415     /* [HGM] but possibly ignore an IllegalMove result */
6416     if (appData.testLegality) {
6417         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6418             DisplayMoveError(_("Illegal move"));
6419             return;
6420         }
6421     }
6422
6423     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6424 }
6425
6426 /* Common tail of UserMoveEvent and DropMenuEvent */
6427 int
6428 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6429      ChessMove moveType;
6430      int fromX, fromY, toX, toY;
6431      /*char*/int promoChar;
6432 {
6433     char *bookHit = 0;
6434
6435     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6436         // [HGM] superchess: suppress promotions to non-available piece
6437         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6438         if(WhiteOnMove(currentMove)) {
6439             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6440         } else {
6441             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6442         }
6443     }
6444
6445     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6446        move type in caller when we know the move is a legal promotion */
6447     if(moveType == NormalMove && promoChar)
6448         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6449
6450     /* [HGM] <popupFix> The following if has been moved here from
6451        UserMoveEvent(). Because it seemed to belong here (why not allow
6452        piece drops in training games?), and because it can only be
6453        performed after it is known to what we promote. */
6454     if (gameMode == Training) {
6455       /* compare the move played on the board to the next move in the
6456        * game. If they match, display the move and the opponent's response.
6457        * If they don't match, display an error message.
6458        */
6459       int saveAnimate;
6460       Board testBoard;
6461       CopyBoard(testBoard, boards[currentMove]);
6462       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6463
6464       if (CompareBoards(testBoard, boards[currentMove+1])) {
6465         ForwardInner(currentMove+1);
6466
6467         /* Autoplay the opponent's response.
6468          * if appData.animate was TRUE when Training mode was entered,
6469          * the response will be animated.
6470          */
6471         saveAnimate = appData.animate;
6472         appData.animate = animateTraining;
6473         ForwardInner(currentMove+1);
6474         appData.animate = saveAnimate;
6475
6476         /* check for the end of the game */
6477         if (currentMove >= forwardMostMove) {
6478           gameMode = PlayFromGameFile;
6479           ModeHighlight();
6480           SetTrainingModeOff();
6481           DisplayInformation(_("End of game"));
6482         }
6483       } else {
6484         DisplayError(_("Incorrect move"), 0);
6485       }
6486       return 1;
6487     }
6488
6489   /* Ok, now we know that the move is good, so we can kill
6490      the previous line in Analysis Mode */
6491   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6492                                 && currentMove < forwardMostMove) {
6493     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6494     else forwardMostMove = currentMove;
6495   }
6496
6497   /* If we need the chess program but it's dead, restart it */
6498   ResurrectChessProgram();
6499
6500   /* A user move restarts a paused game*/
6501   if (pausing)
6502     PauseEvent();
6503
6504   thinkOutput[0] = NULLCHAR;
6505
6506   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6507
6508   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6509     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6510     return 1;
6511   }
6512
6513   if (gameMode == BeginningOfGame) {
6514     if (appData.noChessProgram) {
6515       gameMode = EditGame;
6516       SetGameInfo();
6517     } else {
6518       char buf[MSG_SIZ];
6519       gameMode = MachinePlaysBlack;
6520       StartClocks();
6521       SetGameInfo();
6522       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6523       DisplayTitle(buf);
6524       if (first.sendName) {
6525         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6526         SendToProgram(buf, &first);
6527       }
6528       StartClocks();
6529     }
6530     ModeHighlight();
6531   }
6532
6533   /* Relay move to ICS or chess engine */
6534   if (appData.icsActive) {
6535     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6536         gameMode == IcsExamining) {
6537       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6538         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6539         SendToICS("draw ");
6540         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6541       }
6542       // also send plain move, in case ICS does not understand atomic claims
6543       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6544       ics_user_moved = 1;
6545     }
6546   } else {
6547     if (first.sendTime && (gameMode == BeginningOfGame ||
6548                            gameMode == MachinePlaysWhite ||
6549                            gameMode == MachinePlaysBlack)) {
6550       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6551     }
6552     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6553          // [HGM] book: if program might be playing, let it use book
6554         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6555         first.maybeThinking = TRUE;
6556     } else SendMoveToProgram(forwardMostMove-1, &first);
6557     if (currentMove == cmailOldMove + 1) {
6558       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6559     }
6560   }
6561
6562   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6563
6564   switch (gameMode) {
6565   case EditGame:
6566     if(appData.testLegality)
6567     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6568     case MT_NONE:
6569     case MT_CHECK:
6570       break;
6571     case MT_CHECKMATE:
6572     case MT_STAINMATE:
6573       if (WhiteOnMove(currentMove)) {
6574         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6575       } else {
6576         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6577       }
6578       break;
6579     case MT_STALEMATE:
6580       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6581       break;
6582     }
6583     break;
6584
6585   case MachinePlaysBlack:
6586   case MachinePlaysWhite:
6587     /* disable certain menu options while machine is thinking */
6588     SetMachineThinkingEnables();
6589     break;
6590
6591   default:
6592     break;
6593   }
6594
6595   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6596   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6597
6598   if(bookHit) { // [HGM] book: simulate book reply
6599         static char bookMove[MSG_SIZ]; // a bit generous?
6600
6601         programStats.nodes = programStats.depth = programStats.time =
6602         programStats.score = programStats.got_only_move = 0;
6603         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6604
6605         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6606         strcat(bookMove, bookHit);
6607         HandleMachineMove(bookMove, &first);
6608   }
6609   return 1;
6610 }
6611
6612 void
6613 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6614      Board board;
6615      int flags;
6616      ChessMove kind;
6617      int rf, ff, rt, ft;
6618      VOIDSTAR closure;
6619 {
6620     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6621     Markers *m = (Markers *) closure;
6622     if(rf == fromY && ff == fromX)
6623         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6624                          || kind == WhiteCapturesEnPassant
6625                          || kind == BlackCapturesEnPassant);
6626     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6627 }
6628
6629 void
6630 MarkTargetSquares(int clear)
6631 {
6632   int x, y;
6633   if(!appData.markers || !appData.highlightDragging ||
6634      !appData.testLegality || gameMode == EditPosition) return;
6635   if(clear) {
6636     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6637   } else {
6638     int capt = 0;
6639     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6640     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6641       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6642       if(capt)
6643       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6644     }
6645   }
6646   DrawPosition(TRUE, NULL);
6647 }
6648
6649 int
6650 Explode(Board board, int fromX, int fromY, int toX, int toY)
6651 {
6652     if(gameInfo.variant == VariantAtomic &&
6653        (board[toY][toX] != EmptySquare ||                     // capture?
6654         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6655                          board[fromY][fromX] == BlackPawn   )
6656       )) {
6657         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6658         return TRUE;
6659     }
6660     return FALSE;
6661 }
6662
6663 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6664
6665 int CanPromote(ChessSquare piece, int y)
6666 {
6667         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6668         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6669         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6670            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6671            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6672                                                   gameInfo.variant == VariantMakruk) return FALSE;
6673         return (piece == BlackPawn && y == 1 ||
6674                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6675                 piece == BlackLance && y == 1 ||
6676                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6677 }
6678
6679 void LeftClick(ClickType clickType, int xPix, int yPix)
6680 {
6681     int x, y;
6682     Boolean saveAnimate;
6683     static int second = 0, promotionChoice = 0, clearFlag = 0;
6684     char promoChoice = NULLCHAR;
6685     ChessSquare piece;
6686
6687     if(appData.seekGraph && appData.icsActive && loggedOn &&
6688         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6689         SeekGraphClick(clickType, xPix, yPix, 0);
6690         return;
6691     }
6692
6693     if (clickType == Press) ErrorPopDown();
6694     MarkTargetSquares(1);
6695
6696     x = EventToSquare(xPix, BOARD_WIDTH);
6697     y = EventToSquare(yPix, BOARD_HEIGHT);
6698     if (!flipView && y >= 0) {
6699         y = BOARD_HEIGHT - 1 - y;
6700     }
6701     if (flipView && x >= 0) {
6702         x = BOARD_WIDTH - 1 - x;
6703     }
6704
6705     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6706         defaultPromoChoice = promoSweep;
6707         promoSweep = EmptySquare;   // terminate sweep
6708         promoDefaultAltered = TRUE;
6709         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6710     }
6711
6712     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6713         if(clickType == Release) return; // ignore upclick of click-click destination
6714         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6715         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6716         if(gameInfo.holdingsWidth &&
6717                 (WhiteOnMove(currentMove)
6718                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6719                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6720             // click in right holdings, for determining promotion piece
6721             ChessSquare p = boards[currentMove][y][x];
6722             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6723             if(p != EmptySquare) {
6724                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6725                 fromX = fromY = -1;
6726                 return;
6727             }
6728         }
6729         DrawPosition(FALSE, boards[currentMove]);
6730         return;
6731     }
6732
6733     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6734     if(clickType == Press
6735             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6736               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6737               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6738         return;
6739
6740     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6741         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6742
6743     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6744         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6745                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6746         defaultPromoChoice = DefaultPromoChoice(side);
6747     }
6748
6749     autoQueen = appData.alwaysPromoteToQueen;
6750
6751     if (fromX == -1) {
6752       int originalY = y;
6753       gatingPiece = EmptySquare;
6754       if (clickType != Press) {
6755         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6756             DragPieceEnd(xPix, yPix); dragging = 0;
6757             DrawPosition(FALSE, NULL);
6758         }
6759         return;
6760       }
6761       fromX = x; fromY = y;
6762       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6763          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6764          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6765             /* First square */
6766             if (OKToStartUserMove(fromX, fromY)) {
6767                 second = 0;
6768                 MarkTargetSquares(0);
6769                 DragPieceBegin(xPix, yPix); dragging = 1;
6770                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6771                     promoSweep = defaultPromoChoice;
6772                     selectFlag = 0; lastX = xPix; lastY = yPix;
6773                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6774                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6775                 }
6776                 if (appData.highlightDragging) {
6777                     SetHighlights(fromX, fromY, -1, -1);
6778                 }
6779             } else fromX = fromY = -1;
6780             return;
6781         }
6782     }
6783
6784     /* fromX != -1 */
6785     if (clickType == Press && gameMode != EditPosition) {
6786         ChessSquare fromP;
6787         ChessSquare toP;
6788         int frc;
6789
6790         // ignore off-board to clicks
6791         if(y < 0 || x < 0) return;
6792
6793         /* Check if clicking again on the same color piece */
6794         fromP = boards[currentMove][fromY][fromX];
6795         toP = boards[currentMove][y][x];
6796         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6797         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6798              WhitePawn <= toP && toP <= WhiteKing &&
6799              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6800              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6801             (BlackPawn <= fromP && fromP <= BlackKing &&
6802              BlackPawn <= toP && toP <= BlackKing &&
6803              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6804              !(fromP == BlackKing && toP == BlackRook && frc))) {
6805             /* Clicked again on same color piece -- changed his mind */
6806             second = (x == fromX && y == fromY);
6807             promoDefaultAltered = FALSE;
6808            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6809             if (appData.highlightDragging) {
6810                 SetHighlights(x, y, -1, -1);
6811             } else {
6812                 ClearHighlights();
6813             }
6814             if (OKToStartUserMove(x, y)) {
6815                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6816                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6817                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6818                  gatingPiece = boards[currentMove][fromY][fromX];
6819                 else gatingPiece = EmptySquare;
6820                 fromX = x;
6821                 fromY = y; dragging = 1;
6822                 MarkTargetSquares(0);
6823                 DragPieceBegin(xPix, yPix);
6824                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6825                     promoSweep = defaultPromoChoice;
6826                     selectFlag = 0; lastX = xPix; lastY = yPix;
6827                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6828                 }
6829             }
6830            }
6831            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6832            second = FALSE; 
6833         }
6834         // ignore clicks on holdings
6835         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6836     }
6837
6838     if (clickType == Release && x == fromX && y == fromY) {
6839         DragPieceEnd(xPix, yPix); dragging = 0;
6840         if(clearFlag) {
6841             // a deferred attempt to click-click move an empty square on top of a piece
6842             boards[currentMove][y][x] = EmptySquare;
6843             ClearHighlights();
6844             DrawPosition(FALSE, boards[currentMove]);
6845             fromX = fromY = -1; clearFlag = 0;
6846             return;
6847         }
6848         if (appData.animateDragging) {
6849             /* Undo animation damage if any */
6850             DrawPosition(FALSE, NULL);
6851         }
6852         if (second) {
6853             /* Second up/down in same square; just abort move */
6854             second = 0;
6855             fromX = fromY = -1;
6856             gatingPiece = EmptySquare;
6857             ClearHighlights();
6858             gotPremove = 0;
6859             ClearPremoveHighlights();
6860         } else {
6861             /* First upclick in same square; start click-click mode */
6862             SetHighlights(x, y, -1, -1);
6863         }
6864         return;
6865     }
6866
6867     clearFlag = 0;
6868
6869     /* we now have a different from- and (possibly off-board) to-square */
6870     /* Completed move */
6871     toX = x;
6872     toY = y;
6873     saveAnimate = appData.animate;
6874     if (clickType == Press) {
6875         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6876             // must be Edit Position mode with empty-square selected
6877             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6878             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6879             return;
6880         }
6881         /* Finish clickclick move */
6882         if (appData.animate || appData.highlightLastMove) {
6883             SetHighlights(fromX, fromY, toX, toY);
6884         } else {
6885             ClearHighlights();
6886         }
6887     } else {
6888         /* Finish drag move */
6889         if (appData.highlightLastMove) {
6890             SetHighlights(fromX, fromY, toX, toY);
6891         } else {
6892             ClearHighlights();
6893         }
6894         DragPieceEnd(xPix, yPix); dragging = 0;
6895         /* Don't animate move and drag both */
6896         appData.animate = FALSE;
6897     }
6898
6899     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6900     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6901         ChessSquare piece = boards[currentMove][fromY][fromX];
6902         if(gameMode == EditPosition && piece != EmptySquare &&
6903            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6904             int n;
6905
6906             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6907                 n = PieceToNumber(piece - (int)BlackPawn);
6908                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6909                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6910                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6911             } else
6912             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6913                 n = PieceToNumber(piece);
6914                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6915                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6916                 boards[currentMove][n][BOARD_WIDTH-2]++;
6917             }
6918             boards[currentMove][fromY][fromX] = EmptySquare;
6919         }
6920         ClearHighlights();
6921         fromX = fromY = -1;
6922         DrawPosition(TRUE, boards[currentMove]);
6923         return;
6924     }
6925
6926     // off-board moves should not be highlighted
6927     if(x < 0 || y < 0) ClearHighlights();
6928
6929     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6930
6931     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6932         SetHighlights(fromX, fromY, toX, toY);
6933         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6934             // [HGM] super: promotion to captured piece selected from holdings
6935             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6936             promotionChoice = TRUE;
6937             // kludge follows to temporarily execute move on display, without promoting yet
6938             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6939             boards[currentMove][toY][toX] = p;
6940             DrawPosition(FALSE, boards[currentMove]);
6941             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6942             boards[currentMove][toY][toX] = q;
6943             DisplayMessage("Click in holdings to choose piece", "");
6944             return;
6945         }
6946         PromotionPopUp();
6947     } else {
6948         int oldMove = currentMove;
6949         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6950         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6951         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6952         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6953            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6954             DrawPosition(TRUE, boards[currentMove]);
6955         fromX = fromY = -1;
6956     }
6957     appData.animate = saveAnimate;
6958     if (appData.animate || appData.animateDragging) {
6959         /* Undo animation damage if needed */
6960         DrawPosition(FALSE, NULL);
6961     }
6962 }
6963
6964 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6965 {   // front-end-free part taken out of PieceMenuPopup
6966     int whichMenu; int xSqr, ySqr;
6967
6968     if(seekGraphUp) { // [HGM] seekgraph
6969         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6970         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6971         return -2;
6972     }
6973
6974     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6975          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6976         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6977         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6978         if(action == Press)   {
6979             originalFlip = flipView;
6980             flipView = !flipView; // temporarily flip board to see game from partners perspective
6981             DrawPosition(TRUE, partnerBoard);
6982             DisplayMessage(partnerStatus, "");
6983             partnerUp = TRUE;
6984         } else if(action == Release) {
6985             flipView = originalFlip;
6986             DrawPosition(TRUE, boards[currentMove]);
6987             partnerUp = FALSE;
6988         }
6989         return -2;
6990     }
6991
6992     xSqr = EventToSquare(x, BOARD_WIDTH);
6993     ySqr = EventToSquare(y, BOARD_HEIGHT);
6994     if (action == Release) {
6995         if(pieceSweep != EmptySquare) {
6996             EditPositionMenuEvent(pieceSweep, toX, toY);
6997             pieceSweep = EmptySquare;
6998         } else UnLoadPV(); // [HGM] pv
6999     }
7000     if (action != Press) return -2; // return code to be ignored
7001     switch (gameMode) {
7002       case IcsExamining:
7003         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7004       case EditPosition:
7005         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7006         if (xSqr < 0 || ySqr < 0) return -1;
7007         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7008         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7009         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7010         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7011         NextPiece(0);
7012         return -2;\r
7013       case IcsObserving:
7014         if(!appData.icsEngineAnalyze) return -1;
7015       case IcsPlayingWhite:
7016       case IcsPlayingBlack:
7017         if(!appData.zippyPlay) goto noZip;
7018       case AnalyzeMode:
7019       case AnalyzeFile:
7020       case MachinePlaysWhite:
7021       case MachinePlaysBlack:
7022       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7023         if (!appData.dropMenu) {
7024           LoadPV(x, y);
7025           return 2; // flag front-end to grab mouse events
7026         }
7027         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7028            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7029       case EditGame:
7030       noZip:
7031         if (xSqr < 0 || ySqr < 0) return -1;
7032         if (!appData.dropMenu || appData.testLegality &&
7033             gameInfo.variant != VariantBughouse &&
7034             gameInfo.variant != VariantCrazyhouse) return -1;
7035         whichMenu = 1; // drop menu
7036         break;
7037       default:
7038         return -1;
7039     }
7040
7041     if (((*fromX = xSqr) < 0) ||
7042         ((*fromY = ySqr) < 0)) {
7043         *fromX = *fromY = -1;
7044         return -1;
7045     }
7046     if (flipView)
7047       *fromX = BOARD_WIDTH - 1 - *fromX;
7048     else
7049       *fromY = BOARD_HEIGHT - 1 - *fromY;
7050
7051     return whichMenu;
7052 }
7053
7054 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7055 {
7056 //    char * hint = lastHint;
7057     FrontEndProgramStats stats;
7058
7059     stats.which = cps == &first ? 0 : 1;
7060     stats.depth = cpstats->depth;
7061     stats.nodes = cpstats->nodes;
7062     stats.score = cpstats->score;
7063     stats.time = cpstats->time;
7064     stats.pv = cpstats->movelist;
7065     stats.hint = lastHint;
7066     stats.an_move_index = 0;
7067     stats.an_move_count = 0;
7068
7069     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7070         stats.hint = cpstats->move_name;
7071         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7072         stats.an_move_count = cpstats->nr_moves;
7073     }
7074
7075     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7076
7077     SetProgramStats( &stats );
7078 }
7079
7080 #define MAXPLAYERS 500
7081
7082 char *
7083 TourneyStandings(int display)
7084 {
7085     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7086     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7087     char result, *p, *names[MAXPLAYERS];
7088
7089     names[0] = p = strdup(appData.participants);
7090     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7091
7092     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7093
7094     while(result = appData.results[nr]) {
7095         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7096         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7097         wScore = bScore = 0;
7098         switch(result) {
7099           case '+': wScore = 2; break;
7100           case '-': bScore = 2; break;
7101           case '=': wScore = bScore = 1; break;
7102           case ' ':
7103           case '*': return strdup("busy"); // tourney not finished
7104         }
7105         score[w] += wScore;
7106         score[b] += bScore;
7107         games[w]++;
7108         games[b]++;
7109         nr++;
7110     }
7111     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7112     for(w=0; w<nPlayers; w++) {
7113         bScore = -1;
7114         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7115         ranking[w] = b; points[w] = bScore; score[b] = -2;
7116     }
7117     p = malloc(nPlayers*34+1);
7118     for(w=0; w<nPlayers && w<display; w++)
7119         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7120     free(names[0]);
7121     return p;
7122 }
7123
7124 void
7125 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7126 {       // count all piece types
7127         int p, f, r;
7128         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7129         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7130         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7131                 p = board[r][f];
7132                 pCnt[p]++;
7133                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7134                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7135                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7136                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7137                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7138                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7139         }
7140 }
7141
7142 int
7143 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7144 {
7145         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7146         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7147
7148         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7149         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7150         if(myPawns == 2 && nMine == 3) // KPP
7151             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7152         if(myPawns == 1 && nMine == 2) // KP
7153             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7154         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7155             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7156         if(myPawns) return FALSE;
7157         if(pCnt[WhiteRook+side])
7158             return pCnt[BlackRook-side] ||
7159                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7160                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7161                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7162         if(pCnt[WhiteCannon+side]) {
7163             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7164             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7165         }
7166         if(pCnt[WhiteKnight+side])
7167             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7168         return FALSE;
7169 }
7170
7171 int
7172 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7173 {
7174         VariantClass v = gameInfo.variant;
7175
7176         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7177         if(v == VariantShatranj) return TRUE; // always winnable through baring
7178         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7179         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7180
7181         if(v == VariantXiangqi) {
7182                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7183
7184                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7185                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7186                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7187                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7188                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7189                 if(stale) // we have at least one last-rank P plus perhaps C
7190                     return majors // KPKX
7191                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7192                 else // KCA*E*
7193                     return pCnt[WhiteFerz+side] // KCAK
7194                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7195                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7196                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7197
7198         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7199                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7200
7201                 if(nMine == 1) return FALSE; // bare King
7202                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7203                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7204                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7205                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7206                 if(pCnt[WhiteKnight+side])
7207                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7208                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7209                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7210                 if(nBishops)
7211                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7212                 if(pCnt[WhiteAlfil+side])
7213                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7214                 if(pCnt[WhiteWazir+side])
7215                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7216         }
7217
7218         return TRUE;
7219 }
7220
7221 int
7222 Adjudicate(ChessProgramState *cps)
7223 {       // [HGM] some adjudications useful with buggy engines
7224         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7225         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7226         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7227         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7228         int k, count = 0; static int bare = 1;
7229         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7230         Boolean canAdjudicate = !appData.icsActive;
7231
7232         // most tests only when we understand the game, i.e. legality-checking on
7233             if( appData.testLegality )
7234             {   /* [HGM] Some more adjudications for obstinate engines */
7235                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7236                 static int moveCount = 6;
7237                 ChessMove result;
7238                 char *reason = NULL;
7239
7240                 /* Count what is on board. */
7241                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7242
7243                 /* Some material-based adjudications that have to be made before stalemate test */
7244                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7245                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7246                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7247                      if(canAdjudicate && appData.checkMates) {
7248                          if(engineOpponent)
7249                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7250                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7251                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7252                          return 1;
7253                      }
7254                 }
7255
7256                 /* Bare King in Shatranj (loses) or Losers (wins) */
7257                 if( nrW == 1 || nrB == 1) {
7258                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7259                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7260                      if(canAdjudicate && appData.checkMates) {
7261                          if(engineOpponent)
7262                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7263                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7264                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7265                          return 1;
7266                      }
7267                   } else
7268                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7269                   {    /* bare King */
7270                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7271                         if(canAdjudicate && appData.checkMates) {
7272                             /* but only adjudicate if adjudication enabled */
7273                             if(engineOpponent)
7274                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7275                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7276                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7277                             return 1;
7278                         }
7279                   }
7280                 } else bare = 1;
7281
7282
7283             // don't wait for engine to announce game end if we can judge ourselves
7284             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7285               case MT_CHECK:
7286                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7287                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7288                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7289                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7290                             checkCnt++;
7291                         if(checkCnt >= 2) {
7292                             reason = "Xboard adjudication: 3rd check";
7293                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7294                             break;
7295                         }
7296                     }
7297                 }
7298               case MT_NONE:
7299               default:
7300                 break;
7301               case MT_STALEMATE:
7302               case MT_STAINMATE:
7303                 reason = "Xboard adjudication: Stalemate";
7304                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7305                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7306                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7307                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7308                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7309                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7310                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7311                                                                         EP_CHECKMATE : EP_WINS);
7312                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7313                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7314                 }
7315                 break;
7316               case MT_CHECKMATE:
7317                 reason = "Xboard adjudication: Checkmate";
7318                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7319                 break;
7320             }
7321
7322                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7323                     case EP_STALEMATE:
7324                         result = GameIsDrawn; break;
7325                     case EP_CHECKMATE:
7326                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7327                     case EP_WINS:
7328                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7329                     default:
7330                         result = EndOfFile;
7331                 }
7332                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7333                     if(engineOpponent)
7334                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7335                     GameEnds( result, reason, GE_XBOARD );
7336                     return 1;
7337                 }
7338
7339                 /* Next absolutely insufficient mating material. */
7340                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7341                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7342                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7343
7344                      /* always flag draws, for judging claims */
7345                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7346
7347                      if(canAdjudicate && appData.materialDraws) {
7348                          /* but only adjudicate them if adjudication enabled */
7349                          if(engineOpponent) {
7350                            SendToProgram("force\n", engineOpponent); // suppress reply
7351                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7352                          }
7353                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7354                          return 1;
7355                      }
7356                 }
7357
7358                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7359                 if(gameInfo.variant == VariantXiangqi ?
7360                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7361                  : nrW + nrB == 4 &&
7362                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7363                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7364                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7365                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7366                    ) ) {
7367                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7368                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7369                           if(engineOpponent) {
7370                             SendToProgram("force\n", engineOpponent); // suppress reply
7371                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7372                           }
7373                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7374                           return 1;
7375                      }
7376                 } else moveCount = 6;
7377             }
7378         if (appData.debugMode) { int i;
7379             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7380                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7381                     appData.drawRepeats);
7382             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7383               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7384
7385         }
7386
7387         // Repetition draws and 50-move rule can be applied independently of legality testing
7388
7389                 /* Check for rep-draws */
7390                 count = 0;
7391                 for(k = forwardMostMove-2;
7392                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7393                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7394                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7395                     k-=2)
7396                 {   int rights=0;
7397                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7398                         /* compare castling rights */
7399                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7400                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7401                                 rights++; /* King lost rights, while rook still had them */
7402                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7403                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7404                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7405                                    rights++; /* but at least one rook lost them */
7406                         }
7407                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7408                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7409                                 rights++;
7410                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7411                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7412                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7413                                    rights++;
7414                         }
7415                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7416                             && appData.drawRepeats > 1) {
7417                              /* adjudicate after user-specified nr of repeats */
7418                              int result = GameIsDrawn;
7419                              char *details = "XBoard adjudication: repetition draw";
7420                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7421                                 // [HGM] xiangqi: check for forbidden perpetuals
7422                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7423                                 for(m=forwardMostMove; m>k; m-=2) {
7424                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7425                                         ourPerpetual = 0; // the current mover did not always check
7426                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7427                                         hisPerpetual = 0; // the opponent did not always check
7428                                 }
7429                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7430                                                                         ourPerpetual, hisPerpetual);
7431                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7432                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7433                                     details = "Xboard adjudication: perpetual checking";
7434                                 } else
7435                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7436                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7437                                 } else
7438                                 // Now check for perpetual chases
7439                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7440                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7441                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7442                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7443                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7444                                         details = "Xboard adjudication: perpetual chasing";
7445                                     } else
7446                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7447                                         break; // Abort repetition-checking loop.
7448                                 }
7449                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7450                              }
7451                              if(engineOpponent) {
7452                                SendToProgram("force\n", engineOpponent); // suppress reply
7453                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7454                              }
7455                              GameEnds( result, details, GE_XBOARD );
7456                              return 1;
7457                         }
7458                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7459                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7460                     }
7461                 }
7462
7463                 /* Now we test for 50-move draws. Determine ply count */
7464                 count = forwardMostMove;
7465                 /* look for last irreversble move */
7466                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7467                     count--;
7468                 /* if we hit starting position, add initial plies */
7469                 if( count == backwardMostMove )
7470                     count -= initialRulePlies;
7471                 count = forwardMostMove - count;
7472                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7473                         // adjust reversible move counter for checks in Xiangqi
7474                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7475                         if(i < backwardMostMove) i = backwardMostMove;
7476                         while(i <= forwardMostMove) {
7477                                 lastCheck = inCheck; // check evasion does not count
7478                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7479                                 if(inCheck || lastCheck) count--; // check does not count
7480                                 i++;
7481                         }
7482                 }
7483                 if( count >= 100)
7484                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7485                          /* this is used to judge if draw claims are legal */
7486                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7487                          if(engineOpponent) {
7488                            SendToProgram("force\n", engineOpponent); // suppress reply
7489                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7490                          }
7491                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7492                          return 1;
7493                 }
7494
7495                 /* if draw offer is pending, treat it as a draw claim
7496                  * when draw condition present, to allow engines a way to
7497                  * claim draws before making their move to avoid a race
7498                  * condition occurring after their move
7499                  */
7500                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7501                          char *p = NULL;
7502                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7503                              p = "Draw claim: 50-move rule";
7504                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7505                              p = "Draw claim: 3-fold repetition";
7506                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7507                              p = "Draw claim: insufficient mating material";
7508                          if( p != NULL && canAdjudicate) {
7509                              if(engineOpponent) {
7510                                SendToProgram("force\n", engineOpponent); // suppress reply
7511                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7512                              }
7513                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7514                              return 1;
7515                          }
7516                 }
7517
7518                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7519                     if(engineOpponent) {
7520                       SendToProgram("force\n", engineOpponent); // suppress reply
7521                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7522                     }
7523                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7524                     return 1;
7525                 }
7526         return 0;
7527 }
7528
7529 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7530 {   // [HGM] book: this routine intercepts moves to simulate book replies
7531     char *bookHit = NULL;
7532
7533     //first determine if the incoming move brings opponent into his book
7534     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7535         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7536     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7537     if(bookHit != NULL && !cps->bookSuspend) {
7538         // make sure opponent is not going to reply after receiving move to book position
7539         SendToProgram("force\n", cps);
7540         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7541     }
7542     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7543     // now arrange restart after book miss
7544     if(bookHit) {
7545         // after a book hit we never send 'go', and the code after the call to this routine
7546         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7547         char buf[MSG_SIZ];
7548         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7549         SendToProgram(buf, cps);
7550         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7551     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7552         SendToProgram("go\n", cps);
7553         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7554     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7555         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7556             SendToProgram("go\n", cps);
7557         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7558     }
7559     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7560 }
7561
7562 char *savedMessage;
7563 ChessProgramState *savedState;
7564 void DeferredBookMove(void)
7565 {
7566         if(savedState->lastPing != savedState->lastPong)
7567                     ScheduleDelayedEvent(DeferredBookMove, 10);
7568         else
7569         HandleMachineMove(savedMessage, savedState);
7570 }
7571
7572 void
7573 HandleMachineMove(message, cps)
7574      char *message;
7575      ChessProgramState *cps;
7576 {
7577     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7578     char realname[MSG_SIZ];
7579     int fromX, fromY, toX, toY;
7580     ChessMove moveType;
7581     char promoChar;
7582     char *p;
7583     int machineWhite;
7584     char *bookHit;
7585
7586     cps->userError = 0;
7587
7588 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7589     /*
7590      * Kludge to ignore BEL characters
7591      */
7592     while (*message == '\007') message++;
7593
7594     /*
7595      * [HGM] engine debug message: ignore lines starting with '#' character
7596      */
7597     if(cps->debug && *message == '#') return;
7598
7599     /*
7600      * Look for book output
7601      */
7602     if (cps == &first && bookRequested) {
7603         if (message[0] == '\t' || message[0] == ' ') {
7604             /* Part of the book output is here; append it */
7605             strcat(bookOutput, message);
7606             strcat(bookOutput, "  \n");
7607             return;
7608         } else if (bookOutput[0] != NULLCHAR) {
7609             /* All of book output has arrived; display it */
7610             char *p = bookOutput;
7611             while (*p != NULLCHAR) {
7612                 if (*p == '\t') *p = ' ';
7613                 p++;
7614             }
7615             DisplayInformation(bookOutput);
7616             bookRequested = FALSE;
7617             /* Fall through to parse the current output */
7618         }
7619     }
7620
7621     /*
7622      * Look for machine move.
7623      */
7624     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7625         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7626     {
7627         /* This method is only useful on engines that support ping */
7628         if (cps->lastPing != cps->lastPong) {
7629           if (gameMode == BeginningOfGame) {
7630             /* Extra move from before last new; ignore */
7631             if (appData.debugMode) {
7632                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7633             }
7634           } else {
7635             if (appData.debugMode) {
7636                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7637                         cps->which, gameMode);
7638             }
7639
7640             SendToProgram("undo\n", cps);
7641           }
7642           return;
7643         }
7644
7645         switch (gameMode) {
7646           case BeginningOfGame:
7647             /* Extra move from before last reset; ignore */
7648             if (appData.debugMode) {
7649                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7650             }
7651             return;
7652
7653           case EndOfGame:
7654           case IcsIdle:
7655           default:
7656             /* Extra move after we tried to stop.  The mode test is
7657                not a reliable way of detecting this problem, but it's
7658                the best we can do on engines that don't support ping.
7659             */
7660             if (appData.debugMode) {
7661                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7662                         cps->which, gameMode);
7663             }
7664             SendToProgram("undo\n", cps);
7665             return;
7666
7667           case MachinePlaysWhite:
7668           case IcsPlayingWhite:
7669             machineWhite = TRUE;
7670             break;
7671
7672           case MachinePlaysBlack:
7673           case IcsPlayingBlack:
7674             machineWhite = FALSE;
7675             break;
7676
7677           case TwoMachinesPlay:
7678             machineWhite = (cps->twoMachinesColor[0] == 'w');
7679             break;
7680         }
7681         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7682             if (appData.debugMode) {
7683                 fprintf(debugFP,
7684                         "Ignoring move out of turn by %s, gameMode %d"
7685                         ", forwardMost %d\n",
7686                         cps->which, gameMode, forwardMostMove);
7687             }
7688             return;
7689         }
7690
7691     if (appData.debugMode) { int f = forwardMostMove;
7692         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7693                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7694                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7695     }
7696         if(cps->alphaRank) AlphaRank(machineMove, 4);
7697         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7698                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7699             /* Machine move could not be parsed; ignore it. */
7700           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7701                     machineMove, _(cps->which));
7702             DisplayError(buf1, 0);
7703             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7704                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7705             if (gameMode == TwoMachinesPlay) {
7706               GameEnds(machineWhite ? BlackWins : WhiteWins,
7707                        buf1, GE_XBOARD);
7708             }
7709             return;
7710         }
7711
7712         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7713         /* So we have to redo legality test with true e.p. status here,  */
7714         /* to make sure an illegal e.p. capture does not slip through,   */
7715         /* to cause a forfeit on a justified illegal-move complaint      */
7716         /* of the opponent.                                              */
7717         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7718            ChessMove moveType;
7719            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7720                              fromY, fromX, toY, toX, promoChar);
7721             if (appData.debugMode) {
7722                 int i;
7723                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7724                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7725                 fprintf(debugFP, "castling rights\n");
7726             }
7727             if(moveType == IllegalMove) {
7728               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7729                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7730                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7731                            buf1, GE_XBOARD);
7732                 return;
7733            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7734            /* [HGM] Kludge to handle engines that send FRC-style castling
7735               when they shouldn't (like TSCP-Gothic) */
7736            switch(moveType) {
7737              case WhiteASideCastleFR:
7738              case BlackASideCastleFR:
7739                toX+=2;
7740                currentMoveString[2]++;
7741                break;
7742              case WhiteHSideCastleFR:
7743              case BlackHSideCastleFR:
7744                toX--;
7745                currentMoveString[2]--;
7746                break;
7747              default: ; // nothing to do, but suppresses warning of pedantic compilers
7748            }
7749         }
7750         hintRequested = FALSE;
7751         lastHint[0] = NULLCHAR;
7752         bookRequested = FALSE;
7753         /* Program may be pondering now */
7754         cps->maybeThinking = TRUE;
7755         if (cps->sendTime == 2) cps->sendTime = 1;
7756         if (cps->offeredDraw) cps->offeredDraw--;
7757
7758         /* [AS] Save move info*/
7759         pvInfoList[ forwardMostMove ].score = programStats.score;
7760         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7761         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7762
7763         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7764
7765         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7766         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7767             int count = 0;
7768
7769             while( count < adjudicateLossPlies ) {
7770                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7771
7772                 if( count & 1 ) {
7773                     score = -score; /* Flip score for winning side */
7774                 }
7775
7776                 if( score > adjudicateLossThreshold ) {
7777                     break;
7778                 }
7779
7780                 count++;
7781             }
7782
7783             if( count >= adjudicateLossPlies ) {
7784                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7785
7786                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7787                     "Xboard adjudication",
7788                     GE_XBOARD );
7789
7790                 return;
7791             }
7792         }
7793
7794         if(Adjudicate(cps)) {
7795             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7796             return; // [HGM] adjudicate: for all automatic game ends
7797         }
7798
7799 #if ZIPPY
7800         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7801             first.initDone) {
7802           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7803                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7804                 SendToICS("draw ");
7805                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7806           }
7807           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7808           ics_user_moved = 1;
7809           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7810                 char buf[3*MSG_SIZ];
7811
7812                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7813                         programStats.score / 100.,
7814                         programStats.depth,
7815                         programStats.time / 100.,
7816                         (unsigned int)programStats.nodes,
7817                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7818                         programStats.movelist);
7819                 SendToICS(buf);
7820 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7821           }
7822         }
7823 #endif
7824
7825         /* [AS] Clear stats for next move */
7826         ClearProgramStats();
7827         thinkOutput[0] = NULLCHAR;
7828         hiddenThinkOutputState = 0;
7829
7830         bookHit = NULL;
7831         if (gameMode == TwoMachinesPlay) {
7832             /* [HGM] relaying draw offers moved to after reception of move */
7833             /* and interpreting offer as claim if it brings draw condition */
7834             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7835                 SendToProgram("draw\n", cps->other);
7836             }
7837             if (cps->other->sendTime) {
7838                 SendTimeRemaining(cps->other,
7839                                   cps->other->twoMachinesColor[0] == 'w');
7840             }
7841             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7842             if (firstMove && !bookHit) {
7843                 firstMove = FALSE;
7844                 if (cps->other->useColors) {
7845                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7846                 }
7847                 SendToProgram("go\n", cps->other);
7848             }
7849             cps->other->maybeThinking = TRUE;
7850         }
7851
7852         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7853
7854         if (!pausing && appData.ringBellAfterMoves) {
7855             RingBell();
7856         }
7857
7858         /*
7859          * Reenable menu items that were disabled while
7860          * machine was thinking
7861          */
7862         if (gameMode != TwoMachinesPlay)
7863             SetUserThinkingEnables();
7864
7865         // [HGM] book: after book hit opponent has received move and is now in force mode
7866         // force the book reply into it, and then fake that it outputted this move by jumping
7867         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7868         if(bookHit) {
7869                 static char bookMove[MSG_SIZ]; // a bit generous?
7870
7871                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7872                 strcat(bookMove, bookHit);
7873                 message = bookMove;
7874                 cps = cps->other;
7875                 programStats.nodes = programStats.depth = programStats.time =
7876                 programStats.score = programStats.got_only_move = 0;
7877                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7878
7879                 if(cps->lastPing != cps->lastPong) {
7880                     savedMessage = message; // args for deferred call
7881                     savedState = cps;
7882                     ScheduleDelayedEvent(DeferredBookMove, 10);
7883                     return;
7884                 }
7885                 goto FakeBookMove;
7886         }
7887
7888         return;
7889     }
7890
7891     /* Set special modes for chess engines.  Later something general
7892      *  could be added here; for now there is just one kludge feature,
7893      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7894      *  when "xboard" is given as an interactive command.
7895      */
7896     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7897         cps->useSigint = FALSE;
7898         cps->useSigterm = FALSE;
7899     }
7900     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7901       ParseFeatures(message+8, cps);
7902       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7903     }
7904
7905     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7906       int dummy, s=6; char buf[MSG_SIZ];
7907       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7908       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7909       ParseFEN(boards[0], &dummy, message+s);
7910       DrawPosition(TRUE, boards[0]);
7911       startedFromSetupPosition = TRUE;
7912       return;
7913     }
7914     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7915      * want this, I was asked to put it in, and obliged.
7916      */
7917     if (!strncmp(message, "setboard ", 9)) {
7918         Board initial_position;
7919
7920         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7921
7922         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7923             DisplayError(_("Bad FEN received from engine"), 0);
7924             return ;
7925         } else {
7926            Reset(TRUE, FALSE);
7927            CopyBoard(boards[0], initial_position);
7928            initialRulePlies = FENrulePlies;
7929            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7930            else gameMode = MachinePlaysBlack;
7931            DrawPosition(FALSE, boards[currentMove]);
7932         }
7933         return;
7934     }
7935
7936     /*
7937      * Look for communication commands
7938      */
7939     if (!strncmp(message, "telluser ", 9)) {
7940         if(message[9] == '\\' && message[10] == '\\')
7941             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7942         DisplayNote(message + 9);
7943         return;
7944     }
7945     if (!strncmp(message, "tellusererror ", 14)) {
7946         cps->userError = 1;
7947         if(message[14] == '\\' && message[15] == '\\')
7948             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7949         DisplayError(message + 14, 0);
7950         return;
7951     }
7952     if (!strncmp(message, "tellopponent ", 13)) {
7953       if (appData.icsActive) {
7954         if (loggedOn) {
7955           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7956           SendToICS(buf1);
7957         }
7958       } else {
7959         DisplayNote(message + 13);
7960       }
7961       return;
7962     }
7963     if (!strncmp(message, "tellothers ", 11)) {
7964       if (appData.icsActive) {
7965         if (loggedOn) {
7966           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7967           SendToICS(buf1);
7968         }
7969       }
7970       return;
7971     }
7972     if (!strncmp(message, "tellall ", 8)) {
7973       if (appData.icsActive) {
7974         if (loggedOn) {
7975           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7976           SendToICS(buf1);
7977         }
7978       } else {
7979         DisplayNote(message + 8);
7980       }
7981       return;
7982     }
7983     if (strncmp(message, "warning", 7) == 0) {
7984         /* Undocumented feature, use tellusererror in new code */
7985         DisplayError(message, 0);
7986         return;
7987     }
7988     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7989         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7990         strcat(realname, " query");
7991         AskQuestion(realname, buf2, buf1, cps->pr);
7992         return;
7993     }
7994     /* Commands from the engine directly to ICS.  We don't allow these to be
7995      *  sent until we are logged on. Crafty kibitzes have been known to
7996      *  interfere with the login process.
7997      */
7998     if (loggedOn) {
7999         if (!strncmp(message, "tellics ", 8)) {
8000             SendToICS(message + 8);
8001             SendToICS("\n");
8002             return;
8003         }
8004         if (!strncmp(message, "tellicsnoalias ", 15)) {
8005             SendToICS(ics_prefix);
8006             SendToICS(message + 15);
8007             SendToICS("\n");
8008             return;
8009         }
8010         /* The following are for backward compatibility only */
8011         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8012             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8013             SendToICS(ics_prefix);
8014             SendToICS(message);
8015             SendToICS("\n");
8016             return;
8017         }
8018     }
8019     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8020         return;
8021     }
8022     /*
8023      * If the move is illegal, cancel it and redraw the board.
8024      * Also deal with other error cases.  Matching is rather loose
8025      * here to accommodate engines written before the spec.
8026      */
8027     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8028         strncmp(message, "Error", 5) == 0) {
8029         if (StrStr(message, "name") ||
8030             StrStr(message, "rating") || StrStr(message, "?") ||
8031             StrStr(message, "result") || StrStr(message, "board") ||
8032             StrStr(message, "bk") || StrStr(message, "computer") ||
8033             StrStr(message, "variant") || StrStr(message, "hint") ||
8034             StrStr(message, "random") || StrStr(message, "depth") ||
8035             StrStr(message, "accepted")) {
8036             return;
8037         }
8038         if (StrStr(message, "protover")) {
8039           /* Program is responding to input, so it's apparently done
8040              initializing, and this error message indicates it is
8041              protocol version 1.  So we don't need to wait any longer
8042              for it to initialize and send feature commands. */
8043           FeatureDone(cps, 1);
8044           cps->protocolVersion = 1;
8045           return;
8046         }
8047         cps->maybeThinking = FALSE;
8048
8049         if (StrStr(message, "draw")) {
8050             /* Program doesn't have "draw" command */
8051             cps->sendDrawOffers = 0;
8052             return;
8053         }
8054         if (cps->sendTime != 1 &&
8055             (StrStr(message, "time") || StrStr(message, "otim"))) {
8056           /* Program apparently doesn't have "time" or "otim" command */
8057           cps->sendTime = 0;
8058           return;
8059         }
8060         if (StrStr(message, "analyze")) {
8061             cps->analysisSupport = FALSE;
8062             cps->analyzing = FALSE;
8063             Reset(FALSE, TRUE);
8064             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8065             DisplayError(buf2, 0);
8066             return;
8067         }
8068         if (StrStr(message, "(no matching move)st")) {
8069           /* Special kludge for GNU Chess 4 only */
8070           cps->stKludge = TRUE;
8071           SendTimeControl(cps, movesPerSession, timeControl,
8072                           timeIncrement, appData.searchDepth,
8073                           searchTime);
8074           return;
8075         }
8076         if (StrStr(message, "(no matching move)sd")) {
8077           /* Special kludge for GNU Chess 4 only */
8078           cps->sdKludge = TRUE;
8079           SendTimeControl(cps, movesPerSession, timeControl,
8080                           timeIncrement, appData.searchDepth,
8081                           searchTime);
8082           return;
8083         }
8084         if (!StrStr(message, "llegal")) {
8085             return;
8086         }
8087         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8088             gameMode == IcsIdle) return;
8089         if (forwardMostMove <= backwardMostMove) return;
8090         if (pausing) PauseEvent();
8091       if(appData.forceIllegal) {
8092             // [HGM] illegal: machine refused move; force position after move into it
8093           SendToProgram("force\n", cps);
8094           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8095                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8096                 // when black is to move, while there might be nothing on a2 or black
8097                 // might already have the move. So send the board as if white has the move.
8098                 // But first we must change the stm of the engine, as it refused the last move
8099                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8100                 if(WhiteOnMove(forwardMostMove)) {
8101                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8102                     SendBoard(cps, forwardMostMove); // kludgeless board
8103                 } else {
8104                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8105                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8106                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8107                 }
8108           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8109             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8110                  gameMode == TwoMachinesPlay)
8111               SendToProgram("go\n", cps);
8112             return;
8113       } else
8114         if (gameMode == PlayFromGameFile) {
8115             /* Stop reading this game file */
8116             gameMode = EditGame;
8117             ModeHighlight();
8118         }
8119         /* [HGM] illegal-move claim should forfeit game when Xboard */
8120         /* only passes fully legal moves                            */
8121         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8122             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8123                                 "False illegal-move claim", GE_XBOARD );
8124             return; // do not take back move we tested as valid
8125         }
8126         currentMove = forwardMostMove-1;
8127         DisplayMove(currentMove-1); /* before DisplayMoveError */
8128         SwitchClocks(forwardMostMove-1); // [HGM] race
8129         DisplayBothClocks();
8130         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8131                 parseList[currentMove], _(cps->which));
8132         DisplayMoveError(buf1);
8133         DrawPosition(FALSE, boards[currentMove]);
8134         return;
8135     }
8136     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8137         /* Program has a broken "time" command that
8138            outputs a string not ending in newline.
8139            Don't use it. */
8140         cps->sendTime = 0;
8141     }
8142
8143     /*
8144      * If chess program startup fails, exit with an error message.
8145      * Attempts to recover here are futile.
8146      */
8147     if ((StrStr(message, "unknown host") != NULL)
8148         || (StrStr(message, "No remote directory") != NULL)
8149         || (StrStr(message, "not found") != NULL)
8150         || (StrStr(message, "No such file") != NULL)
8151         || (StrStr(message, "can't alloc") != NULL)
8152         || (StrStr(message, "Permission denied") != NULL)) {
8153
8154         cps->maybeThinking = FALSE;
8155         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8156                 _(cps->which), cps->program, cps->host, message);
8157         RemoveInputSource(cps->isr);
8158         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8159             if(cps == &first) appData.noChessProgram = TRUE;
8160             DisplayError(buf1, 0);
8161         }
8162         return;
8163     }
8164
8165     /*
8166      * Look for hint output
8167      */
8168     if (sscanf(message, "Hint: %s", buf1) == 1) {
8169         if (cps == &first && hintRequested) {
8170             hintRequested = FALSE;
8171             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8172                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8173                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8174                                     PosFlags(forwardMostMove),
8175                                     fromY, fromX, toY, toX, promoChar, buf1);
8176                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8177                 DisplayInformation(buf2);
8178             } else {
8179                 /* Hint move could not be parsed!? */
8180               snprintf(buf2, sizeof(buf2),
8181                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8182                         buf1, _(cps->which));
8183                 DisplayError(buf2, 0);
8184             }
8185         } else {
8186           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8187         }
8188         return;
8189     }
8190
8191     /*
8192      * Ignore other messages if game is not in progress
8193      */
8194     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8195         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8196
8197     /*
8198      * look for win, lose, draw, or draw offer
8199      */
8200     if (strncmp(message, "1-0", 3) == 0) {
8201         char *p, *q, *r = "";
8202         p = strchr(message, '{');
8203         if (p) {
8204             q = strchr(p, '}');
8205             if (q) {
8206                 *q = NULLCHAR;
8207                 r = p + 1;
8208             }
8209         }
8210         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8211         return;
8212     } else if (strncmp(message, "0-1", 3) == 0) {
8213         char *p, *q, *r = "";
8214         p = strchr(message, '{');
8215         if (p) {
8216             q = strchr(p, '}');
8217             if (q) {
8218                 *q = NULLCHAR;
8219                 r = p + 1;
8220             }
8221         }
8222         /* Kludge for Arasan 4.1 bug */
8223         if (strcmp(r, "Black resigns") == 0) {
8224             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8225             return;
8226         }
8227         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8228         return;
8229     } else if (strncmp(message, "1/2", 3) == 0) {
8230         char *p, *q, *r = "";
8231         p = strchr(message, '{');
8232         if (p) {
8233             q = strchr(p, '}');
8234             if (q) {
8235                 *q = NULLCHAR;
8236                 r = p + 1;
8237             }
8238         }
8239
8240         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8241         return;
8242
8243     } else if (strncmp(message, "White resign", 12) == 0) {
8244         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8245         return;
8246     } else if (strncmp(message, "Black resign", 12) == 0) {
8247         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8248         return;
8249     } else if (strncmp(message, "White matches", 13) == 0 ||
8250                strncmp(message, "Black matches", 13) == 0   ) {
8251         /* [HGM] ignore GNUShogi noises */
8252         return;
8253     } else if (strncmp(message, "White", 5) == 0 &&
8254                message[5] != '(' &&
8255                StrStr(message, "Black") == NULL) {
8256         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8257         return;
8258     } else if (strncmp(message, "Black", 5) == 0 &&
8259                message[5] != '(') {
8260         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8261         return;
8262     } else if (strcmp(message, "resign") == 0 ||
8263                strcmp(message, "computer resigns") == 0) {
8264         switch (gameMode) {
8265           case MachinePlaysBlack:
8266           case IcsPlayingBlack:
8267             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8268             break;
8269           case MachinePlaysWhite:
8270           case IcsPlayingWhite:
8271             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8272             break;
8273           case TwoMachinesPlay:
8274             if (cps->twoMachinesColor[0] == 'w')
8275               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8276             else
8277               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8278             break;
8279           default:
8280             /* can't happen */
8281             break;
8282         }
8283         return;
8284     } else if (strncmp(message, "opponent mates", 14) == 0) {
8285         switch (gameMode) {
8286           case MachinePlaysBlack:
8287           case IcsPlayingBlack:
8288             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8289             break;
8290           case MachinePlaysWhite:
8291           case IcsPlayingWhite:
8292             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8293             break;
8294           case TwoMachinesPlay:
8295             if (cps->twoMachinesColor[0] == 'w')
8296               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8297             else
8298               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8299             break;
8300           default:
8301             /* can't happen */
8302             break;
8303         }
8304         return;
8305     } else if (strncmp(message, "computer mates", 14) == 0) {
8306         switch (gameMode) {
8307           case MachinePlaysBlack:
8308           case IcsPlayingBlack:
8309             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8310             break;
8311           case MachinePlaysWhite:
8312           case IcsPlayingWhite:
8313             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8314             break;
8315           case TwoMachinesPlay:
8316             if (cps->twoMachinesColor[0] == 'w')
8317               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8318             else
8319               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8320             break;
8321           default:
8322             /* can't happen */
8323             break;
8324         }
8325         return;
8326     } else if (strncmp(message, "checkmate", 9) == 0) {
8327         if (WhiteOnMove(forwardMostMove)) {
8328             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8329         } else {
8330             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8331         }
8332         return;
8333     } else if (strstr(message, "Draw") != NULL ||
8334                strstr(message, "game is a draw") != NULL) {
8335         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8336         return;
8337     } else if (strstr(message, "offer") != NULL &&
8338                strstr(message, "draw") != NULL) {
8339 #if ZIPPY
8340         if (appData.zippyPlay && first.initDone) {
8341             /* Relay offer to ICS */
8342             SendToICS(ics_prefix);
8343             SendToICS("draw\n");
8344         }
8345 #endif
8346         cps->offeredDraw = 2; /* valid until this engine moves twice */
8347         if (gameMode == TwoMachinesPlay) {
8348             if (cps->other->offeredDraw) {
8349                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8350             /* [HGM] in two-machine mode we delay relaying draw offer      */
8351             /* until after we also have move, to see if it is really claim */
8352             }
8353         } else if (gameMode == MachinePlaysWhite ||
8354                    gameMode == MachinePlaysBlack) {
8355           if (userOfferedDraw) {
8356             DisplayInformation(_("Machine accepts your draw offer"));
8357             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8358           } else {
8359             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8360           }
8361         }
8362     }
8363
8364
8365     /*
8366      * Look for thinking output
8367      */
8368     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8369           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8370                                 ) {
8371         int plylev, mvleft, mvtot, curscore, time;
8372         char mvname[MOVE_LEN];
8373         u64 nodes; // [DM]
8374         char plyext;
8375         int ignore = FALSE;
8376         int prefixHint = FALSE;
8377         mvname[0] = NULLCHAR;
8378
8379         switch (gameMode) {
8380           case MachinePlaysBlack:
8381           case IcsPlayingBlack:
8382             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8383             break;
8384           case MachinePlaysWhite:
8385           case IcsPlayingWhite:
8386             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8387             break;
8388           case AnalyzeMode:
8389           case AnalyzeFile:
8390             break;
8391           case IcsObserving: /* [DM] icsEngineAnalyze */
8392             if (!appData.icsEngineAnalyze) ignore = TRUE;
8393             break;
8394           case TwoMachinesPlay:
8395             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8396                 ignore = TRUE;
8397             }
8398             break;
8399           default:
8400             ignore = TRUE;
8401             break;
8402         }
8403
8404         if (!ignore) {
8405             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8406             buf1[0] = NULLCHAR;
8407             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8408                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8409
8410                 if (plyext != ' ' && plyext != '\t') {
8411                     time *= 100;
8412                 }
8413
8414                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8415                 if( cps->scoreIsAbsolute &&
8416                     ( gameMode == MachinePlaysBlack ||
8417                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8418                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8419                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8420                      !WhiteOnMove(currentMove)
8421                     ) )
8422                 {
8423                     curscore = -curscore;
8424                 }
8425
8426
8427                 tempStats.depth = plylev;
8428                 tempStats.nodes = nodes;
8429                 tempStats.time = time;
8430                 tempStats.score = curscore;
8431                 tempStats.got_only_move = 0;
8432
8433                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8434                         int ticklen;
8435
8436                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8437                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8438                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8439                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8440                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8441                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8442                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8443                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8444                 }
8445
8446                 /* Buffer overflow protection */
8447                 if (buf1[0] != NULLCHAR) {
8448                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8449                         && appData.debugMode) {
8450                         fprintf(debugFP,
8451                                 "PV is too long; using the first %u bytes.\n",
8452                                 (unsigned) sizeof(tempStats.movelist) - 1);
8453                     }
8454
8455                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8456                 } else {
8457                     sprintf(tempStats.movelist, " no PV\n");
8458                 }
8459
8460                 if (tempStats.seen_stat) {
8461                     tempStats.ok_to_send = 1;
8462                 }
8463
8464                 if (strchr(tempStats.movelist, '(') != NULL) {
8465                     tempStats.line_is_book = 1;
8466                     tempStats.nr_moves = 0;
8467                     tempStats.moves_left = 0;
8468                 } else {
8469                     tempStats.line_is_book = 0;
8470                 }
8471
8472                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8473                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8474
8475                 SendProgramStatsToFrontend( cps, &tempStats );
8476
8477                 /*
8478                     [AS] Protect the thinkOutput buffer from overflow... this
8479                     is only useful if buf1 hasn't overflowed first!
8480                 */
8481                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8482                          plylev,
8483                          (gameMode == TwoMachinesPlay ?
8484                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8485                          ((double) curscore) / 100.0,
8486                          prefixHint ? lastHint : "",
8487                          prefixHint ? " " : "" );
8488
8489                 if( buf1[0] != NULLCHAR ) {
8490                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8491
8492                     if( strlen(buf1) > max_len ) {
8493                         if( appData.debugMode) {
8494                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8495                         }
8496                         buf1[max_len+1] = '\0';
8497                     }
8498
8499                     strcat( thinkOutput, buf1 );
8500                 }
8501
8502                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8503                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8504                     DisplayMove(currentMove - 1);
8505                 }
8506                 return;
8507
8508             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8509                 /* crafty (9.25+) says "(only move) <move>"
8510                  * if there is only 1 legal move
8511                  */
8512                 sscanf(p, "(only move) %s", buf1);
8513                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8514                 sprintf(programStats.movelist, "%s (only move)", buf1);
8515                 programStats.depth = 1;
8516                 programStats.nr_moves = 1;
8517                 programStats.moves_left = 1;
8518                 programStats.nodes = 1;
8519                 programStats.time = 1;
8520                 programStats.got_only_move = 1;
8521
8522                 /* Not really, but we also use this member to
8523                    mean "line isn't going to change" (Crafty
8524                    isn't searching, so stats won't change) */
8525                 programStats.line_is_book = 1;
8526
8527                 SendProgramStatsToFrontend( cps, &programStats );
8528
8529                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8530                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8531                     DisplayMove(currentMove - 1);
8532                 }
8533                 return;
8534             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8535                               &time, &nodes, &plylev, &mvleft,
8536                               &mvtot, mvname) >= 5) {
8537                 /* The stat01: line is from Crafty (9.29+) in response
8538                    to the "." command */
8539                 programStats.seen_stat = 1;
8540                 cps->maybeThinking = TRUE;
8541
8542                 if (programStats.got_only_move || !appData.periodicUpdates)
8543                   return;
8544
8545                 programStats.depth = plylev;
8546                 programStats.time = time;
8547                 programStats.nodes = nodes;
8548                 programStats.moves_left = mvleft;
8549                 programStats.nr_moves = mvtot;
8550                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8551                 programStats.ok_to_send = 1;
8552                 programStats.movelist[0] = '\0';
8553
8554                 SendProgramStatsToFrontend( cps, &programStats );
8555
8556                 return;
8557
8558             } else if (strncmp(message,"++",2) == 0) {
8559                 /* Crafty 9.29+ outputs this */
8560                 programStats.got_fail = 2;
8561                 return;
8562
8563             } else if (strncmp(message,"--",2) == 0) {
8564                 /* Crafty 9.29+ outputs this */
8565                 programStats.got_fail = 1;
8566                 return;
8567
8568             } else if (thinkOutput[0] != NULLCHAR &&
8569                        strncmp(message, "    ", 4) == 0) {
8570                 unsigned message_len;
8571
8572                 p = message;
8573                 while (*p && *p == ' ') p++;
8574
8575                 message_len = strlen( p );
8576
8577                 /* [AS] Avoid buffer overflow */
8578                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8579                     strcat(thinkOutput, " ");
8580                     strcat(thinkOutput, p);
8581                 }
8582
8583                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8584                     strcat(programStats.movelist, " ");
8585                     strcat(programStats.movelist, p);
8586                 }
8587
8588                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8589                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8590                     DisplayMove(currentMove - 1);
8591                 }
8592                 return;
8593             }
8594         }
8595         else {
8596             buf1[0] = NULLCHAR;
8597
8598             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8599                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8600             {
8601                 ChessProgramStats cpstats;
8602
8603                 if (plyext != ' ' && plyext != '\t') {
8604                     time *= 100;
8605                 }
8606
8607                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8608                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8609                     curscore = -curscore;
8610                 }
8611
8612                 cpstats.depth = plylev;
8613                 cpstats.nodes = nodes;
8614                 cpstats.time = time;
8615                 cpstats.score = curscore;
8616                 cpstats.got_only_move = 0;
8617                 cpstats.movelist[0] = '\0';
8618
8619                 if (buf1[0] != NULLCHAR) {
8620                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8621                 }
8622
8623                 cpstats.ok_to_send = 0;
8624                 cpstats.line_is_book = 0;
8625                 cpstats.nr_moves = 0;
8626                 cpstats.moves_left = 0;
8627
8628                 SendProgramStatsToFrontend( cps, &cpstats );
8629             }
8630         }
8631     }
8632 }
8633
8634
8635 /* Parse a game score from the character string "game", and
8636    record it as the history of the current game.  The game
8637    score is NOT assumed to start from the standard position.
8638    The display is not updated in any way.
8639    */
8640 void
8641 ParseGameHistory(game)
8642      char *game;
8643 {
8644     ChessMove moveType;
8645     int fromX, fromY, toX, toY, boardIndex;
8646     char promoChar;
8647     char *p, *q;
8648     char buf[MSG_SIZ];
8649
8650     if (appData.debugMode)
8651       fprintf(debugFP, "Parsing game history: %s\n", game);
8652
8653     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8654     gameInfo.site = StrSave(appData.icsHost);
8655     gameInfo.date = PGNDate();
8656     gameInfo.round = StrSave("-");
8657
8658     /* Parse out names of players */
8659     while (*game == ' ') game++;
8660     p = buf;
8661     while (*game != ' ') *p++ = *game++;
8662     *p = NULLCHAR;
8663     gameInfo.white = StrSave(buf);
8664     while (*game == ' ') game++;
8665     p = buf;
8666     while (*game != ' ' && *game != '\n') *p++ = *game++;
8667     *p = NULLCHAR;
8668     gameInfo.black = StrSave(buf);
8669
8670     /* Parse moves */
8671     boardIndex = blackPlaysFirst ? 1 : 0;
8672     yynewstr(game);
8673     for (;;) {
8674         yyboardindex = boardIndex;
8675         moveType = (ChessMove) Myylex();
8676         switch (moveType) {
8677           case IllegalMove:             /* maybe suicide chess, etc. */
8678   if (appData.debugMode) {
8679     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8680     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8681     setbuf(debugFP, NULL);
8682   }
8683           case WhitePromotion:
8684           case BlackPromotion:
8685           case WhiteNonPromotion:
8686           case BlackNonPromotion:
8687           case NormalMove:
8688           case WhiteCapturesEnPassant:
8689           case BlackCapturesEnPassant:
8690           case WhiteKingSideCastle:
8691           case WhiteQueenSideCastle:
8692           case BlackKingSideCastle:
8693           case BlackQueenSideCastle:
8694           case WhiteKingSideCastleWild:
8695           case WhiteQueenSideCastleWild:
8696           case BlackKingSideCastleWild:
8697           case BlackQueenSideCastleWild:
8698           /* PUSH Fabien */
8699           case WhiteHSideCastleFR:
8700           case WhiteASideCastleFR:
8701           case BlackHSideCastleFR:
8702           case BlackASideCastleFR:
8703           /* POP Fabien */
8704             fromX = currentMoveString[0] - AAA;
8705             fromY = currentMoveString[1] - ONE;
8706             toX = currentMoveString[2] - AAA;
8707             toY = currentMoveString[3] - ONE;
8708             promoChar = currentMoveString[4];
8709             break;
8710           case WhiteDrop:
8711           case BlackDrop:
8712             fromX = moveType == WhiteDrop ?
8713               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8714             (int) CharToPiece(ToLower(currentMoveString[0]));
8715             fromY = DROP_RANK;
8716             toX = currentMoveString[2] - AAA;
8717             toY = currentMoveString[3] - ONE;
8718             promoChar = NULLCHAR;
8719             break;
8720           case AmbiguousMove:
8721             /* bug? */
8722             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8723   if (appData.debugMode) {
8724     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8725     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8726     setbuf(debugFP, NULL);
8727   }
8728             DisplayError(buf, 0);
8729             return;
8730           case ImpossibleMove:
8731             /* bug? */
8732             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8733   if (appData.debugMode) {
8734     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8735     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8736     setbuf(debugFP, NULL);
8737   }
8738             DisplayError(buf, 0);
8739             return;
8740           case EndOfFile:
8741             if (boardIndex < backwardMostMove) {
8742                 /* Oops, gap.  How did that happen? */
8743                 DisplayError(_("Gap in move list"), 0);
8744                 return;
8745             }
8746             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8747             if (boardIndex > forwardMostMove) {
8748                 forwardMostMove = boardIndex;
8749             }
8750             return;
8751           case ElapsedTime:
8752             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8753                 strcat(parseList[boardIndex-1], " ");
8754                 strcat(parseList[boardIndex-1], yy_text);
8755             }
8756             continue;
8757           case Comment:
8758           case PGNTag:
8759           case NAG:
8760           default:
8761             /* ignore */
8762             continue;
8763           case WhiteWins:
8764           case BlackWins:
8765           case GameIsDrawn:
8766           case GameUnfinished:
8767             if (gameMode == IcsExamining) {
8768                 if (boardIndex < backwardMostMove) {
8769                     /* Oops, gap.  How did that happen? */
8770                     return;
8771                 }
8772                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8773                 return;
8774             }
8775             gameInfo.result = moveType;
8776             p = strchr(yy_text, '{');
8777             if (p == NULL) p = strchr(yy_text, '(');
8778             if (p == NULL) {
8779                 p = yy_text;
8780                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8781             } else {
8782                 q = strchr(p, *p == '{' ? '}' : ')');
8783                 if (q != NULL) *q = NULLCHAR;
8784                 p++;
8785             }
8786             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8787             gameInfo.resultDetails = StrSave(p);
8788             continue;
8789         }
8790         if (boardIndex >= forwardMostMove &&
8791             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8792             backwardMostMove = blackPlaysFirst ? 1 : 0;
8793             return;
8794         }
8795         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8796                                  fromY, fromX, toY, toX, promoChar,
8797                                  parseList[boardIndex]);
8798         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8799         /* currentMoveString is set as a side-effect of yylex */
8800         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8801         strcat(moveList[boardIndex], "\n");
8802         boardIndex++;
8803         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8804         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8805           case MT_NONE:
8806           case MT_STALEMATE:
8807           default:
8808             break;
8809           case MT_CHECK:
8810             if(gameInfo.variant != VariantShogi)
8811                 strcat(parseList[boardIndex - 1], "+");
8812             break;
8813           case MT_CHECKMATE:
8814           case MT_STAINMATE:
8815             strcat(parseList[boardIndex - 1], "#");
8816             break;
8817         }
8818     }
8819 }
8820
8821
8822 /* Apply a move to the given board  */
8823 void
8824 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8825      int fromX, fromY, toX, toY;
8826      int promoChar;
8827      Board board;
8828 {
8829   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8830   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8831
8832     /* [HGM] compute & store e.p. status and castling rights for new position */
8833     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8834
8835       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8836       oldEP = (signed char)board[EP_STATUS];
8837       board[EP_STATUS] = EP_NONE;
8838
8839       if( board[toY][toX] != EmptySquare )
8840            board[EP_STATUS] = EP_CAPTURE;
8841
8842   if (fromY == DROP_RANK) {
8843         /* must be first */
8844         piece = board[toY][toX] = (ChessSquare) fromX;
8845   } else {
8846       int i;
8847
8848       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8849            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8850                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8851       } else
8852       if( board[fromY][fromX] == WhitePawn ) {
8853            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8854                board[EP_STATUS] = EP_PAWN_MOVE;
8855            if( toY-fromY==2) {
8856                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8857                         gameInfo.variant != VariantBerolina || toX < fromX)
8858                       board[EP_STATUS] = toX | berolina;
8859                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8860                         gameInfo.variant != VariantBerolina || toX > fromX)
8861                       board[EP_STATUS] = toX;
8862            }
8863       } else
8864       if( board[fromY][fromX] == BlackPawn ) {
8865            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8866                board[EP_STATUS] = EP_PAWN_MOVE;
8867            if( toY-fromY== -2) {
8868                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8869                         gameInfo.variant != VariantBerolina || toX < fromX)
8870                       board[EP_STATUS] = toX | berolina;
8871                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8872                         gameInfo.variant != VariantBerolina || toX > fromX)
8873                       board[EP_STATUS] = toX;
8874            }
8875        }
8876
8877        for(i=0; i<nrCastlingRights; i++) {
8878            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8879               board[CASTLING][i] == toX   && castlingRank[i] == toY
8880              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8881        }
8882
8883      if (fromX == toX && fromY == toY) return;
8884
8885      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8886      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8887      if(gameInfo.variant == VariantKnightmate)
8888          king += (int) WhiteUnicorn - (int) WhiteKing;
8889
8890     /* Code added by Tord: */
8891     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8892     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8893         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8894       board[fromY][fromX] = EmptySquare;
8895       board[toY][toX] = EmptySquare;
8896       if((toX > fromX) != (piece == WhiteRook)) {
8897         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8898       } else {
8899         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8900       }
8901     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8902                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8903       board[fromY][fromX] = EmptySquare;
8904       board[toY][toX] = EmptySquare;
8905       if((toX > fromX) != (piece == BlackRook)) {
8906         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8907       } else {
8908         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8909       }
8910     /* End of code added by Tord */
8911
8912     } else if (board[fromY][fromX] == king
8913         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8914         && toY == fromY && toX > fromX+1) {
8915         board[fromY][fromX] = EmptySquare;
8916         board[toY][toX] = king;
8917         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8918         board[fromY][BOARD_RGHT-1] = EmptySquare;
8919     } else if (board[fromY][fromX] == king
8920         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8921                && toY == fromY && toX < fromX-1) {
8922         board[fromY][fromX] = EmptySquare;
8923         board[toY][toX] = king;
8924         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8925         board[fromY][BOARD_LEFT] = EmptySquare;
8926     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8927                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8928                && toY >= BOARD_HEIGHT-promoRank
8929                ) {
8930         /* white pawn promotion */
8931         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8932         if (board[toY][toX] == EmptySquare) {
8933             board[toY][toX] = WhiteQueen;
8934         }
8935         if(gameInfo.variant==VariantBughouse ||
8936            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8937             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8938         board[fromY][fromX] = EmptySquare;
8939     } else if ((fromY == BOARD_HEIGHT-4)
8940                && (toX != fromX)
8941                && gameInfo.variant != VariantXiangqi
8942                && gameInfo.variant != VariantBerolina
8943                && (board[fromY][fromX] == WhitePawn)
8944                && (board[toY][toX] == EmptySquare)) {
8945         board[fromY][fromX] = EmptySquare;
8946         board[toY][toX] = WhitePawn;
8947         captured = board[toY - 1][toX];
8948         board[toY - 1][toX] = EmptySquare;
8949     } else if ((fromY == BOARD_HEIGHT-4)
8950                && (toX == fromX)
8951                && gameInfo.variant == VariantBerolina
8952                && (board[fromY][fromX] == WhitePawn)
8953                && (board[toY][toX] == EmptySquare)) {
8954         board[fromY][fromX] = EmptySquare;
8955         board[toY][toX] = WhitePawn;
8956         if(oldEP & EP_BEROLIN_A) {
8957                 captured = board[fromY][fromX-1];
8958                 board[fromY][fromX-1] = EmptySquare;
8959         }else{  captured = board[fromY][fromX+1];
8960                 board[fromY][fromX+1] = EmptySquare;
8961         }
8962     } else if (board[fromY][fromX] == king
8963         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8964                && toY == fromY && toX > fromX+1) {
8965         board[fromY][fromX] = EmptySquare;
8966         board[toY][toX] = king;
8967         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8968         board[fromY][BOARD_RGHT-1] = EmptySquare;
8969     } else if (board[fromY][fromX] == king
8970         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8971                && toY == fromY && toX < fromX-1) {
8972         board[fromY][fromX] = EmptySquare;
8973         board[toY][toX] = king;
8974         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8975         board[fromY][BOARD_LEFT] = EmptySquare;
8976     } else if (fromY == 7 && fromX == 3
8977                && board[fromY][fromX] == BlackKing
8978                && toY == 7 && toX == 5) {
8979         board[fromY][fromX] = EmptySquare;
8980         board[toY][toX] = BlackKing;
8981         board[fromY][7] = EmptySquare;
8982         board[toY][4] = BlackRook;
8983     } else if (fromY == 7 && fromX == 3
8984                && board[fromY][fromX] == BlackKing
8985                && toY == 7 && toX == 1) {
8986         board[fromY][fromX] = EmptySquare;
8987         board[toY][toX] = BlackKing;
8988         board[fromY][0] = EmptySquare;
8989         board[toY][2] = BlackRook;
8990     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8991                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8992                && toY < promoRank
8993                ) {
8994         /* black pawn promotion */
8995         board[toY][toX] = CharToPiece(ToLower(promoChar));
8996         if (board[toY][toX] == EmptySquare) {
8997             board[toY][toX] = BlackQueen;
8998         }
8999         if(gameInfo.variant==VariantBughouse ||
9000            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9001             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9002         board[fromY][fromX] = EmptySquare;
9003     } else if ((fromY == 3)
9004                && (toX != fromX)
9005                && gameInfo.variant != VariantXiangqi
9006                && gameInfo.variant != VariantBerolina
9007                && (board[fromY][fromX] == BlackPawn)
9008                && (board[toY][toX] == EmptySquare)) {
9009         board[fromY][fromX] = EmptySquare;
9010         board[toY][toX] = BlackPawn;
9011         captured = board[toY + 1][toX];
9012         board[toY + 1][toX] = EmptySquare;
9013     } else if ((fromY == 3)
9014                && (toX == fromX)
9015                && gameInfo.variant == VariantBerolina
9016                && (board[fromY][fromX] == BlackPawn)
9017                && (board[toY][toX] == EmptySquare)) {
9018         board[fromY][fromX] = EmptySquare;
9019         board[toY][toX] = BlackPawn;
9020         if(oldEP & EP_BEROLIN_A) {
9021                 captured = board[fromY][fromX-1];
9022                 board[fromY][fromX-1] = EmptySquare;
9023         }else{  captured = board[fromY][fromX+1];
9024                 board[fromY][fromX+1] = EmptySquare;
9025         }
9026     } else {
9027         board[toY][toX] = board[fromY][fromX];
9028         board[fromY][fromX] = EmptySquare;
9029     }
9030   }
9031
9032     if (gameInfo.holdingsWidth != 0) {
9033
9034       /* !!A lot more code needs to be written to support holdings  */
9035       /* [HGM] OK, so I have written it. Holdings are stored in the */
9036       /* penultimate board files, so they are automaticlly stored   */
9037       /* in the game history.                                       */
9038       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9039                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9040         /* Delete from holdings, by decreasing count */
9041         /* and erasing image if necessary            */
9042         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9043         if(p < (int) BlackPawn) { /* white drop */
9044              p -= (int)WhitePawn;
9045                  p = PieceToNumber((ChessSquare)p);
9046              if(p >= gameInfo.holdingsSize) p = 0;
9047              if(--board[p][BOARD_WIDTH-2] <= 0)
9048                   board[p][BOARD_WIDTH-1] = EmptySquare;
9049              if((int)board[p][BOARD_WIDTH-2] < 0)
9050                         board[p][BOARD_WIDTH-2] = 0;
9051         } else {                  /* black drop */
9052              p -= (int)BlackPawn;
9053                  p = PieceToNumber((ChessSquare)p);
9054              if(p >= gameInfo.holdingsSize) p = 0;
9055              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9056                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9057              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9058                         board[BOARD_HEIGHT-1-p][1] = 0;
9059         }
9060       }
9061       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9062           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9063         /* [HGM] holdings: Add to holdings, if holdings exist */
9064         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9065                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9066                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9067         }
9068         p = (int) captured;
9069         if (p >= (int) BlackPawn) {
9070           p -= (int)BlackPawn;
9071           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9072                   /* in Shogi restore piece to its original  first */
9073                   captured = (ChessSquare) (DEMOTED captured);
9074                   p = DEMOTED p;
9075           }
9076           p = PieceToNumber((ChessSquare)p);
9077           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9078           board[p][BOARD_WIDTH-2]++;
9079           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9080         } else {
9081           p -= (int)WhitePawn;
9082           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9083                   captured = (ChessSquare) (DEMOTED captured);
9084                   p = DEMOTED p;
9085           }
9086           p = PieceToNumber((ChessSquare)p);
9087           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9088           board[BOARD_HEIGHT-1-p][1]++;
9089           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9090         }
9091       }
9092     } else if (gameInfo.variant == VariantAtomic) {
9093       if (captured != EmptySquare) {
9094         int y, x;
9095         for (y = toY-1; y <= toY+1; y++) {
9096           for (x = toX-1; x <= toX+1; x++) {
9097             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9098                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9099               board[y][x] = EmptySquare;
9100             }
9101           }
9102         }
9103         board[toY][toX] = EmptySquare;
9104       }
9105     }
9106     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9107         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9108     } else
9109     if(promoChar == '+') {
9110         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9111         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9112     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9113         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9114     }
9115     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9116                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9117         // [HGM] superchess: take promotion piece out of holdings
9118         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9119         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9120             if(!--board[k][BOARD_WIDTH-2])
9121                 board[k][BOARD_WIDTH-1] = EmptySquare;
9122         } else {
9123             if(!--board[BOARD_HEIGHT-1-k][1])
9124                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9125         }
9126     }
9127
9128 }
9129
9130 /* Updates forwardMostMove */
9131 void
9132 MakeMove(fromX, fromY, toX, toY, promoChar)
9133      int fromX, fromY, toX, toY;
9134      int promoChar;
9135 {
9136 //    forwardMostMove++; // [HGM] bare: moved downstream
9137
9138     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9139         int timeLeft; static int lastLoadFlag=0; int king, piece;
9140         piece = boards[forwardMostMove][fromY][fromX];
9141         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9142         if(gameInfo.variant == VariantKnightmate)
9143             king += (int) WhiteUnicorn - (int) WhiteKing;
9144         if(forwardMostMove == 0) {
9145             if(blackPlaysFirst)
9146                 fprintf(serverMoves, "%s;", second.tidy);
9147             fprintf(serverMoves, "%s;", first.tidy);
9148             if(!blackPlaysFirst)
9149                 fprintf(serverMoves, "%s;", second.tidy);
9150         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9151         lastLoadFlag = loadFlag;
9152         // print base move
9153         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9154         // print castling suffix
9155         if( toY == fromY && piece == king ) {
9156             if(toX-fromX > 1)
9157                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9158             if(fromX-toX >1)
9159                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9160         }
9161         // e.p. suffix
9162         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9163              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9164              boards[forwardMostMove][toY][toX] == EmptySquare
9165              && fromX != toX && fromY != toY)
9166                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9167         // promotion suffix
9168         if(promoChar != NULLCHAR)
9169                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9170         if(!loadFlag) {
9171             fprintf(serverMoves, "/%d/%d",
9172                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9173             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9174             else                      timeLeft = blackTimeRemaining/1000;
9175             fprintf(serverMoves, "/%d", timeLeft);
9176         }
9177         fflush(serverMoves);
9178     }
9179
9180     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9181       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9182                         0, 1);
9183       return;
9184     }
9185     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9186     if (commentList[forwardMostMove+1] != NULL) {
9187         free(commentList[forwardMostMove+1]);
9188         commentList[forwardMostMove+1] = NULL;
9189     }
9190     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9191     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9192     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9193     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9194     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9195     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9196     gameInfo.result = GameUnfinished;
9197     if (gameInfo.resultDetails != NULL) {
9198         free(gameInfo.resultDetails);
9199         gameInfo.resultDetails = NULL;
9200     }
9201     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9202                               moveList[forwardMostMove - 1]);
9203     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9204                              PosFlags(forwardMostMove - 1),
9205                              fromY, fromX, toY, toX, promoChar,
9206                              parseList[forwardMostMove - 1]);
9207     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9208       case MT_NONE:
9209       case MT_STALEMATE:
9210       default:
9211         break;
9212       case MT_CHECK:
9213         if(gameInfo.variant != VariantShogi)
9214             strcat(parseList[forwardMostMove - 1], "+");
9215         break;
9216       case MT_CHECKMATE:
9217       case MT_STAINMATE:
9218         strcat(parseList[forwardMostMove - 1], "#");
9219         break;
9220     }
9221     if (appData.debugMode) {
9222         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9223     }
9224
9225 }
9226
9227 /* Updates currentMove if not pausing */
9228 void
9229 ShowMove(fromX, fromY, toX, toY)
9230 {
9231     int instant = (gameMode == PlayFromGameFile) ?
9232         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9233     if(appData.noGUI) return;
9234     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9235         if (!instant) {
9236             if (forwardMostMove == currentMove + 1) {
9237                 AnimateMove(boards[forwardMostMove - 1],
9238                             fromX, fromY, toX, toY);
9239             }
9240             if (appData.highlightLastMove) {
9241                 SetHighlights(fromX, fromY, toX, toY);
9242             }
9243         }
9244         currentMove = forwardMostMove;
9245     }
9246
9247     if (instant) return;
9248
9249     DisplayMove(currentMove - 1);
9250     DrawPosition(FALSE, boards[currentMove]);
9251     DisplayBothClocks();
9252     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9253 }
9254
9255 void SendEgtPath(ChessProgramState *cps)
9256 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9257         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9258
9259         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9260
9261         while(*p) {
9262             char c, *q = name+1, *r, *s;
9263
9264             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9265             while(*p && *p != ',') *q++ = *p++;
9266             *q++ = ':'; *q = 0;
9267             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9268                 strcmp(name, ",nalimov:") == 0 ) {
9269                 // take nalimov path from the menu-changeable option first, if it is defined
9270               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9271                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9272             } else
9273             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9274                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9275                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9276                 s = r = StrStr(s, ":") + 1; // beginning of path info
9277                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9278                 c = *r; *r = 0;             // temporarily null-terminate path info
9279                     *--q = 0;               // strip of trailig ':' from name
9280                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9281                 *r = c;
9282                 SendToProgram(buf,cps);     // send egtbpath command for this format
9283             }
9284             if(*p == ',') p++; // read away comma to position for next format name
9285         }
9286 }
9287
9288 void
9289 InitChessProgram(cps, setup)
9290      ChessProgramState *cps;
9291      int setup; /* [HGM] needed to setup FRC opening position */
9292 {
9293     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9294     if (appData.noChessProgram) return;
9295     hintRequested = FALSE;
9296     bookRequested = FALSE;
9297
9298     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9299     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9300     if(cps->memSize) { /* [HGM] memory */
9301       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9302         SendToProgram(buf, cps);
9303     }
9304     SendEgtPath(cps); /* [HGM] EGT */
9305     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9306       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9307         SendToProgram(buf, cps);
9308     }
9309
9310     SendToProgram(cps->initString, cps);
9311     if (gameInfo.variant != VariantNormal &&
9312         gameInfo.variant != VariantLoadable
9313         /* [HGM] also send variant if board size non-standard */
9314         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9315                                             ) {
9316       char *v = VariantName(gameInfo.variant);
9317       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9318         /* [HGM] in protocol 1 we have to assume all variants valid */
9319         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9320         DisplayFatalError(buf, 0, 1);
9321         return;
9322       }
9323
9324       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9325       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9326       if( gameInfo.variant == VariantXiangqi )
9327            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9328       if( gameInfo.variant == VariantShogi )
9329            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9330       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9331            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9332       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9333           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9334            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9335       if( gameInfo.variant == VariantCourier )
9336            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9337       if( gameInfo.variant == VariantSuper )
9338            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9339       if( gameInfo.variant == VariantGreat )
9340            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9341       if( gameInfo.variant == VariantSChess )
9342            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9343
9344       if(overruled) {
9345         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9346                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9347            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9348            if(StrStr(cps->variants, b) == NULL) {
9349                // specific sized variant not known, check if general sizing allowed
9350                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9351                    if(StrStr(cps->variants, "boardsize") == NULL) {
9352                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9353                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9354                        DisplayFatalError(buf, 0, 1);
9355                        return;
9356                    }
9357                    /* [HGM] here we really should compare with the maximum supported board size */
9358                }
9359            }
9360       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9361       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9362       SendToProgram(buf, cps);
9363     }
9364     currentlyInitializedVariant = gameInfo.variant;
9365
9366     /* [HGM] send opening position in FRC to first engine */
9367     if(setup) {
9368           SendToProgram("force\n", cps);
9369           SendBoard(cps, 0);
9370           /* engine is now in force mode! Set flag to wake it up after first move. */
9371           setboardSpoiledMachineBlack = 1;
9372     }
9373
9374     if (cps->sendICS) {
9375       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9376       SendToProgram(buf, cps);
9377     }
9378     cps->maybeThinking = FALSE;
9379     cps->offeredDraw = 0;
9380     if (!appData.icsActive) {
9381         SendTimeControl(cps, movesPerSession, timeControl,
9382                         timeIncrement, appData.searchDepth,
9383                         searchTime);
9384     }
9385     if (appData.showThinking
9386         // [HGM] thinking: four options require thinking output to be sent
9387         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9388                                 ) {
9389         SendToProgram("post\n", cps);
9390     }
9391     SendToProgram("hard\n", cps);
9392     if (!appData.ponderNextMove) {
9393         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9394            it without being sure what state we are in first.  "hard"
9395            is not a toggle, so that one is OK.
9396          */
9397         SendToProgram("easy\n", cps);
9398     }
9399     if (cps->usePing) {
9400       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9401       SendToProgram(buf, cps);
9402     }
9403     cps->initDone = TRUE;
9404 }
9405
9406
9407 void
9408 StartChessProgram(cps)
9409      ChessProgramState *cps;
9410 {
9411     char buf[MSG_SIZ];
9412     int err;
9413
9414     if (appData.noChessProgram) return;
9415     cps->initDone = FALSE;
9416
9417     if (strcmp(cps->host, "localhost") == 0) {
9418         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9419     } else if (*appData.remoteShell == NULLCHAR) {
9420         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9421     } else {
9422         if (*appData.remoteUser == NULLCHAR) {
9423           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9424                     cps->program);
9425         } else {
9426           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9427                     cps->host, appData.remoteUser, cps->program);
9428         }
9429         err = StartChildProcess(buf, "", &cps->pr);
9430     }
9431
9432     if (err != 0) {
9433       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9434         DisplayFatalError(buf, err, 1);
9435         cps->pr = NoProc;
9436         cps->isr = NULL;
9437         return;
9438     }
9439
9440     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9441     if (cps->protocolVersion > 1) {
9442       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9443       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9444       cps->comboCnt = 0;  //                and values of combo boxes
9445       SendToProgram(buf, cps);
9446     } else {
9447       SendToProgram("xboard\n", cps);
9448     }
9449 }
9450
9451 void
9452 TwoMachinesEventIfReady P((void))
9453 {
9454   static int curMess = 0;
9455   if (first.lastPing != first.lastPong) {
9456     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9457     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9458     return;
9459   }
9460   if (second.lastPing != second.lastPong) {
9461     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9462     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9463     return;
9464   }
9465   DisplayMessage("", ""); curMess = 0;
9466   ThawUI();
9467   TwoMachinesEvent();
9468 }
9469
9470 int
9471 CreateTourney(char *name)
9472 {
9473         FILE *f;
9474         if(name[0] == NULLCHAR) return 0;
9475         f = fopen(appData.tourneyFile, "r");
9476         if(f) { // file exists
9477             ParseArgsFromFile(f); // parse it
9478         } else {
9479             f = fopen(appData.tourneyFile, "w");
9480             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9481                 // create a file with tournament description
9482                 fprintf(f, "-participants {%s}\n", appData.participants);
9483                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9484                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9485                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9486                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9487                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9488                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9489                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9490                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9491                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9492                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9493                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9494                 if(searchTime > 0)
9495                         fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9496                 else {
9497                         fprintf(f, "-mps %d\n", appData.movesPerSession);
9498                         fprintf(f, "-tc %s\n", appData.timeControl);
9499                         fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9500                 }
9501                 fprintf(f, "-results \"\"\n");
9502             }
9503         }
9504         fclose(f);
9505         appData.noChessProgram = FALSE;
9506         appData.clockMode = TRUE;
9507         SetGNUMode();
9508         return 1;
9509 }
9510
9511 #define MAXENGINES 1000
9512 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9513
9514 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9515 {
9516     char buf[MSG_SIZ], *p, *q;
9517     int i=1;
9518     while(*names) {
9519         p = names; q = buf;
9520         while(*p && *p != '\n') *q++ = *p++;
9521         *q = 0;
9522         if(engineList[i]) free(engineList[i]);
9523         engineList[i] = strdup(buf);
9524         if(*p == '\n') p++;
9525         TidyProgramName(engineList[i], "localhost", buf);
9526         if(engineMnemonic[i]) free(engineMnemonic[i]);
9527         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9528             strcat(buf, " (");
9529             sscanf(q + 8, "%s", buf + strlen(buf));
9530             strcat(buf, ")");
9531         }
9532         engineMnemonic[i] = strdup(buf);
9533         names = p; i++;
9534       if(i > MAXENGINES - 2) break;
9535     }
9536     engineList[i] = NULL;
9537 }
9538
9539 // following implemented as macro to avoid type limitations
9540 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9541
9542 void SwapEngines(int n)
9543 {   // swap settings for first engine and other engine (so far only some selected options)
9544     int h;
9545     char *p;
9546     if(n == 0) return;
9547     SWAP(directory, p)
9548     SWAP(chessProgram, p)
9549     SWAP(isUCI, h)
9550     SWAP(hasOwnBookUCI, h)
9551     SWAP(protocolVersion, h)
9552     SWAP(reuse, h)
9553     SWAP(scoreIsAbsolute, h)
9554     SWAP(timeOdds, h)
9555     SWAP(logo, p)
9556     SWAP(pgnName, p)
9557 }
9558
9559 void
9560 SetPlayer(int player)
9561 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9562     int i;
9563     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9564     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9565     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9566     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9567     if(mnemonic[i]) {
9568         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9569         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9570         ParseArgsFromString(buf);
9571     }
9572     free(engineName);
9573 }
9574
9575 int
9576 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9577 {   // determine players from game number
9578     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9579
9580     if(appData.tourneyType == 0) {
9581         roundsPerCycle = (nPlayers - 1) | 1;
9582         pairingsPerRound = nPlayers / 2;
9583     } else if(appData.tourneyType > 0) {
9584         roundsPerCycle = nPlayers - appData.tourneyType;
9585         pairingsPerRound = appData.tourneyType;
9586     }
9587     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9588     gamesPerCycle = gamesPerRound * roundsPerCycle;
9589     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9590     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9591     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9592     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9593     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9594     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9595
9596     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9597     if(appData.roundSync) *syncInterval = gamesPerRound;
9598
9599     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9600
9601     if(appData.tourneyType == 0) {
9602         if(curPairing == (nPlayers-1)/2 ) {
9603             *whitePlayer = curRound;
9604             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9605         } else {
9606             *whitePlayer = curRound - pairingsPerRound + curPairing;
9607             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9608             *blackPlayer = curRound + pairingsPerRound - curPairing;
9609             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9610         }
9611     } else if(appData.tourneyType > 0) {
9612         *whitePlayer = curPairing;
9613         *blackPlayer = curRound + appData.tourneyType;
9614     }
9615
9616     // take care of white/black alternation per round. 
9617     // For cycles and games this is already taken care of by default, derived from matchGame!
9618     return curRound & 1;
9619 }
9620
9621 int
9622 NextTourneyGame(int nr, int *swapColors)
9623 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9624     char *p, *q;
9625     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9626     FILE *tf;
9627     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9628     tf = fopen(appData.tourneyFile, "r");
9629     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9630     ParseArgsFromFile(tf); fclose(tf);
9631     InitTimeControls(); // TC might be altered from tourney file
9632
9633     p = appData.participants;
9634     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9635     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9636
9637     if(syncInterval) {
9638         p = q = appData.results;
9639         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9640         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9641             DisplayMessage(_("Waiting for other game(s)"),"");
9642             waitingForGame = TRUE;
9643             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9644             return 0;
9645         }
9646         waitingForGame = FALSE;
9647     }
9648
9649     if(first.pr != NoProc) return 1; // engines already loaded
9650
9651     // redefine engines, engine dir, etc.
9652     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9653     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9654     SwapEngines(1);
9655     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9656     SwapEngines(1);         // and make that valid for second engine by swapping
9657     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9658     InitEngine(&second, 1);
9659     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9660     return 1;
9661 }
9662
9663 void
9664 NextMatchGame()
9665 {   // performs game initialization that does not invoke engines, and then tries to start the game
9666     int firstWhite, swapColors = 0;
9667     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9668     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9669     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9670     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9671     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9672     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9673     Reset(FALSE, first.pr != NoProc);
9674     appData.noChessProgram = FALSE;
9675     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9676     TwoMachinesEvent();
9677 }
9678
9679 void UserAdjudicationEvent( int result )
9680 {
9681     ChessMove gameResult = GameIsDrawn;
9682
9683     if( result > 0 ) {
9684         gameResult = WhiteWins;
9685     }
9686     else if( result < 0 ) {
9687         gameResult = BlackWins;
9688     }
9689
9690     if( gameMode == TwoMachinesPlay ) {
9691         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9692     }
9693 }
9694
9695
9696 // [HGM] save: calculate checksum of game to make games easily identifiable
9697 int StringCheckSum(char *s)
9698 {
9699         int i = 0;
9700         if(s==NULL) return 0;
9701         while(*s) i = i*259 + *s++;
9702         return i;
9703 }
9704
9705 int GameCheckSum()
9706 {
9707         int i, sum=0;
9708         for(i=backwardMostMove; i<forwardMostMove; i++) {
9709                 sum += pvInfoList[i].depth;
9710                 sum += StringCheckSum(parseList[i]);
9711                 sum += StringCheckSum(commentList[i]);
9712                 sum *= 261;
9713         }
9714         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9715         return sum + StringCheckSum(commentList[i]);
9716 } // end of save patch
9717
9718 void
9719 GameEnds(result, resultDetails, whosays)
9720      ChessMove result;
9721      char *resultDetails;
9722      int whosays;
9723 {
9724     GameMode nextGameMode;
9725     int isIcsGame;
9726     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9727
9728     if(endingGame) return; /* [HGM] crash: forbid recursion */
9729     endingGame = 1;
9730     if(twoBoards) { // [HGM] dual: switch back to one board
9731         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9732         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9733     }
9734     if (appData.debugMode) {
9735       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9736               result, resultDetails ? resultDetails : "(null)", whosays);
9737     }
9738
9739     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9740
9741     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9742         /* If we are playing on ICS, the server decides when the
9743            game is over, but the engine can offer to draw, claim
9744            a draw, or resign.
9745          */
9746 #if ZIPPY
9747         if (appData.zippyPlay && first.initDone) {
9748             if (result == GameIsDrawn) {
9749                 /* In case draw still needs to be claimed */
9750                 SendToICS(ics_prefix);
9751                 SendToICS("draw\n");
9752             } else if (StrCaseStr(resultDetails, "resign")) {
9753                 SendToICS(ics_prefix);
9754                 SendToICS("resign\n");
9755             }
9756         }
9757 #endif
9758         endingGame = 0; /* [HGM] crash */
9759         return;
9760     }
9761
9762     /* If we're loading the game from a file, stop */
9763     if (whosays == GE_FILE) {
9764       (void) StopLoadGameTimer();
9765       gameFileFP = NULL;
9766     }
9767
9768     /* Cancel draw offers */
9769     first.offeredDraw = second.offeredDraw = 0;
9770
9771     /* If this is an ICS game, only ICS can really say it's done;
9772        if not, anyone can. */
9773     isIcsGame = (gameMode == IcsPlayingWhite ||
9774                  gameMode == IcsPlayingBlack ||
9775                  gameMode == IcsObserving    ||
9776                  gameMode == IcsExamining);
9777
9778     if (!isIcsGame || whosays == GE_ICS) {
9779         /* OK -- not an ICS game, or ICS said it was done */
9780         StopClocks();
9781         if (!isIcsGame && !appData.noChessProgram)
9782           SetUserThinkingEnables();
9783
9784         /* [HGM] if a machine claims the game end we verify this claim */
9785         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9786             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9787                 char claimer;
9788                 ChessMove trueResult = (ChessMove) -1;
9789
9790                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9791                                             first.twoMachinesColor[0] :
9792                                             second.twoMachinesColor[0] ;
9793
9794                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9795                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9796                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9797                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9798                 } else
9799                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9800                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9801                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9802                 } else
9803                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9804                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9805                 }
9806
9807                 // now verify win claims, but not in drop games, as we don't understand those yet
9808                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9809                                                  || gameInfo.variant == VariantGreat) &&
9810                     (result == WhiteWins && claimer == 'w' ||
9811                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9812                       if (appData.debugMode) {
9813                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9814                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9815                       }
9816                       if(result != trueResult) {
9817                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9818                               result = claimer == 'w' ? BlackWins : WhiteWins;
9819                               resultDetails = buf;
9820                       }
9821                 } else
9822                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9823                     && (forwardMostMove <= backwardMostMove ||
9824                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9825                         (claimer=='b')==(forwardMostMove&1))
9826                                                                                   ) {
9827                       /* [HGM] verify: draws that were not flagged are false claims */
9828                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9829                       result = claimer == 'w' ? BlackWins : WhiteWins;
9830                       resultDetails = buf;
9831                 }
9832                 /* (Claiming a loss is accepted no questions asked!) */
9833             }
9834             /* [HGM] bare: don't allow bare King to win */
9835             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9836                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9837                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9838                && result != GameIsDrawn)
9839             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9840                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9841                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9842                         if(p >= 0 && p <= (int)WhiteKing) k++;
9843                 }
9844                 if (appData.debugMode) {
9845                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9846                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9847                 }
9848                 if(k <= 1) {
9849                         result = GameIsDrawn;
9850                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9851                         resultDetails = buf;
9852                 }
9853             }
9854         }
9855
9856
9857         if(serverMoves != NULL && !loadFlag) { char c = '=';
9858             if(result==WhiteWins) c = '+';
9859             if(result==BlackWins) c = '-';
9860             if(resultDetails != NULL)
9861                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9862         }
9863         if (resultDetails != NULL) {
9864             gameInfo.result = result;
9865             gameInfo.resultDetails = StrSave(resultDetails);
9866
9867             /* display last move only if game was not loaded from file */
9868             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9869                 DisplayMove(currentMove - 1);
9870
9871             if (forwardMostMove != 0) {
9872                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9873                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9874                                                                 ) {
9875                     if (*appData.saveGameFile != NULLCHAR) {
9876                         SaveGameToFile(appData.saveGameFile, TRUE);
9877                     } else if (appData.autoSaveGames) {
9878                         AutoSaveGame();
9879                     }
9880                     if (*appData.savePositionFile != NULLCHAR) {
9881                         SavePositionToFile(appData.savePositionFile);
9882                     }
9883                 }
9884             }
9885
9886             /* Tell program how game ended in case it is learning */
9887             /* [HGM] Moved this to after saving the PGN, just in case */
9888             /* engine died and we got here through time loss. In that */
9889             /* case we will get a fatal error writing the pipe, which */
9890             /* would otherwise lose us the PGN.                       */
9891             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9892             /* output during GameEnds should never be fatal anymore   */
9893             if (gameMode == MachinePlaysWhite ||
9894                 gameMode == MachinePlaysBlack ||
9895                 gameMode == TwoMachinesPlay ||
9896                 gameMode == IcsPlayingWhite ||
9897                 gameMode == IcsPlayingBlack ||
9898                 gameMode == BeginningOfGame) {
9899                 char buf[MSG_SIZ];
9900                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9901                         resultDetails);
9902                 if (first.pr != NoProc) {
9903                     SendToProgram(buf, &first);
9904                 }
9905                 if (second.pr != NoProc &&
9906                     gameMode == TwoMachinesPlay) {
9907                     SendToProgram(buf, &second);
9908                 }
9909             }
9910         }
9911
9912         if (appData.icsActive) {
9913             if (appData.quietPlay &&
9914                 (gameMode == IcsPlayingWhite ||
9915                  gameMode == IcsPlayingBlack)) {
9916                 SendToICS(ics_prefix);
9917                 SendToICS("set shout 1\n");
9918             }
9919             nextGameMode = IcsIdle;
9920             ics_user_moved = FALSE;
9921             /* clean up premove.  It's ugly when the game has ended and the
9922              * premove highlights are still on the board.
9923              */
9924             if (gotPremove) {
9925               gotPremove = FALSE;
9926               ClearPremoveHighlights();
9927               DrawPosition(FALSE, boards[currentMove]);
9928             }
9929             if (whosays == GE_ICS) {
9930                 switch (result) {
9931                 case WhiteWins:
9932                     if (gameMode == IcsPlayingWhite)
9933                         PlayIcsWinSound();
9934                     else if(gameMode == IcsPlayingBlack)
9935                         PlayIcsLossSound();
9936                     break;
9937                 case BlackWins:
9938                     if (gameMode == IcsPlayingBlack)
9939                         PlayIcsWinSound();
9940                     else if(gameMode == IcsPlayingWhite)
9941                         PlayIcsLossSound();
9942                     break;
9943                 case GameIsDrawn:
9944                     PlayIcsDrawSound();
9945                     break;
9946                 default:
9947                     PlayIcsUnfinishedSound();
9948                 }
9949             }
9950         } else if (gameMode == EditGame ||
9951                    gameMode == PlayFromGameFile ||
9952                    gameMode == AnalyzeMode ||
9953                    gameMode == AnalyzeFile) {
9954             nextGameMode = gameMode;
9955         } else {
9956             nextGameMode = EndOfGame;
9957         }
9958         pausing = FALSE;
9959         ModeHighlight();
9960     } else {
9961         nextGameMode = gameMode;
9962     }
9963
9964     if (appData.noChessProgram) {
9965         gameMode = nextGameMode;
9966         ModeHighlight();
9967         endingGame = 0; /* [HGM] crash */
9968         return;
9969     }
9970
9971     if (first.reuse) {
9972         /* Put first chess program into idle state */
9973         if (first.pr != NoProc &&
9974             (gameMode == MachinePlaysWhite ||
9975              gameMode == MachinePlaysBlack ||
9976              gameMode == TwoMachinesPlay ||
9977              gameMode == IcsPlayingWhite ||
9978              gameMode == IcsPlayingBlack ||
9979              gameMode == BeginningOfGame)) {
9980             SendToProgram("force\n", &first);
9981             if (first.usePing) {
9982               char buf[MSG_SIZ];
9983               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9984               SendToProgram(buf, &first);
9985             }
9986         }
9987     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9988         /* Kill off first chess program */
9989         if (first.isr != NULL)
9990           RemoveInputSource(first.isr);
9991         first.isr = NULL;
9992
9993         if (first.pr != NoProc) {
9994             ExitAnalyzeMode();
9995             DoSleep( appData.delayBeforeQuit );
9996             SendToProgram("quit\n", &first);
9997             DoSleep( appData.delayAfterQuit );
9998             DestroyChildProcess(first.pr, first.useSigterm);
9999         }
10000         first.pr = NoProc;
10001     }
10002     if (second.reuse) {
10003         /* Put second chess program into idle state */
10004         if (second.pr != NoProc &&
10005             gameMode == TwoMachinesPlay) {
10006             SendToProgram("force\n", &second);
10007             if (second.usePing) {
10008               char buf[MSG_SIZ];
10009               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10010               SendToProgram(buf, &second);
10011             }
10012         }
10013     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10014         /* Kill off second chess program */
10015         if (second.isr != NULL)
10016           RemoveInputSource(second.isr);
10017         second.isr = NULL;
10018
10019         if (second.pr != NoProc) {
10020             DoSleep( appData.delayBeforeQuit );
10021             SendToProgram("quit\n", &second);
10022             DoSleep( appData.delayAfterQuit );
10023             DestroyChildProcess(second.pr, second.useSigterm);
10024         }
10025         second.pr = NoProc;
10026     }
10027
10028     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10029         char resChar = '=';
10030         switch (result) {
10031         case WhiteWins:
10032           resChar = '+';
10033           if (first.twoMachinesColor[0] == 'w') {
10034             first.matchWins++;
10035           } else {
10036             second.matchWins++;
10037           }
10038           break;
10039         case BlackWins:
10040           resChar = '-';
10041           if (first.twoMachinesColor[0] == 'b') {
10042             first.matchWins++;
10043           } else {
10044             second.matchWins++;
10045           }
10046           break;
10047         case GameUnfinished:
10048           resChar = ' ';
10049         default:
10050           break;
10051         }
10052
10053         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10054         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10055             ReserveGame(nextGame, resChar); // sets nextGame
10056             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10057         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10058
10059         if (nextGame <= appData.matchGames && !abortMatch) {
10060             gameMode = nextGameMode;
10061             matchGame = nextGame; // this will be overruled in tourney mode!
10062             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10063             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10064             endingGame = 0; /* [HGM] crash */
10065             return;
10066         } else {
10067             gameMode = nextGameMode;
10068             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10069                      first.tidy, second.tidy,
10070                      first.matchWins, second.matchWins,
10071                      appData.matchGames - (first.matchWins + second.matchWins));
10072             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10073             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10074                 first.twoMachinesColor = "black\n";
10075                 second.twoMachinesColor = "white\n";
10076             } else {
10077                 first.twoMachinesColor = "white\n";
10078                 second.twoMachinesColor = "black\n";
10079             }
10080         }
10081     }
10082     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10083         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10084       ExitAnalyzeMode();
10085     gameMode = nextGameMode;
10086     ModeHighlight();
10087     endingGame = 0;  /* [HGM] crash */
10088     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10089         if(matchMode == TRUE) { // match through command line: exit with or without popup
10090             if(ranking) {
10091                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10092                 else ExitEvent(0);
10093             } else DisplayFatalError(buf, 0, 0);
10094         } else { // match through menu; just stop, with or without popup
10095             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10096             if(ranking){
10097                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10098             } else DisplayNote(buf);
10099       }
10100       if(ranking) free(ranking);
10101     }
10102 }
10103
10104 /* Assumes program was just initialized (initString sent).
10105    Leaves program in force mode. */
10106 void
10107 FeedMovesToProgram(cps, upto)
10108      ChessProgramState *cps;
10109      int upto;
10110 {
10111     int i;
10112
10113     if (appData.debugMode)
10114       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10115               startedFromSetupPosition ? "position and " : "",
10116               backwardMostMove, upto, cps->which);
10117     if(currentlyInitializedVariant != gameInfo.variant) {
10118       char buf[MSG_SIZ];
10119         // [HGM] variantswitch: make engine aware of new variant
10120         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10121                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10122         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10123         SendToProgram(buf, cps);
10124         currentlyInitializedVariant = gameInfo.variant;
10125     }
10126     SendToProgram("force\n", cps);
10127     if (startedFromSetupPosition) {
10128         SendBoard(cps, backwardMostMove);
10129     if (appData.debugMode) {
10130         fprintf(debugFP, "feedMoves\n");
10131     }
10132     }
10133     for (i = backwardMostMove; i < upto; i++) {
10134         SendMoveToProgram(i, cps);
10135     }
10136 }
10137
10138
10139 int
10140 ResurrectChessProgram()
10141 {
10142      /* The chess program may have exited.
10143         If so, restart it and feed it all the moves made so far. */
10144     static int doInit = 0;
10145
10146     if (appData.noChessProgram) return 1;
10147
10148     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10149         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10150         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10151         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10152     } else {
10153         if (first.pr != NoProc) return 1;
10154         StartChessProgram(&first);
10155     }
10156     InitChessProgram(&first, FALSE);
10157     FeedMovesToProgram(&first, currentMove);
10158
10159     if (!first.sendTime) {
10160         /* can't tell gnuchess what its clock should read,
10161            so we bow to its notion. */
10162         ResetClocks();
10163         timeRemaining[0][currentMove] = whiteTimeRemaining;
10164         timeRemaining[1][currentMove] = blackTimeRemaining;
10165     }
10166
10167     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10168                 appData.icsEngineAnalyze) && first.analysisSupport) {
10169       SendToProgram("analyze\n", &first);
10170       first.analyzing = TRUE;
10171     }
10172     return 1;
10173 }
10174
10175 /*
10176  * Button procedures
10177  */
10178 void
10179 Reset(redraw, init)
10180      int redraw, init;
10181 {
10182     int i;
10183
10184     if (appData.debugMode) {
10185         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10186                 redraw, init, gameMode);
10187     }
10188     CleanupTail(); // [HGM] vari: delete any stored variations
10189     pausing = pauseExamInvalid = FALSE;
10190     startedFromSetupPosition = blackPlaysFirst = FALSE;
10191     firstMove = TRUE;
10192     whiteFlag = blackFlag = FALSE;
10193     userOfferedDraw = FALSE;
10194     hintRequested = bookRequested = FALSE;
10195     first.maybeThinking = FALSE;
10196     second.maybeThinking = FALSE;
10197     first.bookSuspend = FALSE; // [HGM] book
10198     second.bookSuspend = FALSE;
10199     thinkOutput[0] = NULLCHAR;
10200     lastHint[0] = NULLCHAR;
10201     ClearGameInfo(&gameInfo);
10202     gameInfo.variant = StringToVariant(appData.variant);
10203     ics_user_moved = ics_clock_paused = FALSE;
10204     ics_getting_history = H_FALSE;
10205     ics_gamenum = -1;
10206     white_holding[0] = black_holding[0] = NULLCHAR;
10207     ClearProgramStats();
10208     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10209
10210     ResetFrontEnd();
10211     ClearHighlights();
10212     flipView = appData.flipView;
10213     ClearPremoveHighlights();
10214     gotPremove = FALSE;
10215     alarmSounded = FALSE;
10216
10217     GameEnds(EndOfFile, NULL, GE_PLAYER);
10218     if(appData.serverMovesName != NULL) {
10219         /* [HGM] prepare to make moves file for broadcasting */
10220         clock_t t = clock();
10221         if(serverMoves != NULL) fclose(serverMoves);
10222         serverMoves = fopen(appData.serverMovesName, "r");
10223         if(serverMoves != NULL) {
10224             fclose(serverMoves);
10225             /* delay 15 sec before overwriting, so all clients can see end */
10226             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10227         }
10228         serverMoves = fopen(appData.serverMovesName, "w");
10229     }
10230
10231     ExitAnalyzeMode();
10232     gameMode = BeginningOfGame;
10233     ModeHighlight();
10234     if(appData.icsActive) gameInfo.variant = VariantNormal;
10235     currentMove = forwardMostMove = backwardMostMove = 0;
10236     InitPosition(redraw);
10237     for (i = 0; i < MAX_MOVES; i++) {
10238         if (commentList[i] != NULL) {
10239             free(commentList[i]);
10240             commentList[i] = NULL;
10241         }
10242     }
10243     ResetClocks();
10244     timeRemaining[0][0] = whiteTimeRemaining;
10245     timeRemaining[1][0] = blackTimeRemaining;
10246
10247     if (first.pr == NULL) {
10248         StartChessProgram(&first);
10249     }
10250     if (init) {
10251             InitChessProgram(&first, startedFromSetupPosition);
10252     }
10253     DisplayTitle("");
10254     DisplayMessage("", "");
10255     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10256     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10257 }
10258
10259 void
10260 AutoPlayGameLoop()
10261 {
10262     for (;;) {
10263         if (!AutoPlayOneMove())
10264           return;
10265         if (matchMode || appData.timeDelay == 0)
10266           continue;
10267         if (appData.timeDelay < 0)
10268           return;
10269         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10270         break;
10271     }
10272 }
10273
10274
10275 int
10276 AutoPlayOneMove()
10277 {
10278     int fromX, fromY, toX, toY;
10279
10280     if (appData.debugMode) {
10281       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10282     }
10283
10284     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10285       return FALSE;
10286
10287     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10288       pvInfoList[currentMove].depth = programStats.depth;
10289       pvInfoList[currentMove].score = programStats.score;
10290       pvInfoList[currentMove].time  = 0;
10291       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10292     }
10293
10294     if (currentMove >= forwardMostMove) {
10295       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10296       gameMode = EditGame;
10297       ModeHighlight();
10298
10299       /* [AS] Clear current move marker at the end of a game */
10300       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10301
10302       return FALSE;
10303     }
10304
10305     toX = moveList[currentMove][2] - AAA;
10306     toY = moveList[currentMove][3] - ONE;
10307
10308     if (moveList[currentMove][1] == '@') {
10309         if (appData.highlightLastMove) {
10310             SetHighlights(-1, -1, toX, toY);
10311         }
10312     } else {
10313         fromX = moveList[currentMove][0] - AAA;
10314         fromY = moveList[currentMove][1] - ONE;
10315
10316         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10317
10318         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10319
10320         if (appData.highlightLastMove) {
10321             SetHighlights(fromX, fromY, toX, toY);
10322         }
10323     }
10324     DisplayMove(currentMove);
10325     SendMoveToProgram(currentMove++, &first);
10326     DisplayBothClocks();
10327     DrawPosition(FALSE, boards[currentMove]);
10328     // [HGM] PV info: always display, routine tests if empty
10329     DisplayComment(currentMove - 1, commentList[currentMove]);
10330     return TRUE;
10331 }
10332
10333
10334 int
10335 LoadGameOneMove(readAhead)
10336      ChessMove readAhead;
10337 {
10338     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10339     char promoChar = NULLCHAR;
10340     ChessMove moveType;
10341     char move[MSG_SIZ];
10342     char *p, *q;
10343
10344     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10345         gameMode != AnalyzeMode && gameMode != Training) {
10346         gameFileFP = NULL;
10347         return FALSE;
10348     }
10349
10350     yyboardindex = forwardMostMove;
10351     if (readAhead != EndOfFile) {
10352       moveType = readAhead;
10353     } else {
10354       if (gameFileFP == NULL)
10355           return FALSE;
10356       moveType = (ChessMove) Myylex();
10357     }
10358
10359     done = FALSE;
10360     switch (moveType) {
10361       case Comment:
10362         if (appData.debugMode)
10363           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10364         p = yy_text;
10365
10366         /* append the comment but don't display it */
10367         AppendComment(currentMove, p, FALSE);
10368         return TRUE;
10369
10370       case WhiteCapturesEnPassant:
10371       case BlackCapturesEnPassant:
10372       case WhitePromotion:
10373       case BlackPromotion:
10374       case WhiteNonPromotion:
10375       case BlackNonPromotion:
10376       case NormalMove:
10377       case WhiteKingSideCastle:
10378       case WhiteQueenSideCastle:
10379       case BlackKingSideCastle:
10380       case BlackQueenSideCastle:
10381       case WhiteKingSideCastleWild:
10382       case WhiteQueenSideCastleWild:
10383       case BlackKingSideCastleWild:
10384       case BlackQueenSideCastleWild:
10385       /* PUSH Fabien */
10386       case WhiteHSideCastleFR:
10387       case WhiteASideCastleFR:
10388       case BlackHSideCastleFR:
10389       case BlackASideCastleFR:
10390       /* POP Fabien */
10391         if (appData.debugMode)
10392           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10393         fromX = currentMoveString[0] - AAA;
10394         fromY = currentMoveString[1] - ONE;
10395         toX = currentMoveString[2] - AAA;
10396         toY = currentMoveString[3] - ONE;
10397         promoChar = currentMoveString[4];
10398         break;
10399
10400       case WhiteDrop:
10401       case BlackDrop:
10402         if (appData.debugMode)
10403           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10404         fromX = moveType == WhiteDrop ?
10405           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10406         (int) CharToPiece(ToLower(currentMoveString[0]));
10407         fromY = DROP_RANK;
10408         toX = currentMoveString[2] - AAA;
10409         toY = currentMoveString[3] - ONE;
10410         break;
10411
10412       case WhiteWins:
10413       case BlackWins:
10414       case GameIsDrawn:
10415       case GameUnfinished:
10416         if (appData.debugMode)
10417           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10418         p = strchr(yy_text, '{');
10419         if (p == NULL) p = strchr(yy_text, '(');
10420         if (p == NULL) {
10421             p = yy_text;
10422             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10423         } else {
10424             q = strchr(p, *p == '{' ? '}' : ')');
10425             if (q != NULL) *q = NULLCHAR;
10426             p++;
10427         }
10428         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10429         GameEnds(moveType, p, GE_FILE);
10430         done = TRUE;
10431         if (cmailMsgLoaded) {
10432             ClearHighlights();
10433             flipView = WhiteOnMove(currentMove);
10434             if (moveType == GameUnfinished) flipView = !flipView;
10435             if (appData.debugMode)
10436               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10437         }
10438         break;
10439
10440       case EndOfFile:
10441         if (appData.debugMode)
10442           fprintf(debugFP, "Parser hit end of file\n");
10443         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10444           case MT_NONE:
10445           case MT_CHECK:
10446             break;
10447           case MT_CHECKMATE:
10448           case MT_STAINMATE:
10449             if (WhiteOnMove(currentMove)) {
10450                 GameEnds(BlackWins, "Black mates", GE_FILE);
10451             } else {
10452                 GameEnds(WhiteWins, "White mates", GE_FILE);
10453             }
10454             break;
10455           case MT_STALEMATE:
10456             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10457             break;
10458         }
10459         done = TRUE;
10460         break;
10461
10462       case MoveNumberOne:
10463         if (lastLoadGameStart == GNUChessGame) {
10464             /* GNUChessGames have numbers, but they aren't move numbers */
10465             if (appData.debugMode)
10466               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10467                       yy_text, (int) moveType);
10468             return LoadGameOneMove(EndOfFile); /* tail recursion */
10469         }
10470         /* else fall thru */
10471
10472       case XBoardGame:
10473       case GNUChessGame:
10474       case PGNTag:
10475         /* Reached start of next game in file */
10476         if (appData.debugMode)
10477           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10478         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10479           case MT_NONE:
10480           case MT_CHECK:
10481             break;
10482           case MT_CHECKMATE:
10483           case MT_STAINMATE:
10484             if (WhiteOnMove(currentMove)) {
10485                 GameEnds(BlackWins, "Black mates", GE_FILE);
10486             } else {
10487                 GameEnds(WhiteWins, "White mates", GE_FILE);
10488             }
10489             break;
10490           case MT_STALEMATE:
10491             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10492             break;
10493         }
10494         done = TRUE;
10495         break;
10496
10497       case PositionDiagram:     /* should not happen; ignore */
10498       case ElapsedTime:         /* ignore */
10499       case NAG:                 /* ignore */
10500         if (appData.debugMode)
10501           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10502                   yy_text, (int) moveType);
10503         return LoadGameOneMove(EndOfFile); /* tail recursion */
10504
10505       case IllegalMove:
10506         if (appData.testLegality) {
10507             if (appData.debugMode)
10508               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10509             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10510                     (forwardMostMove / 2) + 1,
10511                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10512             DisplayError(move, 0);
10513             done = TRUE;
10514         } else {
10515             if (appData.debugMode)
10516               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10517                       yy_text, currentMoveString);
10518             fromX = currentMoveString[0] - AAA;
10519             fromY = currentMoveString[1] - ONE;
10520             toX = currentMoveString[2] - AAA;
10521             toY = currentMoveString[3] - ONE;
10522             promoChar = currentMoveString[4];
10523         }
10524         break;
10525
10526       case AmbiguousMove:
10527         if (appData.debugMode)
10528           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10529         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10530                 (forwardMostMove / 2) + 1,
10531                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10532         DisplayError(move, 0);
10533         done = TRUE;
10534         break;
10535
10536       default:
10537       case ImpossibleMove:
10538         if (appData.debugMode)
10539           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10540         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10541                 (forwardMostMove / 2) + 1,
10542                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10543         DisplayError(move, 0);
10544         done = TRUE;
10545         break;
10546     }
10547
10548     if (done) {
10549         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10550             DrawPosition(FALSE, boards[currentMove]);
10551             DisplayBothClocks();
10552             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10553               DisplayComment(currentMove - 1, commentList[currentMove]);
10554         }
10555         (void) StopLoadGameTimer();
10556         gameFileFP = NULL;
10557         cmailOldMove = forwardMostMove;
10558         return FALSE;
10559     } else {
10560         /* currentMoveString is set as a side-effect of yylex */
10561
10562         thinkOutput[0] = NULLCHAR;
10563         MakeMove(fromX, fromY, toX, toY, promoChar);
10564         currentMove = forwardMostMove;
10565         return TRUE;
10566     }
10567 }
10568
10569 /* Load the nth game from the given file */
10570 int
10571 LoadGameFromFile(filename, n, title, useList)
10572      char *filename;
10573      int n;
10574      char *title;
10575      /*Boolean*/ int useList;
10576 {
10577     FILE *f;
10578     char buf[MSG_SIZ];
10579
10580     if (strcmp(filename, "-") == 0) {
10581         f = stdin;
10582         title = "stdin";
10583     } else {
10584         f = fopen(filename, "rb");
10585         if (f == NULL) {
10586           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10587             DisplayError(buf, errno);
10588             return FALSE;
10589         }
10590     }
10591     if (fseek(f, 0, 0) == -1) {
10592         /* f is not seekable; probably a pipe */
10593         useList = FALSE;
10594     }
10595     if (useList && n == 0) {
10596         int error = GameListBuild(f);
10597         if (error) {
10598             DisplayError(_("Cannot build game list"), error);
10599         } else if (!ListEmpty(&gameList) &&
10600                    ((ListGame *) gameList.tailPred)->number > 1) {
10601             GameListPopUp(f, title);
10602             return TRUE;
10603         }
10604         GameListDestroy();
10605         n = 1;
10606     }
10607     if (n == 0) n = 1;
10608     return LoadGame(f, n, title, FALSE);
10609 }
10610
10611
10612 void
10613 MakeRegisteredMove()
10614 {
10615     int fromX, fromY, toX, toY;
10616     char promoChar;
10617     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10618         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10619           case CMAIL_MOVE:
10620           case CMAIL_DRAW:
10621             if (appData.debugMode)
10622               fprintf(debugFP, "Restoring %s for game %d\n",
10623                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10624
10625             thinkOutput[0] = NULLCHAR;
10626             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10627             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10628             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10629             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10630             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10631             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10632             MakeMove(fromX, fromY, toX, toY, promoChar);
10633             ShowMove(fromX, fromY, toX, toY);
10634
10635             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10636               case MT_NONE:
10637               case MT_CHECK:
10638                 break;
10639
10640               case MT_CHECKMATE:
10641               case MT_STAINMATE:
10642                 if (WhiteOnMove(currentMove)) {
10643                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10644                 } else {
10645                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10646                 }
10647                 break;
10648
10649               case MT_STALEMATE:
10650                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10651                 break;
10652             }
10653
10654             break;
10655
10656           case CMAIL_RESIGN:
10657             if (WhiteOnMove(currentMove)) {
10658                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10659             } else {
10660                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10661             }
10662             break;
10663
10664           case CMAIL_ACCEPT:
10665             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10666             break;
10667
10668           default:
10669             break;
10670         }
10671     }
10672
10673     return;
10674 }
10675
10676 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10677 int
10678 CmailLoadGame(f, gameNumber, title, useList)
10679      FILE *f;
10680      int gameNumber;
10681      char *title;
10682      int useList;
10683 {
10684     int retVal;
10685
10686     if (gameNumber > nCmailGames) {
10687         DisplayError(_("No more games in this message"), 0);
10688         return FALSE;
10689     }
10690     if (f == lastLoadGameFP) {
10691         int offset = gameNumber - lastLoadGameNumber;
10692         if (offset == 0) {
10693             cmailMsg[0] = NULLCHAR;
10694             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10695                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10696                 nCmailMovesRegistered--;
10697             }
10698             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10699             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10700                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10701             }
10702         } else {
10703             if (! RegisterMove()) return FALSE;
10704         }
10705     }
10706
10707     retVal = LoadGame(f, gameNumber, title, useList);
10708
10709     /* Make move registered during previous look at this game, if any */
10710     MakeRegisteredMove();
10711
10712     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10713         commentList[currentMove]
10714           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10715         DisplayComment(currentMove - 1, commentList[currentMove]);
10716     }
10717
10718     return retVal;
10719 }
10720
10721 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10722 int
10723 ReloadGame(offset)
10724      int offset;
10725 {
10726     int gameNumber = lastLoadGameNumber + offset;
10727     if (lastLoadGameFP == NULL) {
10728         DisplayError(_("No game has been loaded yet"), 0);
10729         return FALSE;
10730     }
10731     if (gameNumber <= 0) {
10732         DisplayError(_("Can't back up any further"), 0);
10733         return FALSE;
10734     }
10735     if (cmailMsgLoaded) {
10736         return CmailLoadGame(lastLoadGameFP, gameNumber,
10737                              lastLoadGameTitle, lastLoadGameUseList);
10738     } else {
10739         return LoadGame(lastLoadGameFP, gameNumber,
10740                         lastLoadGameTitle, lastLoadGameUseList);
10741     }
10742 }
10743
10744
10745
10746 /* Load the nth game from open file f */
10747 int
10748 LoadGame(f, gameNumber, title, useList)
10749      FILE *f;
10750      int gameNumber;
10751      char *title;
10752      int useList;
10753 {
10754     ChessMove cm;
10755     char buf[MSG_SIZ];
10756     int gn = gameNumber;
10757     ListGame *lg = NULL;
10758     int numPGNTags = 0;
10759     int err;
10760     GameMode oldGameMode;
10761     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10762
10763     if (appData.debugMode)
10764         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10765
10766     if (gameMode == Training )
10767         SetTrainingModeOff();
10768
10769     oldGameMode = gameMode;
10770     if (gameMode != BeginningOfGame) {
10771       Reset(FALSE, TRUE);
10772     }
10773
10774     gameFileFP = f;
10775     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10776         fclose(lastLoadGameFP);
10777     }
10778
10779     if (useList) {
10780         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10781
10782         if (lg) {
10783             fseek(f, lg->offset, 0);
10784             GameListHighlight(gameNumber);
10785             gn = 1;
10786         }
10787         else {
10788             DisplayError(_("Game number out of range"), 0);
10789             return FALSE;
10790         }
10791     } else {
10792         GameListDestroy();
10793         if (fseek(f, 0, 0) == -1) {
10794             if (f == lastLoadGameFP ?
10795                 gameNumber == lastLoadGameNumber + 1 :
10796                 gameNumber == 1) {
10797                 gn = 1;
10798             } else {
10799                 DisplayError(_("Can't seek on game file"), 0);
10800                 return FALSE;
10801             }
10802         }
10803     }
10804     lastLoadGameFP = f;
10805     lastLoadGameNumber = gameNumber;
10806     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10807     lastLoadGameUseList = useList;
10808
10809     yynewfile(f);
10810
10811     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10812       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10813                 lg->gameInfo.black);
10814             DisplayTitle(buf);
10815     } else if (*title != NULLCHAR) {
10816         if (gameNumber > 1) {
10817           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10818             DisplayTitle(buf);
10819         } else {
10820             DisplayTitle(title);
10821         }
10822     }
10823
10824     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10825         gameMode = PlayFromGameFile;
10826         ModeHighlight();
10827     }
10828
10829     currentMove = forwardMostMove = backwardMostMove = 0;
10830     CopyBoard(boards[0], initialPosition);
10831     StopClocks();
10832
10833     /*
10834      * Skip the first gn-1 games in the file.
10835      * Also skip over anything that precedes an identifiable
10836      * start of game marker, to avoid being confused by
10837      * garbage at the start of the file.  Currently
10838      * recognized start of game markers are the move number "1",
10839      * the pattern "gnuchess .* game", the pattern
10840      * "^[#;%] [^ ]* game file", and a PGN tag block.
10841      * A game that starts with one of the latter two patterns
10842      * will also have a move number 1, possibly
10843      * following a position diagram.
10844      * 5-4-02: Let's try being more lenient and allowing a game to
10845      * start with an unnumbered move.  Does that break anything?
10846      */
10847     cm = lastLoadGameStart = EndOfFile;
10848     while (gn > 0) {
10849         yyboardindex = forwardMostMove;
10850         cm = (ChessMove) Myylex();
10851         switch (cm) {
10852           case EndOfFile:
10853             if (cmailMsgLoaded) {
10854                 nCmailGames = CMAIL_MAX_GAMES - gn;
10855             } else {
10856                 Reset(TRUE, TRUE);
10857                 DisplayError(_("Game not found in file"), 0);
10858             }
10859             return FALSE;
10860
10861           case GNUChessGame:
10862           case XBoardGame:
10863             gn--;
10864             lastLoadGameStart = cm;
10865             break;
10866
10867           case MoveNumberOne:
10868             switch (lastLoadGameStart) {
10869               case GNUChessGame:
10870               case XBoardGame:
10871               case PGNTag:
10872                 break;
10873               case MoveNumberOne:
10874               case EndOfFile:
10875                 gn--;           /* count this game */
10876                 lastLoadGameStart = cm;
10877                 break;
10878               default:
10879                 /* impossible */
10880                 break;
10881             }
10882             break;
10883
10884           case PGNTag:
10885             switch (lastLoadGameStart) {
10886               case GNUChessGame:
10887               case PGNTag:
10888               case MoveNumberOne:
10889               case EndOfFile:
10890                 gn--;           /* count this game */
10891                 lastLoadGameStart = cm;
10892                 break;
10893               case XBoardGame:
10894                 lastLoadGameStart = cm; /* game counted already */
10895                 break;
10896               default:
10897                 /* impossible */
10898                 break;
10899             }
10900             if (gn > 0) {
10901                 do {
10902                     yyboardindex = forwardMostMove;
10903                     cm = (ChessMove) Myylex();
10904                 } while (cm == PGNTag || cm == Comment);
10905             }
10906             break;
10907
10908           case WhiteWins:
10909           case BlackWins:
10910           case GameIsDrawn:
10911             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10912                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10913                     != CMAIL_OLD_RESULT) {
10914                     nCmailResults ++ ;
10915                     cmailResult[  CMAIL_MAX_GAMES
10916                                 - gn - 1] = CMAIL_OLD_RESULT;
10917                 }
10918             }
10919             break;
10920
10921           case NormalMove:
10922             /* Only a NormalMove can be at the start of a game
10923              * without a position diagram. */
10924             if (lastLoadGameStart == EndOfFile ) {
10925               gn--;
10926               lastLoadGameStart = MoveNumberOne;
10927             }
10928             break;
10929
10930           default:
10931             break;
10932         }
10933     }
10934
10935     if (appData.debugMode)
10936       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10937
10938     if (cm == XBoardGame) {
10939         /* Skip any header junk before position diagram and/or move 1 */
10940         for (;;) {
10941             yyboardindex = forwardMostMove;
10942             cm = (ChessMove) Myylex();
10943
10944             if (cm == EndOfFile ||
10945                 cm == GNUChessGame || cm == XBoardGame) {
10946                 /* Empty game; pretend end-of-file and handle later */
10947                 cm = EndOfFile;
10948                 break;
10949             }
10950
10951             if (cm == MoveNumberOne || cm == PositionDiagram ||
10952                 cm == PGNTag || cm == Comment)
10953               break;
10954         }
10955     } else if (cm == GNUChessGame) {
10956         if (gameInfo.event != NULL) {
10957             free(gameInfo.event);
10958         }
10959         gameInfo.event = StrSave(yy_text);
10960     }
10961
10962     startedFromSetupPosition = FALSE;
10963     while (cm == PGNTag) {
10964         if (appData.debugMode)
10965           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10966         err = ParsePGNTag(yy_text, &gameInfo);
10967         if (!err) numPGNTags++;
10968
10969         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10970         if(gameInfo.variant != oldVariant) {
10971             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10972             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10973             InitPosition(TRUE);
10974             oldVariant = gameInfo.variant;
10975             if (appData.debugMode)
10976               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10977         }
10978
10979
10980         if (gameInfo.fen != NULL) {
10981           Board initial_position;
10982           startedFromSetupPosition = TRUE;
10983           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10984             Reset(TRUE, TRUE);
10985             DisplayError(_("Bad FEN position in file"), 0);
10986             return FALSE;
10987           }
10988           CopyBoard(boards[0], initial_position);
10989           if (blackPlaysFirst) {
10990             currentMove = forwardMostMove = backwardMostMove = 1;
10991             CopyBoard(boards[1], initial_position);
10992             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10993             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10994             timeRemaining[0][1] = whiteTimeRemaining;
10995             timeRemaining[1][1] = blackTimeRemaining;
10996             if (commentList[0] != NULL) {
10997               commentList[1] = commentList[0];
10998               commentList[0] = NULL;
10999             }
11000           } else {
11001             currentMove = forwardMostMove = backwardMostMove = 0;
11002           }
11003           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11004           {   int i;
11005               initialRulePlies = FENrulePlies;
11006               for( i=0; i< nrCastlingRights; i++ )
11007                   initialRights[i] = initial_position[CASTLING][i];
11008           }
11009           yyboardindex = forwardMostMove;
11010           free(gameInfo.fen);
11011           gameInfo.fen = NULL;
11012         }
11013
11014         yyboardindex = forwardMostMove;
11015         cm = (ChessMove) Myylex();
11016
11017         /* Handle comments interspersed among the tags */
11018         while (cm == Comment) {
11019             char *p;
11020             if (appData.debugMode)
11021               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11022             p = yy_text;
11023             AppendComment(currentMove, p, FALSE);
11024             yyboardindex = forwardMostMove;
11025             cm = (ChessMove) Myylex();
11026         }
11027     }
11028
11029     /* don't rely on existence of Event tag since if game was
11030      * pasted from clipboard the Event tag may not exist
11031      */
11032     if (numPGNTags > 0){
11033         char *tags;
11034         if (gameInfo.variant == VariantNormal) {
11035           VariantClass v = StringToVariant(gameInfo.event);
11036           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11037           if(v < VariantShogi) gameInfo.variant = v;
11038         }
11039         if (!matchMode) {
11040           if( appData.autoDisplayTags ) {
11041             tags = PGNTags(&gameInfo);
11042             TagsPopUp(tags, CmailMsg());
11043             free(tags);
11044           }
11045         }
11046     } else {
11047         /* Make something up, but don't display it now */
11048         SetGameInfo();
11049         TagsPopDown();
11050     }
11051
11052     if (cm == PositionDiagram) {
11053         int i, j;
11054         char *p;
11055         Board initial_position;
11056
11057         if (appData.debugMode)
11058           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11059
11060         if (!startedFromSetupPosition) {
11061             p = yy_text;
11062             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11063               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11064                 switch (*p) {
11065                   case '{':
11066                   case '[':
11067                   case '-':
11068                   case ' ':
11069                   case '\t':
11070                   case '\n':
11071                   case '\r':
11072                     break;
11073                   default:
11074                     initial_position[i][j++] = CharToPiece(*p);
11075                     break;
11076                 }
11077             while (*p == ' ' || *p == '\t' ||
11078                    *p == '\n' || *p == '\r') p++;
11079
11080             if (strncmp(p, "black", strlen("black"))==0)
11081               blackPlaysFirst = TRUE;
11082             else
11083               blackPlaysFirst = FALSE;
11084             startedFromSetupPosition = TRUE;
11085
11086             CopyBoard(boards[0], initial_position);
11087             if (blackPlaysFirst) {
11088                 currentMove = forwardMostMove = backwardMostMove = 1;
11089                 CopyBoard(boards[1], initial_position);
11090                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11091                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11092                 timeRemaining[0][1] = whiteTimeRemaining;
11093                 timeRemaining[1][1] = blackTimeRemaining;
11094                 if (commentList[0] != NULL) {
11095                     commentList[1] = commentList[0];
11096                     commentList[0] = NULL;
11097                 }
11098             } else {
11099                 currentMove = forwardMostMove = backwardMostMove = 0;
11100             }
11101         }
11102         yyboardindex = forwardMostMove;
11103         cm = (ChessMove) Myylex();
11104     }
11105
11106     if (first.pr == NoProc) {
11107         StartChessProgram(&first);
11108     }
11109     InitChessProgram(&first, FALSE);
11110     SendToProgram("force\n", &first);
11111     if (startedFromSetupPosition) {
11112         SendBoard(&first, forwardMostMove);
11113     if (appData.debugMode) {
11114         fprintf(debugFP, "Load Game\n");
11115     }
11116         DisplayBothClocks();
11117     }
11118
11119     /* [HGM] server: flag to write setup moves in broadcast file as one */
11120     loadFlag = appData.suppressLoadMoves;
11121
11122     while (cm == Comment) {
11123         char *p;
11124         if (appData.debugMode)
11125           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11126         p = yy_text;
11127         AppendComment(currentMove, p, FALSE);
11128         yyboardindex = forwardMostMove;
11129         cm = (ChessMove) Myylex();
11130     }
11131
11132     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11133         cm == WhiteWins || cm == BlackWins ||
11134         cm == GameIsDrawn || cm == GameUnfinished) {
11135         DisplayMessage("", _("No moves in game"));
11136         if (cmailMsgLoaded) {
11137             if (appData.debugMode)
11138               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11139             ClearHighlights();
11140             flipView = FALSE;
11141         }
11142         DrawPosition(FALSE, boards[currentMove]);
11143         DisplayBothClocks();
11144         gameMode = EditGame;
11145         ModeHighlight();
11146         gameFileFP = NULL;
11147         cmailOldMove = 0;
11148         return TRUE;
11149     }
11150
11151     // [HGM] PV info: routine tests if comment empty
11152     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11153         DisplayComment(currentMove - 1, commentList[currentMove]);
11154     }
11155     if (!matchMode && appData.timeDelay != 0)
11156       DrawPosition(FALSE, boards[currentMove]);
11157
11158     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11159       programStats.ok_to_send = 1;
11160     }
11161
11162     /* if the first token after the PGN tags is a move
11163      * and not move number 1, retrieve it from the parser
11164      */
11165     if (cm != MoveNumberOne)
11166         LoadGameOneMove(cm);
11167
11168     /* load the remaining moves from the file */
11169     while (LoadGameOneMove(EndOfFile)) {
11170       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11171       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11172     }
11173
11174     /* rewind to the start of the game */
11175     currentMove = backwardMostMove;
11176
11177     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11178
11179     if (oldGameMode == AnalyzeFile ||
11180         oldGameMode == AnalyzeMode) {
11181       AnalyzeFileEvent();
11182     }
11183
11184     if (matchMode || appData.timeDelay == 0) {
11185       ToEndEvent();
11186       gameMode = EditGame;
11187       ModeHighlight();
11188     } else if (appData.timeDelay > 0) {
11189       AutoPlayGameLoop();
11190     }
11191
11192     if (appData.debugMode)
11193         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11194
11195     loadFlag = 0; /* [HGM] true game starts */
11196     return TRUE;
11197 }
11198
11199 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11200 int
11201 ReloadPosition(offset)
11202      int offset;
11203 {
11204     int positionNumber = lastLoadPositionNumber + offset;
11205     if (lastLoadPositionFP == NULL) {
11206         DisplayError(_("No position has been loaded yet"), 0);
11207         return FALSE;
11208     }
11209     if (positionNumber <= 0) {
11210         DisplayError(_("Can't back up any further"), 0);
11211         return FALSE;
11212     }
11213     return LoadPosition(lastLoadPositionFP, positionNumber,
11214                         lastLoadPositionTitle);
11215 }
11216
11217 /* Load the nth position from the given file */
11218 int
11219 LoadPositionFromFile(filename, n, title)
11220      char *filename;
11221      int n;
11222      char *title;
11223 {
11224     FILE *f;
11225     char buf[MSG_SIZ];
11226
11227     if (strcmp(filename, "-") == 0) {
11228         return LoadPosition(stdin, n, "stdin");
11229     } else {
11230         f = fopen(filename, "rb");
11231         if (f == NULL) {
11232             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11233             DisplayError(buf, errno);
11234             return FALSE;
11235         } else {
11236             return LoadPosition(f, n, title);
11237         }
11238     }
11239 }
11240
11241 /* Load the nth position from the given open file, and close it */
11242 int
11243 LoadPosition(f, positionNumber, title)
11244      FILE *f;
11245      int positionNumber;
11246      char *title;
11247 {
11248     char *p, line[MSG_SIZ];
11249     Board initial_position;
11250     int i, j, fenMode, pn;
11251
11252     if (gameMode == Training )
11253         SetTrainingModeOff();
11254
11255     if (gameMode != BeginningOfGame) {
11256         Reset(FALSE, TRUE);
11257     }
11258     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11259         fclose(lastLoadPositionFP);
11260     }
11261     if (positionNumber == 0) positionNumber = 1;
11262     lastLoadPositionFP = f;
11263     lastLoadPositionNumber = positionNumber;
11264     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11265     if (first.pr == NoProc) {
11266       StartChessProgram(&first);
11267       InitChessProgram(&first, FALSE);
11268     }
11269     pn = positionNumber;
11270     if (positionNumber < 0) {
11271         /* Negative position number means to seek to that byte offset */
11272         if (fseek(f, -positionNumber, 0) == -1) {
11273             DisplayError(_("Can't seek on position file"), 0);
11274             return FALSE;
11275         };
11276         pn = 1;
11277     } else {
11278         if (fseek(f, 0, 0) == -1) {
11279             if (f == lastLoadPositionFP ?
11280                 positionNumber == lastLoadPositionNumber + 1 :
11281                 positionNumber == 1) {
11282                 pn = 1;
11283             } else {
11284                 DisplayError(_("Can't seek on position file"), 0);
11285                 return FALSE;
11286             }
11287         }
11288     }
11289     /* See if this file is FEN or old-style xboard */
11290     if (fgets(line, MSG_SIZ, f) == NULL) {
11291         DisplayError(_("Position not found in file"), 0);
11292         return FALSE;
11293     }
11294     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11295     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11296
11297     if (pn >= 2) {
11298         if (fenMode || line[0] == '#') pn--;
11299         while (pn > 0) {
11300             /* skip positions before number pn */
11301             if (fgets(line, MSG_SIZ, f) == NULL) {
11302                 Reset(TRUE, TRUE);
11303                 DisplayError(_("Position not found in file"), 0);
11304                 return FALSE;
11305             }
11306             if (fenMode || line[0] == '#') pn--;
11307         }
11308     }
11309
11310     if (fenMode) {
11311         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11312             DisplayError(_("Bad FEN position in file"), 0);
11313             return FALSE;
11314         }
11315     } else {
11316         (void) fgets(line, MSG_SIZ, f);
11317         (void) fgets(line, MSG_SIZ, f);
11318
11319         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11320             (void) fgets(line, MSG_SIZ, f);
11321             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11322                 if (*p == ' ')
11323                   continue;
11324                 initial_position[i][j++] = CharToPiece(*p);
11325             }
11326         }
11327
11328         blackPlaysFirst = FALSE;
11329         if (!feof(f)) {
11330             (void) fgets(line, MSG_SIZ, f);
11331             if (strncmp(line, "black", strlen("black"))==0)
11332               blackPlaysFirst = TRUE;
11333         }
11334     }
11335     startedFromSetupPosition = TRUE;
11336
11337     SendToProgram("force\n", &first);
11338     CopyBoard(boards[0], initial_position);
11339     if (blackPlaysFirst) {
11340         currentMove = forwardMostMove = backwardMostMove = 1;
11341         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11342         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11343         CopyBoard(boards[1], initial_position);
11344         DisplayMessage("", _("Black to play"));
11345     } else {
11346         currentMove = forwardMostMove = backwardMostMove = 0;
11347         DisplayMessage("", _("White to play"));
11348     }
11349     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11350     SendBoard(&first, forwardMostMove);
11351     if (appData.debugMode) {
11352 int i, j;
11353   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11354   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11355         fprintf(debugFP, "Load Position\n");
11356     }
11357
11358     if (positionNumber > 1) {
11359       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11360         DisplayTitle(line);
11361     } else {
11362         DisplayTitle(title);
11363     }
11364     gameMode = EditGame;
11365     ModeHighlight();
11366     ResetClocks();
11367     timeRemaining[0][1] = whiteTimeRemaining;
11368     timeRemaining[1][1] = blackTimeRemaining;
11369     DrawPosition(FALSE, boards[currentMove]);
11370
11371     return TRUE;
11372 }
11373
11374
11375 void
11376 CopyPlayerNameIntoFileName(dest, src)
11377      char **dest, *src;
11378 {
11379     while (*src != NULLCHAR && *src != ',') {
11380         if (*src == ' ') {
11381             *(*dest)++ = '_';
11382             src++;
11383         } else {
11384             *(*dest)++ = *src++;
11385         }
11386     }
11387 }
11388
11389 char *DefaultFileName(ext)
11390      char *ext;
11391 {
11392     static char def[MSG_SIZ];
11393     char *p;
11394
11395     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11396         p = def;
11397         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11398         *p++ = '-';
11399         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11400         *p++ = '.';
11401         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11402     } else {
11403         def[0] = NULLCHAR;
11404     }
11405     return def;
11406 }
11407
11408 /* Save the current game to the given file */
11409 int
11410 SaveGameToFile(filename, append)
11411      char *filename;
11412      int append;
11413 {
11414     FILE *f;
11415     char buf[MSG_SIZ];
11416     int result;
11417
11418     if (strcmp(filename, "-") == 0) {
11419         return SaveGame(stdout, 0, NULL);
11420     } else {
11421         f = fopen(filename, append ? "a" : "w");
11422         if (f == NULL) {
11423             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11424             DisplayError(buf, errno);
11425             return FALSE;
11426         } else {
11427             safeStrCpy(buf, lastMsg, MSG_SIZ);
11428             DisplayMessage(_("Waiting for access to save file"), "");
11429             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11430             DisplayMessage(_("Saving game"), "");
11431             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11432             result = SaveGame(f, 0, NULL);
11433             DisplayMessage(buf, "");
11434             return result;
11435         }
11436     }
11437 }
11438
11439 char *
11440 SavePart(str)
11441      char *str;
11442 {
11443     static char buf[MSG_SIZ];
11444     char *p;
11445
11446     p = strchr(str, ' ');
11447     if (p == NULL) return str;
11448     strncpy(buf, str, p - str);
11449     buf[p - str] = NULLCHAR;
11450     return buf;
11451 }
11452
11453 #define PGN_MAX_LINE 75
11454
11455 #define PGN_SIDE_WHITE  0
11456 #define PGN_SIDE_BLACK  1
11457
11458 /* [AS] */
11459 static int FindFirstMoveOutOfBook( int side )
11460 {
11461     int result = -1;
11462
11463     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11464         int index = backwardMostMove;
11465         int has_book_hit = 0;
11466
11467         if( (index % 2) != side ) {
11468             index++;
11469         }
11470
11471         while( index < forwardMostMove ) {
11472             /* Check to see if engine is in book */
11473             int depth = pvInfoList[index].depth;
11474             int score = pvInfoList[index].score;
11475             int in_book = 0;
11476
11477             if( depth <= 2 ) {
11478                 in_book = 1;
11479             }
11480             else if( score == 0 && depth == 63 ) {
11481                 in_book = 1; /* Zappa */
11482             }
11483             else if( score == 2 && depth == 99 ) {
11484                 in_book = 1; /* Abrok */
11485             }
11486
11487             has_book_hit += in_book;
11488
11489             if( ! in_book ) {
11490                 result = index;
11491
11492                 break;
11493             }
11494
11495             index += 2;
11496         }
11497     }
11498
11499     return result;
11500 }
11501
11502 /* [AS] */
11503 void GetOutOfBookInfo( char * buf )
11504 {
11505     int oob[2];
11506     int i;
11507     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11508
11509     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11510     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11511
11512     *buf = '\0';
11513
11514     if( oob[0] >= 0 || oob[1] >= 0 ) {
11515         for( i=0; i<2; i++ ) {
11516             int idx = oob[i];
11517
11518             if( idx >= 0 ) {
11519                 if( i > 0 && oob[0] >= 0 ) {
11520                     strcat( buf, "   " );
11521                 }
11522
11523                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11524                 sprintf( buf+strlen(buf), "%s%.2f",
11525                     pvInfoList[idx].score >= 0 ? "+" : "",
11526                     pvInfoList[idx].score / 100.0 );
11527             }
11528         }
11529     }
11530 }
11531
11532 /* Save game in PGN style and close the file */
11533 int
11534 SaveGamePGN(f)
11535      FILE *f;
11536 {
11537     int i, offset, linelen, newblock;
11538     time_t tm;
11539 //    char *movetext;
11540     char numtext[32];
11541     int movelen, numlen, blank;
11542     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11543
11544     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11545
11546     tm = time((time_t *) NULL);
11547
11548     PrintPGNTags(f, &gameInfo);
11549
11550     if (backwardMostMove > 0 || startedFromSetupPosition) {
11551         char *fen = PositionToFEN(backwardMostMove, NULL);
11552         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11553         fprintf(f, "\n{--------------\n");
11554         PrintPosition(f, backwardMostMove);
11555         fprintf(f, "--------------}\n");
11556         free(fen);
11557     }
11558     else {
11559         /* [AS] Out of book annotation */
11560         if( appData.saveOutOfBookInfo ) {
11561             char buf[64];
11562
11563             GetOutOfBookInfo( buf );
11564
11565             if( buf[0] != '\0' ) {
11566                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11567             }
11568         }
11569
11570         fprintf(f, "\n");
11571     }
11572
11573     i = backwardMostMove;
11574     linelen = 0;
11575     newblock = TRUE;
11576
11577     while (i < forwardMostMove) {
11578         /* Print comments preceding this move */
11579         if (commentList[i] != NULL) {
11580             if (linelen > 0) fprintf(f, "\n");
11581             fprintf(f, "%s", commentList[i]);
11582             linelen = 0;
11583             newblock = TRUE;
11584         }
11585
11586         /* Format move number */
11587         if ((i % 2) == 0)
11588           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11589         else
11590           if (newblock)
11591             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11592           else
11593             numtext[0] = NULLCHAR;
11594
11595         numlen = strlen(numtext);
11596         newblock = FALSE;
11597
11598         /* Print move number */
11599         blank = linelen > 0 && numlen > 0;
11600         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11601             fprintf(f, "\n");
11602             linelen = 0;
11603             blank = 0;
11604         }
11605         if (blank) {
11606             fprintf(f, " ");
11607             linelen++;
11608         }
11609         fprintf(f, "%s", numtext);
11610         linelen += numlen;
11611
11612         /* Get move */
11613         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11614         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11615
11616         /* Print move */
11617         blank = linelen > 0 && movelen > 0;
11618         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11619             fprintf(f, "\n");
11620             linelen = 0;
11621             blank = 0;
11622         }
11623         if (blank) {
11624             fprintf(f, " ");
11625             linelen++;
11626         }
11627         fprintf(f, "%s", move_buffer);
11628         linelen += movelen;
11629
11630         /* [AS] Add PV info if present */
11631         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11632             /* [HGM] add time */
11633             char buf[MSG_SIZ]; int seconds;
11634
11635             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11636
11637             if( seconds <= 0)
11638               buf[0] = 0;
11639             else
11640               if( seconds < 30 )
11641                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11642               else
11643                 {
11644                   seconds = (seconds + 4)/10; // round to full seconds
11645                   if( seconds < 60 )
11646                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11647                   else
11648                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11649                 }
11650
11651             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11652                       pvInfoList[i].score >= 0 ? "+" : "",
11653                       pvInfoList[i].score / 100.0,
11654                       pvInfoList[i].depth,
11655                       buf );
11656
11657             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11658
11659             /* Print score/depth */
11660             blank = linelen > 0 && movelen > 0;
11661             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11662                 fprintf(f, "\n");
11663                 linelen = 0;
11664                 blank = 0;
11665             }
11666             if (blank) {
11667                 fprintf(f, " ");
11668                 linelen++;
11669             }
11670             fprintf(f, "%s", move_buffer);
11671             linelen += movelen;
11672         }
11673
11674         i++;
11675     }
11676
11677     /* Start a new line */
11678     if (linelen > 0) fprintf(f, "\n");
11679
11680     /* Print comments after last move */
11681     if (commentList[i] != NULL) {
11682         fprintf(f, "%s\n", commentList[i]);
11683     }
11684
11685     /* Print result */
11686     if (gameInfo.resultDetails != NULL &&
11687         gameInfo.resultDetails[0] != NULLCHAR) {
11688         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11689                 PGNResult(gameInfo.result));
11690     } else {
11691         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11692     }
11693
11694     fclose(f);
11695     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11696     return TRUE;
11697 }
11698
11699 /* Save game in old style and close the file */
11700 int
11701 SaveGameOldStyle(f)
11702      FILE *f;
11703 {
11704     int i, offset;
11705     time_t tm;
11706
11707     tm = time((time_t *) NULL);
11708
11709     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11710     PrintOpponents(f);
11711
11712     if (backwardMostMove > 0 || startedFromSetupPosition) {
11713         fprintf(f, "\n[--------------\n");
11714         PrintPosition(f, backwardMostMove);
11715         fprintf(f, "--------------]\n");
11716     } else {
11717         fprintf(f, "\n");
11718     }
11719
11720     i = backwardMostMove;
11721     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11722
11723     while (i < forwardMostMove) {
11724         if (commentList[i] != NULL) {
11725             fprintf(f, "[%s]\n", commentList[i]);
11726         }
11727
11728         if ((i % 2) == 1) {
11729             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11730             i++;
11731         } else {
11732             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11733             i++;
11734             if (commentList[i] != NULL) {
11735                 fprintf(f, "\n");
11736                 continue;
11737             }
11738             if (i >= forwardMostMove) {
11739                 fprintf(f, "\n");
11740                 break;
11741             }
11742             fprintf(f, "%s\n", parseList[i]);
11743             i++;
11744         }
11745     }
11746
11747     if (commentList[i] != NULL) {
11748         fprintf(f, "[%s]\n", commentList[i]);
11749     }
11750
11751     /* This isn't really the old style, but it's close enough */
11752     if (gameInfo.resultDetails != NULL &&
11753         gameInfo.resultDetails[0] != NULLCHAR) {
11754         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11755                 gameInfo.resultDetails);
11756     } else {
11757         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11758     }
11759
11760     fclose(f);
11761     return TRUE;
11762 }
11763
11764 /* Save the current game to open file f and close the file */
11765 int
11766 SaveGame(f, dummy, dummy2)
11767      FILE *f;
11768      int dummy;
11769      char *dummy2;
11770 {
11771     if (gameMode == EditPosition) EditPositionDone(TRUE);
11772     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11773     if (appData.oldSaveStyle)
11774       return SaveGameOldStyle(f);
11775     else
11776       return SaveGamePGN(f);
11777 }
11778
11779 /* Save the current position to the given file */
11780 int
11781 SavePositionToFile(filename)
11782      char *filename;
11783 {
11784     FILE *f;
11785     char buf[MSG_SIZ];
11786
11787     if (strcmp(filename, "-") == 0) {
11788         return SavePosition(stdout, 0, NULL);
11789     } else {
11790         f = fopen(filename, "a");
11791         if (f == NULL) {
11792             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11793             DisplayError(buf, errno);
11794             return FALSE;
11795         } else {
11796             safeStrCpy(buf, lastMsg, MSG_SIZ);
11797             DisplayMessage(_("Waiting for access to save file"), "");
11798             flock(fileno(f), LOCK_EX); // [HGM] lock
11799             DisplayMessage(_("Saving position"), "");
11800             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11801             SavePosition(f, 0, NULL);
11802             DisplayMessage(buf, "");
11803             return TRUE;
11804         }
11805     }
11806 }
11807
11808 /* Save the current position to the given open file and close the file */
11809 int
11810 SavePosition(f, dummy, dummy2)
11811      FILE *f;
11812      int dummy;
11813      char *dummy2;
11814 {
11815     time_t tm;
11816     char *fen;
11817
11818     if (gameMode == EditPosition) EditPositionDone(TRUE);
11819     if (appData.oldSaveStyle) {
11820         tm = time((time_t *) NULL);
11821
11822         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11823         PrintOpponents(f);
11824         fprintf(f, "[--------------\n");
11825         PrintPosition(f, currentMove);
11826         fprintf(f, "--------------]\n");
11827     } else {
11828         fen = PositionToFEN(currentMove, NULL);
11829         fprintf(f, "%s\n", fen);
11830         free(fen);
11831     }
11832     fclose(f);
11833     return TRUE;
11834 }
11835
11836 void
11837 ReloadCmailMsgEvent(unregister)
11838      int unregister;
11839 {
11840 #if !WIN32
11841     static char *inFilename = NULL;
11842     static char *outFilename;
11843     int i;
11844     struct stat inbuf, outbuf;
11845     int status;
11846
11847     /* Any registered moves are unregistered if unregister is set, */
11848     /* i.e. invoked by the signal handler */
11849     if (unregister) {
11850         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11851             cmailMoveRegistered[i] = FALSE;
11852             if (cmailCommentList[i] != NULL) {
11853                 free(cmailCommentList[i]);
11854                 cmailCommentList[i] = NULL;
11855             }
11856         }
11857         nCmailMovesRegistered = 0;
11858     }
11859
11860     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11861         cmailResult[i] = CMAIL_NOT_RESULT;
11862     }
11863     nCmailResults = 0;
11864
11865     if (inFilename == NULL) {
11866         /* Because the filenames are static they only get malloced once  */
11867         /* and they never get freed                                      */
11868         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11869         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11870
11871         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11872         sprintf(outFilename, "%s.out", appData.cmailGameName);
11873     }
11874
11875     status = stat(outFilename, &outbuf);
11876     if (status < 0) {
11877         cmailMailedMove = FALSE;
11878     } else {
11879         status = stat(inFilename, &inbuf);
11880         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11881     }
11882
11883     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11884        counts the games, notes how each one terminated, etc.
11885
11886        It would be nice to remove this kludge and instead gather all
11887        the information while building the game list.  (And to keep it
11888        in the game list nodes instead of having a bunch of fixed-size
11889        parallel arrays.)  Note this will require getting each game's
11890        termination from the PGN tags, as the game list builder does
11891        not process the game moves.  --mann
11892        */
11893     cmailMsgLoaded = TRUE;
11894     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11895
11896     /* Load first game in the file or popup game menu */
11897     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11898
11899 #endif /* !WIN32 */
11900     return;
11901 }
11902
11903 int
11904 RegisterMove()
11905 {
11906     FILE *f;
11907     char string[MSG_SIZ];
11908
11909     if (   cmailMailedMove
11910         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11911         return TRUE;            /* Allow free viewing  */
11912     }
11913
11914     /* Unregister move to ensure that we don't leave RegisterMove        */
11915     /* with the move registered when the conditions for registering no   */
11916     /* longer hold                                                       */
11917     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11918         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11919         nCmailMovesRegistered --;
11920
11921         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11922           {
11923               free(cmailCommentList[lastLoadGameNumber - 1]);
11924               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11925           }
11926     }
11927
11928     if (cmailOldMove == -1) {
11929         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11930         return FALSE;
11931     }
11932
11933     if (currentMove > cmailOldMove + 1) {
11934         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11935         return FALSE;
11936     }
11937
11938     if (currentMove < cmailOldMove) {
11939         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11940         return FALSE;
11941     }
11942
11943     if (forwardMostMove > currentMove) {
11944         /* Silently truncate extra moves */
11945         TruncateGame();
11946     }
11947
11948     if (   (currentMove == cmailOldMove + 1)
11949         || (   (currentMove == cmailOldMove)
11950             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11951                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11952         if (gameInfo.result != GameUnfinished) {
11953             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11954         }
11955
11956         if (commentList[currentMove] != NULL) {
11957             cmailCommentList[lastLoadGameNumber - 1]
11958               = StrSave(commentList[currentMove]);
11959         }
11960         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11961
11962         if (appData.debugMode)
11963           fprintf(debugFP, "Saving %s for game %d\n",
11964                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11965
11966         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11967
11968         f = fopen(string, "w");
11969         if (appData.oldSaveStyle) {
11970             SaveGameOldStyle(f); /* also closes the file */
11971
11972             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11973             f = fopen(string, "w");
11974             SavePosition(f, 0, NULL); /* also closes the file */
11975         } else {
11976             fprintf(f, "{--------------\n");
11977             PrintPosition(f, currentMove);
11978             fprintf(f, "--------------}\n\n");
11979
11980             SaveGame(f, 0, NULL); /* also closes the file*/
11981         }
11982
11983         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11984         nCmailMovesRegistered ++;
11985     } else if (nCmailGames == 1) {
11986         DisplayError(_("You have not made a move yet"), 0);
11987         return FALSE;
11988     }
11989
11990     return TRUE;
11991 }
11992
11993 void
11994 MailMoveEvent()
11995 {
11996 #if !WIN32
11997     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11998     FILE *commandOutput;
11999     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12000     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12001     int nBuffers;
12002     int i;
12003     int archived;
12004     char *arcDir;
12005
12006     if (! cmailMsgLoaded) {
12007         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12008         return;
12009     }
12010
12011     if (nCmailGames == nCmailResults) {
12012         DisplayError(_("No unfinished games"), 0);
12013         return;
12014     }
12015
12016 #if CMAIL_PROHIBIT_REMAIL
12017     if (cmailMailedMove) {
12018       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);
12019         DisplayError(msg, 0);
12020         return;
12021     }
12022 #endif
12023
12024     if (! (cmailMailedMove || RegisterMove())) return;
12025
12026     if (   cmailMailedMove
12027         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12028       snprintf(string, MSG_SIZ, partCommandString,
12029                appData.debugMode ? " -v" : "", appData.cmailGameName);
12030         commandOutput = popen(string, "r");
12031
12032         if (commandOutput == NULL) {
12033             DisplayError(_("Failed to invoke cmail"), 0);
12034         } else {
12035             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12036                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12037             }
12038             if (nBuffers > 1) {
12039                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12040                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12041                 nBytes = MSG_SIZ - 1;
12042             } else {
12043                 (void) memcpy(msg, buffer, nBytes);
12044             }
12045             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12046
12047             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12048                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12049
12050                 archived = TRUE;
12051                 for (i = 0; i < nCmailGames; i ++) {
12052                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12053                         archived = FALSE;
12054                     }
12055                 }
12056                 if (   archived
12057                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12058                         != NULL)) {
12059                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12060                            arcDir,
12061                            appData.cmailGameName,
12062                            gameInfo.date);
12063                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12064                     cmailMsgLoaded = FALSE;
12065                 }
12066             }
12067
12068             DisplayInformation(msg);
12069             pclose(commandOutput);
12070         }
12071     } else {
12072         if ((*cmailMsg) != '\0') {
12073             DisplayInformation(cmailMsg);
12074         }
12075     }
12076
12077     return;
12078 #endif /* !WIN32 */
12079 }
12080
12081 char *
12082 CmailMsg()
12083 {
12084 #if WIN32
12085     return NULL;
12086 #else
12087     int  prependComma = 0;
12088     char number[5];
12089     char string[MSG_SIZ];       /* Space for game-list */
12090     int  i;
12091
12092     if (!cmailMsgLoaded) return "";
12093
12094     if (cmailMailedMove) {
12095       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12096     } else {
12097         /* Create a list of games left */
12098       snprintf(string, MSG_SIZ, "[");
12099         for (i = 0; i < nCmailGames; i ++) {
12100             if (! (   cmailMoveRegistered[i]
12101                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12102                 if (prependComma) {
12103                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12104                 } else {
12105                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12106                     prependComma = 1;
12107                 }
12108
12109                 strcat(string, number);
12110             }
12111         }
12112         strcat(string, "]");
12113
12114         if (nCmailMovesRegistered + nCmailResults == 0) {
12115             switch (nCmailGames) {
12116               case 1:
12117                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12118                 break;
12119
12120               case 2:
12121                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12122                 break;
12123
12124               default:
12125                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12126                          nCmailGames);
12127                 break;
12128             }
12129         } else {
12130             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12131               case 1:
12132                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12133                          string);
12134                 break;
12135
12136               case 0:
12137                 if (nCmailResults == nCmailGames) {
12138                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12139                 } else {
12140                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12141                 }
12142                 break;
12143
12144               default:
12145                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12146                          string);
12147             }
12148         }
12149     }
12150     return cmailMsg;
12151 #endif /* WIN32 */
12152 }
12153
12154 void
12155 ResetGameEvent()
12156 {
12157     if (gameMode == Training)
12158       SetTrainingModeOff();
12159
12160     Reset(TRUE, TRUE);
12161     cmailMsgLoaded = FALSE;
12162     if (appData.icsActive) {
12163       SendToICS(ics_prefix);
12164       SendToICS("refresh\n");
12165     }
12166 }
12167
12168 void
12169 ExitEvent(status)
12170      int status;
12171 {
12172     exiting++;
12173     if (exiting > 2) {
12174       /* Give up on clean exit */
12175       exit(status);
12176     }
12177     if (exiting > 1) {
12178       /* Keep trying for clean exit */
12179       return;
12180     }
12181
12182     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12183
12184     if (telnetISR != NULL) {
12185       RemoveInputSource(telnetISR);
12186     }
12187     if (icsPR != NoProc) {
12188       DestroyChildProcess(icsPR, TRUE);
12189     }
12190
12191     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12192     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12193
12194     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12195     /* make sure this other one finishes before killing it!                  */
12196     if(endingGame) { int count = 0;
12197         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12198         while(endingGame && count++ < 10) DoSleep(1);
12199         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12200     }
12201
12202     /* Kill off chess programs */
12203     if (first.pr != NoProc) {
12204         ExitAnalyzeMode();
12205
12206         DoSleep( appData.delayBeforeQuit );
12207         SendToProgram("quit\n", &first);
12208         DoSleep( appData.delayAfterQuit );
12209         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12210     }
12211     if (second.pr != NoProc) {
12212         DoSleep( appData.delayBeforeQuit );
12213         SendToProgram("quit\n", &second);
12214         DoSleep( appData.delayAfterQuit );
12215         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12216     }
12217     if (first.isr != NULL) {
12218         RemoveInputSource(first.isr);
12219     }
12220     if (second.isr != NULL) {
12221         RemoveInputSource(second.isr);
12222     }
12223
12224     ShutDownFrontEnd();
12225     exit(status);
12226 }
12227
12228 void
12229 PauseEvent()
12230 {
12231     if (appData.debugMode)
12232         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12233     if (pausing) {
12234         pausing = FALSE;
12235         ModeHighlight();
12236         if (gameMode == MachinePlaysWhite ||
12237             gameMode == MachinePlaysBlack) {
12238             StartClocks();
12239         } else {
12240             DisplayBothClocks();
12241         }
12242         if (gameMode == PlayFromGameFile) {
12243             if (appData.timeDelay >= 0)
12244                 AutoPlayGameLoop();
12245         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12246             Reset(FALSE, TRUE);
12247             SendToICS(ics_prefix);
12248             SendToICS("refresh\n");
12249         } else if (currentMove < forwardMostMove) {
12250             ForwardInner(forwardMostMove);
12251         }
12252         pauseExamInvalid = FALSE;
12253     } else {
12254         switch (gameMode) {
12255           default:
12256             return;
12257           case IcsExamining:
12258             pauseExamForwardMostMove = forwardMostMove;
12259             pauseExamInvalid = FALSE;
12260             /* fall through */
12261           case IcsObserving:
12262           case IcsPlayingWhite:
12263           case IcsPlayingBlack:
12264             pausing = TRUE;
12265             ModeHighlight();
12266             return;
12267           case PlayFromGameFile:
12268             (void) StopLoadGameTimer();
12269             pausing = TRUE;
12270             ModeHighlight();
12271             break;
12272           case BeginningOfGame:
12273             if (appData.icsActive) return;
12274             /* else fall through */
12275           case MachinePlaysWhite:
12276           case MachinePlaysBlack:
12277           case TwoMachinesPlay:
12278             if (forwardMostMove == 0)
12279               return;           /* don't pause if no one has moved */
12280             if ((gameMode == MachinePlaysWhite &&
12281                  !WhiteOnMove(forwardMostMove)) ||
12282                 (gameMode == MachinePlaysBlack &&
12283                  WhiteOnMove(forwardMostMove))) {
12284                 StopClocks();
12285             }
12286             pausing = TRUE;
12287             ModeHighlight();
12288             break;
12289         }
12290     }
12291 }
12292
12293 void
12294 EditCommentEvent()
12295 {
12296     char title[MSG_SIZ];
12297
12298     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12299       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12300     } else {
12301       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12302                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12303                parseList[currentMove - 1]);
12304     }
12305
12306     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12307 }
12308
12309
12310 void
12311 EditTagsEvent()
12312 {
12313     char *tags = PGNTags(&gameInfo);
12314     EditTagsPopUp(tags, NULL);
12315     free(tags);
12316 }
12317
12318 void
12319 AnalyzeModeEvent()
12320 {
12321     if (appData.noChessProgram || gameMode == AnalyzeMode)
12322       return;
12323
12324     if (gameMode != AnalyzeFile) {
12325         if (!appData.icsEngineAnalyze) {
12326                EditGameEvent();
12327                if (gameMode != EditGame) return;
12328         }
12329         ResurrectChessProgram();
12330         SendToProgram("analyze\n", &first);
12331         first.analyzing = TRUE;
12332         /*first.maybeThinking = TRUE;*/
12333         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12334         EngineOutputPopUp();
12335     }
12336     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12337     pausing = FALSE;
12338     ModeHighlight();
12339     SetGameInfo();
12340
12341     StartAnalysisClock();
12342     GetTimeMark(&lastNodeCountTime);
12343     lastNodeCount = 0;
12344 }
12345
12346 void
12347 AnalyzeFileEvent()
12348 {
12349     if (appData.noChessProgram || gameMode == AnalyzeFile)
12350       return;
12351
12352     if (gameMode != AnalyzeMode) {
12353         EditGameEvent();
12354         if (gameMode != EditGame) return;
12355         ResurrectChessProgram();
12356         SendToProgram("analyze\n", &first);
12357         first.analyzing = TRUE;
12358         /*first.maybeThinking = TRUE;*/
12359         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12360         EngineOutputPopUp();
12361     }
12362     gameMode = AnalyzeFile;
12363     pausing = FALSE;
12364     ModeHighlight();
12365     SetGameInfo();
12366
12367     StartAnalysisClock();
12368     GetTimeMark(&lastNodeCountTime);
12369     lastNodeCount = 0;
12370 }
12371
12372 void
12373 MachineWhiteEvent()
12374 {
12375     char buf[MSG_SIZ];
12376     char *bookHit = NULL;
12377
12378     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12379       return;
12380
12381
12382     if (gameMode == PlayFromGameFile ||
12383         gameMode == TwoMachinesPlay  ||
12384         gameMode == Training         ||
12385         gameMode == AnalyzeMode      ||
12386         gameMode == EndOfGame)
12387         EditGameEvent();
12388
12389     if (gameMode == EditPosition)
12390         EditPositionDone(TRUE);
12391
12392     if (!WhiteOnMove(currentMove)) {
12393         DisplayError(_("It is not White's turn"), 0);
12394         return;
12395     }
12396
12397     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12398       ExitAnalyzeMode();
12399
12400     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12401         gameMode == AnalyzeFile)
12402         TruncateGame();
12403
12404     ResurrectChessProgram();    /* in case it isn't running */
12405     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12406         gameMode = MachinePlaysWhite;
12407         ResetClocks();
12408     } else
12409     gameMode = MachinePlaysWhite;
12410     pausing = FALSE;
12411     ModeHighlight();
12412     SetGameInfo();
12413     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12414     DisplayTitle(buf);
12415     if (first.sendName) {
12416       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12417       SendToProgram(buf, &first);
12418     }
12419     if (first.sendTime) {
12420       if (first.useColors) {
12421         SendToProgram("black\n", &first); /*gnu kludge*/
12422       }
12423       SendTimeRemaining(&first, TRUE);
12424     }
12425     if (first.useColors) {
12426       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12427     }
12428     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12429     SetMachineThinkingEnables();
12430     first.maybeThinking = TRUE;
12431     StartClocks();
12432     firstMove = FALSE;
12433
12434     if (appData.autoFlipView && !flipView) {
12435       flipView = !flipView;
12436       DrawPosition(FALSE, NULL);
12437       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12438     }
12439
12440     if(bookHit) { // [HGM] book: simulate book reply
12441         static char bookMove[MSG_SIZ]; // a bit generous?
12442
12443         programStats.nodes = programStats.depth = programStats.time =
12444         programStats.score = programStats.got_only_move = 0;
12445         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12446
12447         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12448         strcat(bookMove, bookHit);
12449         HandleMachineMove(bookMove, &first);
12450     }
12451 }
12452
12453 void
12454 MachineBlackEvent()
12455 {
12456   char buf[MSG_SIZ];
12457   char *bookHit = NULL;
12458
12459     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12460         return;
12461
12462
12463     if (gameMode == PlayFromGameFile ||
12464         gameMode == TwoMachinesPlay  ||
12465         gameMode == Training         ||
12466         gameMode == AnalyzeMode      ||
12467         gameMode == EndOfGame)
12468         EditGameEvent();
12469
12470     if (gameMode == EditPosition)
12471         EditPositionDone(TRUE);
12472
12473     if (WhiteOnMove(currentMove)) {
12474         DisplayError(_("It is not Black's turn"), 0);
12475         return;
12476     }
12477
12478     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12479       ExitAnalyzeMode();
12480
12481     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12482         gameMode == AnalyzeFile)
12483         TruncateGame();
12484
12485     ResurrectChessProgram();    /* in case it isn't running */
12486     gameMode = MachinePlaysBlack;
12487     pausing = FALSE;
12488     ModeHighlight();
12489     SetGameInfo();
12490     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12491     DisplayTitle(buf);
12492     if (first.sendName) {
12493       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12494       SendToProgram(buf, &first);
12495     }
12496     if (first.sendTime) {
12497       if (first.useColors) {
12498         SendToProgram("white\n", &first); /*gnu kludge*/
12499       }
12500       SendTimeRemaining(&first, FALSE);
12501     }
12502     if (first.useColors) {
12503       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12504     }
12505     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12506     SetMachineThinkingEnables();
12507     first.maybeThinking = TRUE;
12508     StartClocks();
12509
12510     if (appData.autoFlipView && flipView) {
12511       flipView = !flipView;
12512       DrawPosition(FALSE, NULL);
12513       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12514     }
12515     if(bookHit) { // [HGM] book: simulate book reply
12516         static char bookMove[MSG_SIZ]; // a bit generous?
12517
12518         programStats.nodes = programStats.depth = programStats.time =
12519         programStats.score = programStats.got_only_move = 0;
12520         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12521
12522         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12523         strcat(bookMove, bookHit);
12524         HandleMachineMove(bookMove, &first);
12525     }
12526 }
12527
12528
12529 void
12530 DisplayTwoMachinesTitle()
12531 {
12532     char buf[MSG_SIZ];
12533     if (appData.matchGames > 0) {
12534         if (first.twoMachinesColor[0] == 'w') {
12535           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12536                    gameInfo.white, gameInfo.black,
12537                    first.matchWins, second.matchWins,
12538                    matchGame - 1 - (first.matchWins + second.matchWins));
12539         } else {
12540           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12541                    gameInfo.white, gameInfo.black,
12542                    second.matchWins, first.matchWins,
12543                    matchGame - 1 - (first.matchWins + second.matchWins));
12544         }
12545     } else {
12546       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12547     }
12548     DisplayTitle(buf);
12549 }
12550
12551 void
12552 SettingsMenuIfReady()
12553 {
12554   if (second.lastPing != second.lastPong) {
12555     DisplayMessage("", _("Waiting for second chess program"));
12556     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12557     return;
12558   }
12559   ThawUI();
12560   DisplayMessage("", "");
12561   SettingsPopUp(&second);
12562 }
12563
12564 int
12565 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12566 {
12567     char buf[MSG_SIZ];
12568     if (cps->pr == NULL) {
12569         StartChessProgram(cps);
12570         if (cps->protocolVersion == 1) {
12571           retry();
12572         } else {
12573           /* kludge: allow timeout for initial "feature" command */
12574           FreezeUI();
12575           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12576           DisplayMessage("", buf);
12577           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12578         }
12579         return 1;
12580     }
12581     return 0;
12582 }
12583
12584 void
12585 TwoMachinesEvent P((void))
12586 {
12587     int i;
12588     char buf[MSG_SIZ];
12589     ChessProgramState *onmove;
12590     char *bookHit = NULL;
12591     static int stalling = 0;
12592     TimeMark now;
12593     long wait;
12594
12595     if (appData.noChessProgram) return;
12596
12597     switch (gameMode) {
12598       case TwoMachinesPlay:
12599         return;
12600       case MachinePlaysWhite:
12601       case MachinePlaysBlack:
12602         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12603             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12604             return;
12605         }
12606         /* fall through */
12607       case BeginningOfGame:
12608       case PlayFromGameFile:
12609       case EndOfGame:
12610         EditGameEvent();
12611         if (gameMode != EditGame) return;
12612         break;
12613       case EditPosition:
12614         EditPositionDone(TRUE);
12615         break;
12616       case AnalyzeMode:
12617       case AnalyzeFile:
12618         ExitAnalyzeMode();
12619         break;
12620       case EditGame:
12621       default:
12622         break;
12623     }
12624
12625 //    forwardMostMove = currentMove;
12626     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12627
12628     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12629
12630     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12631     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12632       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12633       return;
12634     }
12635     if(!stalling) {
12636       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12637       SendToProgram("force\n", &second);
12638       stalling = 1;
12639       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12640       return;
12641     }
12642     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12643     if(appData.matchPause>10000 || appData.matchPause<10)
12644                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12645     wait = SubtractTimeMarks(&now, &pauseStart);
12646     if(wait < appData.matchPause) {
12647         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12648         return;
12649     }
12650     stalling = 0;
12651     DisplayMessage("", "");
12652     if (startedFromSetupPosition) {
12653         SendBoard(&second, backwardMostMove);
12654     if (appData.debugMode) {
12655         fprintf(debugFP, "Two Machines\n");
12656     }
12657     }
12658     for (i = backwardMostMove; i < forwardMostMove; i++) {
12659         SendMoveToProgram(i, &second);
12660     }
12661
12662     gameMode = TwoMachinesPlay;
12663     pausing = FALSE;
12664     ModeHighlight();
12665     SetGameInfo();
12666     DisplayTwoMachinesTitle();
12667     firstMove = TRUE;
12668     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12669         onmove = &first;
12670     } else {
12671         onmove = &second;
12672     }
12673     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12674     SendToProgram(first.computerString, &first);
12675     if (first.sendName) {
12676       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12677       SendToProgram(buf, &first);
12678     }
12679     SendToProgram(second.computerString, &second);
12680     if (second.sendName) {
12681       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12682       SendToProgram(buf, &second);
12683     }
12684
12685     ResetClocks();
12686     if (!first.sendTime || !second.sendTime) {
12687         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12688         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12689     }
12690     if (onmove->sendTime) {
12691       if (onmove->useColors) {
12692         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12693       }
12694       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12695     }
12696     if (onmove->useColors) {
12697       SendToProgram(onmove->twoMachinesColor, onmove);
12698     }
12699     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12700 //    SendToProgram("go\n", onmove);
12701     onmove->maybeThinking = TRUE;
12702     SetMachineThinkingEnables();
12703
12704     StartClocks();
12705
12706     if(bookHit) { // [HGM] book: simulate book reply
12707         static char bookMove[MSG_SIZ]; // a bit generous?
12708
12709         programStats.nodes = programStats.depth = programStats.time =
12710         programStats.score = programStats.got_only_move = 0;
12711         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12712
12713         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12714         strcat(bookMove, bookHit);
12715         savedMessage = bookMove; // args for deferred call
12716         savedState = onmove;
12717         ScheduleDelayedEvent(DeferredBookMove, 1);
12718     }
12719 }
12720
12721 void
12722 TrainingEvent()
12723 {
12724     if (gameMode == Training) {
12725       SetTrainingModeOff();
12726       gameMode = PlayFromGameFile;
12727       DisplayMessage("", _("Training mode off"));
12728     } else {
12729       gameMode = Training;
12730       animateTraining = appData.animate;
12731
12732       /* make sure we are not already at the end of the game */
12733       if (currentMove < forwardMostMove) {
12734         SetTrainingModeOn();
12735         DisplayMessage("", _("Training mode on"));
12736       } else {
12737         gameMode = PlayFromGameFile;
12738         DisplayError(_("Already at end of game"), 0);
12739       }
12740     }
12741     ModeHighlight();
12742 }
12743
12744 void
12745 IcsClientEvent()
12746 {
12747     if (!appData.icsActive) return;
12748     switch (gameMode) {
12749       case IcsPlayingWhite:
12750       case IcsPlayingBlack:
12751       case IcsObserving:
12752       case IcsIdle:
12753       case BeginningOfGame:
12754       case IcsExamining:
12755         return;
12756
12757       case EditGame:
12758         break;
12759
12760       case EditPosition:
12761         EditPositionDone(TRUE);
12762         break;
12763
12764       case AnalyzeMode:
12765       case AnalyzeFile:
12766         ExitAnalyzeMode();
12767         break;
12768
12769       default:
12770         EditGameEvent();
12771         break;
12772     }
12773
12774     gameMode = IcsIdle;
12775     ModeHighlight();
12776     return;
12777 }
12778
12779
12780 void
12781 EditGameEvent()
12782 {
12783     int i;
12784
12785     switch (gameMode) {
12786       case Training:
12787         SetTrainingModeOff();
12788         break;
12789       case MachinePlaysWhite:
12790       case MachinePlaysBlack:
12791       case BeginningOfGame:
12792         SendToProgram("force\n", &first);
12793         SetUserThinkingEnables();
12794         break;
12795       case PlayFromGameFile:
12796         (void) StopLoadGameTimer();
12797         if (gameFileFP != NULL) {
12798             gameFileFP = NULL;
12799         }
12800         break;
12801       case EditPosition:
12802         EditPositionDone(TRUE);
12803         break;
12804       case AnalyzeMode:
12805       case AnalyzeFile:
12806         ExitAnalyzeMode();
12807         SendToProgram("force\n", &first);
12808         break;
12809       case TwoMachinesPlay:
12810         GameEnds(EndOfFile, NULL, GE_PLAYER);
12811         ResurrectChessProgram();
12812         SetUserThinkingEnables();
12813         break;
12814       case EndOfGame:
12815         ResurrectChessProgram();
12816         break;
12817       case IcsPlayingBlack:
12818       case IcsPlayingWhite:
12819         DisplayError(_("Warning: You are still playing a game"), 0);
12820         break;
12821       case IcsObserving:
12822         DisplayError(_("Warning: You are still observing a game"), 0);
12823         break;
12824       case IcsExamining:
12825         DisplayError(_("Warning: You are still examining a game"), 0);
12826         break;
12827       case IcsIdle:
12828         break;
12829       case EditGame:
12830       default:
12831         return;
12832     }
12833
12834     pausing = FALSE;
12835     StopClocks();
12836     first.offeredDraw = second.offeredDraw = 0;
12837
12838     if (gameMode == PlayFromGameFile) {
12839         whiteTimeRemaining = timeRemaining[0][currentMove];
12840         blackTimeRemaining = timeRemaining[1][currentMove];
12841         DisplayTitle("");
12842     }
12843
12844     if (gameMode == MachinePlaysWhite ||
12845         gameMode == MachinePlaysBlack ||
12846         gameMode == TwoMachinesPlay ||
12847         gameMode == EndOfGame) {
12848         i = forwardMostMove;
12849         while (i > currentMove) {
12850             SendToProgram("undo\n", &first);
12851             i--;
12852         }
12853         whiteTimeRemaining = timeRemaining[0][currentMove];
12854         blackTimeRemaining = timeRemaining[1][currentMove];
12855         DisplayBothClocks();
12856         if (whiteFlag || blackFlag) {
12857             whiteFlag = blackFlag = 0;
12858         }
12859         DisplayTitle("");
12860     }
12861
12862     gameMode = EditGame;
12863     ModeHighlight();
12864     SetGameInfo();
12865 }
12866
12867
12868 void
12869 EditPositionEvent()
12870 {
12871     if (gameMode == EditPosition) {
12872         EditGameEvent();
12873         return;
12874     }
12875
12876     EditGameEvent();
12877     if (gameMode != EditGame) return;
12878
12879     gameMode = EditPosition;
12880     ModeHighlight();
12881     SetGameInfo();
12882     if (currentMove > 0)
12883       CopyBoard(boards[0], boards[currentMove]);
12884
12885     blackPlaysFirst = !WhiteOnMove(currentMove);
12886     ResetClocks();
12887     currentMove = forwardMostMove = backwardMostMove = 0;
12888     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12889     DisplayMove(-1);
12890 }
12891
12892 void
12893 ExitAnalyzeMode()
12894 {
12895     /* [DM] icsEngineAnalyze - possible call from other functions */
12896     if (appData.icsEngineAnalyze) {
12897         appData.icsEngineAnalyze = FALSE;
12898
12899         DisplayMessage("",_("Close ICS engine analyze..."));
12900     }
12901     if (first.analysisSupport && first.analyzing) {
12902       SendToProgram("exit\n", &first);
12903       first.analyzing = FALSE;
12904     }
12905     thinkOutput[0] = NULLCHAR;
12906 }
12907
12908 void
12909 EditPositionDone(Boolean fakeRights)
12910 {
12911     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12912
12913     startedFromSetupPosition = TRUE;
12914     InitChessProgram(&first, FALSE);
12915     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12916       boards[0][EP_STATUS] = EP_NONE;
12917       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12918     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12919         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12920         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12921       } else boards[0][CASTLING][2] = NoRights;
12922     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12923         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12924         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12925       } else boards[0][CASTLING][5] = NoRights;
12926     }
12927     SendToProgram("force\n", &first);
12928     if (blackPlaysFirst) {
12929         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12930         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12931         currentMove = forwardMostMove = backwardMostMove = 1;
12932         CopyBoard(boards[1], boards[0]);
12933     } else {
12934         currentMove = forwardMostMove = backwardMostMove = 0;
12935     }
12936     SendBoard(&first, forwardMostMove);
12937     if (appData.debugMode) {
12938         fprintf(debugFP, "EditPosDone\n");
12939     }
12940     DisplayTitle("");
12941     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12942     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12943     gameMode = EditGame;
12944     ModeHighlight();
12945     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12946     ClearHighlights(); /* [AS] */
12947 }
12948
12949 /* Pause for `ms' milliseconds */
12950 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12951 void
12952 TimeDelay(ms)
12953      long ms;
12954 {
12955     TimeMark m1, m2;
12956
12957     GetTimeMark(&m1);
12958     do {
12959         GetTimeMark(&m2);
12960     } while (SubtractTimeMarks(&m2, &m1) < ms);
12961 }
12962
12963 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12964 void
12965 SendMultiLineToICS(buf)
12966      char *buf;
12967 {
12968     char temp[MSG_SIZ+1], *p;
12969     int len;
12970
12971     len = strlen(buf);
12972     if (len > MSG_SIZ)
12973       len = MSG_SIZ;
12974
12975     strncpy(temp, buf, len);
12976     temp[len] = 0;
12977
12978     p = temp;
12979     while (*p) {
12980         if (*p == '\n' || *p == '\r')
12981           *p = ' ';
12982         ++p;
12983     }
12984
12985     strcat(temp, "\n");
12986     SendToICS(temp);
12987     SendToPlayer(temp, strlen(temp));
12988 }
12989
12990 void
12991 SetWhiteToPlayEvent()
12992 {
12993     if (gameMode == EditPosition) {
12994         blackPlaysFirst = FALSE;
12995         DisplayBothClocks();    /* works because currentMove is 0 */
12996     } else if (gameMode == IcsExamining) {
12997         SendToICS(ics_prefix);
12998         SendToICS("tomove white\n");
12999     }
13000 }
13001
13002 void
13003 SetBlackToPlayEvent()
13004 {
13005     if (gameMode == EditPosition) {
13006         blackPlaysFirst = TRUE;
13007         currentMove = 1;        /* kludge */
13008         DisplayBothClocks();
13009         currentMove = 0;
13010     } else if (gameMode == IcsExamining) {
13011         SendToICS(ics_prefix);
13012         SendToICS("tomove black\n");
13013     }
13014 }
13015
13016 void
13017 EditPositionMenuEvent(selection, x, y)
13018      ChessSquare selection;
13019      int x, y;
13020 {
13021     char buf[MSG_SIZ];
13022     ChessSquare piece = boards[0][y][x];
13023
13024     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13025
13026     switch (selection) {
13027       case ClearBoard:
13028         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13029             SendToICS(ics_prefix);
13030             SendToICS("bsetup clear\n");
13031         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13032             SendToICS(ics_prefix);
13033             SendToICS("clearboard\n");
13034         } else {
13035             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13036                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13037                 for (y = 0; y < BOARD_HEIGHT; y++) {
13038                     if (gameMode == IcsExamining) {
13039                         if (boards[currentMove][y][x] != EmptySquare) {
13040                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13041                                     AAA + x, ONE + y);
13042                             SendToICS(buf);
13043                         }
13044                     } else {
13045                         boards[0][y][x] = p;
13046                     }
13047                 }
13048             }
13049         }
13050         if (gameMode == EditPosition) {
13051             DrawPosition(FALSE, boards[0]);
13052         }
13053         break;
13054
13055       case WhitePlay:
13056         SetWhiteToPlayEvent();
13057         break;
13058
13059       case BlackPlay:
13060         SetBlackToPlayEvent();
13061         break;
13062
13063       case EmptySquare:
13064         if (gameMode == IcsExamining) {
13065             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13066             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13067             SendToICS(buf);
13068         } else {
13069             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13070                 if(x == BOARD_LEFT-2) {
13071                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13072                     boards[0][y][1] = 0;
13073                 } else
13074                 if(x == BOARD_RGHT+1) {
13075                     if(y >= gameInfo.holdingsSize) break;
13076                     boards[0][y][BOARD_WIDTH-2] = 0;
13077                 } else break;
13078             }
13079             boards[0][y][x] = EmptySquare;
13080             DrawPosition(FALSE, boards[0]);
13081         }
13082         break;
13083
13084       case PromotePiece:
13085         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13086            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13087             selection = (ChessSquare) (PROMOTED piece);
13088         } else if(piece == EmptySquare) selection = WhiteSilver;
13089         else selection = (ChessSquare)((int)piece - 1);
13090         goto defaultlabel;
13091
13092       case DemotePiece:
13093         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13094            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13095             selection = (ChessSquare) (DEMOTED piece);
13096         } else if(piece == EmptySquare) selection = BlackSilver;
13097         else selection = (ChessSquare)((int)piece + 1);
13098         goto defaultlabel;
13099
13100       case WhiteQueen:
13101       case BlackQueen:
13102         if(gameInfo.variant == VariantShatranj ||
13103            gameInfo.variant == VariantXiangqi  ||
13104            gameInfo.variant == VariantCourier  ||
13105            gameInfo.variant == VariantMakruk     )
13106             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13107         goto defaultlabel;
13108
13109       case WhiteKing:
13110       case BlackKing:
13111         if(gameInfo.variant == VariantXiangqi)
13112             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13113         if(gameInfo.variant == VariantKnightmate)
13114             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13115       default:
13116         defaultlabel:
13117         if (gameMode == IcsExamining) {
13118             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13119             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13120                      PieceToChar(selection), AAA + x, ONE + y);
13121             SendToICS(buf);
13122         } else {
13123             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13124                 int n;
13125                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13126                     n = PieceToNumber(selection - BlackPawn);
13127                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13128                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13129                     boards[0][BOARD_HEIGHT-1-n][1]++;
13130                 } else
13131                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13132                     n = PieceToNumber(selection);
13133                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13134                     boards[0][n][BOARD_WIDTH-1] = selection;
13135                     boards[0][n][BOARD_WIDTH-2]++;
13136                 }
13137             } else
13138             boards[0][y][x] = selection;
13139             DrawPosition(TRUE, boards[0]);
13140         }
13141         break;
13142     }
13143 }
13144
13145
13146 void
13147 DropMenuEvent(selection, x, y)
13148      ChessSquare selection;
13149      int x, y;
13150 {
13151     ChessMove moveType;
13152
13153     switch (gameMode) {
13154       case IcsPlayingWhite:
13155       case MachinePlaysBlack:
13156         if (!WhiteOnMove(currentMove)) {
13157             DisplayMoveError(_("It is Black's turn"));
13158             return;
13159         }
13160         moveType = WhiteDrop;
13161         break;
13162       case IcsPlayingBlack:
13163       case MachinePlaysWhite:
13164         if (WhiteOnMove(currentMove)) {
13165             DisplayMoveError(_("It is White's turn"));
13166             return;
13167         }
13168         moveType = BlackDrop;
13169         break;
13170       case EditGame:
13171         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13172         break;
13173       default:
13174         return;
13175     }
13176
13177     if (moveType == BlackDrop && selection < BlackPawn) {
13178       selection = (ChessSquare) ((int) selection
13179                                  + (int) BlackPawn - (int) WhitePawn);
13180     }
13181     if (boards[currentMove][y][x] != EmptySquare) {
13182         DisplayMoveError(_("That square is occupied"));
13183         return;
13184     }
13185
13186     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13187 }
13188
13189 void
13190 AcceptEvent()
13191 {
13192     /* Accept a pending offer of any kind from opponent */
13193
13194     if (appData.icsActive) {
13195         SendToICS(ics_prefix);
13196         SendToICS("accept\n");
13197     } else if (cmailMsgLoaded) {
13198         if (currentMove == cmailOldMove &&
13199             commentList[cmailOldMove] != NULL &&
13200             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13201                    "Black offers a draw" : "White offers a draw")) {
13202             TruncateGame();
13203             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13204             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13205         } else {
13206             DisplayError(_("There is no pending offer on this move"), 0);
13207             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13208         }
13209     } else {
13210         /* Not used for offers from chess program */
13211     }
13212 }
13213
13214 void
13215 DeclineEvent()
13216 {
13217     /* Decline a pending offer of any kind from opponent */
13218
13219     if (appData.icsActive) {
13220         SendToICS(ics_prefix);
13221         SendToICS("decline\n");
13222     } else if (cmailMsgLoaded) {
13223         if (currentMove == cmailOldMove &&
13224             commentList[cmailOldMove] != NULL &&
13225             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13226                    "Black offers a draw" : "White offers a draw")) {
13227 #ifdef NOTDEF
13228             AppendComment(cmailOldMove, "Draw declined", TRUE);
13229             DisplayComment(cmailOldMove - 1, "Draw declined");
13230 #endif /*NOTDEF*/
13231         } else {
13232             DisplayError(_("There is no pending offer on this move"), 0);
13233         }
13234     } else {
13235         /* Not used for offers from chess program */
13236     }
13237 }
13238
13239 void
13240 RematchEvent()
13241 {
13242     /* Issue ICS rematch command */
13243     if (appData.icsActive) {
13244         SendToICS(ics_prefix);
13245         SendToICS("rematch\n");
13246     }
13247 }
13248
13249 void
13250 CallFlagEvent()
13251 {
13252     /* Call your opponent's flag (claim a win on time) */
13253     if (appData.icsActive) {
13254         SendToICS(ics_prefix);
13255         SendToICS("flag\n");
13256     } else {
13257         switch (gameMode) {
13258           default:
13259             return;
13260           case MachinePlaysWhite:
13261             if (whiteFlag) {
13262                 if (blackFlag)
13263                   GameEnds(GameIsDrawn, "Both players ran out of time",
13264                            GE_PLAYER);
13265                 else
13266                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13267             } else {
13268                 DisplayError(_("Your opponent is not out of time"), 0);
13269             }
13270             break;
13271           case MachinePlaysBlack:
13272             if (blackFlag) {
13273                 if (whiteFlag)
13274                   GameEnds(GameIsDrawn, "Both players ran out of time",
13275                            GE_PLAYER);
13276                 else
13277                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13278             } else {
13279                 DisplayError(_("Your opponent is not out of time"), 0);
13280             }
13281             break;
13282         }
13283     }
13284 }
13285
13286 void
13287 ClockClick(int which)
13288 {       // [HGM] code moved to back-end from winboard.c
13289         if(which) { // black clock
13290           if (gameMode == EditPosition || gameMode == IcsExamining) {
13291             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13292             SetBlackToPlayEvent();
13293           } else if (gameMode == EditGame || shiftKey) {
13294             AdjustClock(which, -1);
13295           } else if (gameMode == IcsPlayingWhite ||
13296                      gameMode == MachinePlaysBlack) {
13297             CallFlagEvent();
13298           }
13299         } else { // white clock
13300           if (gameMode == EditPosition || gameMode == IcsExamining) {
13301             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13302             SetWhiteToPlayEvent();
13303           } else if (gameMode == EditGame || shiftKey) {
13304             AdjustClock(which, -1);
13305           } else if (gameMode == IcsPlayingBlack ||
13306                    gameMode == MachinePlaysWhite) {
13307             CallFlagEvent();
13308           }
13309         }
13310 }
13311
13312 void
13313 DrawEvent()
13314 {
13315     /* Offer draw or accept pending draw offer from opponent */
13316
13317     if (appData.icsActive) {
13318         /* Note: tournament rules require draw offers to be
13319            made after you make your move but before you punch
13320            your clock.  Currently ICS doesn't let you do that;
13321            instead, you immediately punch your clock after making
13322            a move, but you can offer a draw at any time. */
13323
13324         SendToICS(ics_prefix);
13325         SendToICS("draw\n");
13326         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13327     } else if (cmailMsgLoaded) {
13328         if (currentMove == cmailOldMove &&
13329             commentList[cmailOldMove] != NULL &&
13330             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13331                    "Black offers a draw" : "White offers a draw")) {
13332             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13333             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13334         } else if (currentMove == cmailOldMove + 1) {
13335             char *offer = WhiteOnMove(cmailOldMove) ?
13336               "White offers a draw" : "Black offers a draw";
13337             AppendComment(currentMove, offer, TRUE);
13338             DisplayComment(currentMove - 1, offer);
13339             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13340         } else {
13341             DisplayError(_("You must make your move before offering a draw"), 0);
13342             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13343         }
13344     } else if (first.offeredDraw) {
13345         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13346     } else {
13347         if (first.sendDrawOffers) {
13348             SendToProgram("draw\n", &first);
13349             userOfferedDraw = TRUE;
13350         }
13351     }
13352 }
13353
13354 void
13355 AdjournEvent()
13356 {
13357     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13358
13359     if (appData.icsActive) {
13360         SendToICS(ics_prefix);
13361         SendToICS("adjourn\n");
13362     } else {
13363         /* Currently GNU Chess doesn't offer or accept Adjourns */
13364     }
13365 }
13366
13367
13368 void
13369 AbortEvent()
13370 {
13371     /* Offer Abort or accept pending Abort offer from opponent */
13372
13373     if (appData.icsActive) {
13374         SendToICS(ics_prefix);
13375         SendToICS("abort\n");
13376     } else {
13377         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13378     }
13379 }
13380
13381 void
13382 ResignEvent()
13383 {
13384     /* Resign.  You can do this even if it's not your turn. */
13385
13386     if (appData.icsActive) {
13387         SendToICS(ics_prefix);
13388         SendToICS("resign\n");
13389     } else {
13390         switch (gameMode) {
13391           case MachinePlaysWhite:
13392             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13393             break;
13394           case MachinePlaysBlack:
13395             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13396             break;
13397           case EditGame:
13398             if (cmailMsgLoaded) {
13399                 TruncateGame();
13400                 if (WhiteOnMove(cmailOldMove)) {
13401                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13402                 } else {
13403                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13404                 }
13405                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13406             }
13407             break;
13408           default:
13409             break;
13410         }
13411     }
13412 }
13413
13414
13415 void
13416 StopObservingEvent()
13417 {
13418     /* Stop observing current games */
13419     SendToICS(ics_prefix);
13420     SendToICS("unobserve\n");
13421 }
13422
13423 void
13424 StopExaminingEvent()
13425 {
13426     /* Stop observing current game */
13427     SendToICS(ics_prefix);
13428     SendToICS("unexamine\n");
13429 }
13430
13431 void
13432 ForwardInner(target)
13433      int target;
13434 {
13435     int limit;
13436
13437     if (appData.debugMode)
13438         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13439                 target, currentMove, forwardMostMove);
13440
13441     if (gameMode == EditPosition)
13442       return;
13443
13444     if (gameMode == PlayFromGameFile && !pausing)
13445       PauseEvent();
13446
13447     if (gameMode == IcsExamining && pausing)
13448       limit = pauseExamForwardMostMove;
13449     else
13450       limit = forwardMostMove;
13451
13452     if (target > limit) target = limit;
13453
13454     if (target > 0 && moveList[target - 1][0]) {
13455         int fromX, fromY, toX, toY;
13456         toX = moveList[target - 1][2] - AAA;
13457         toY = moveList[target - 1][3] - ONE;
13458         if (moveList[target - 1][1] == '@') {
13459             if (appData.highlightLastMove) {
13460                 SetHighlights(-1, -1, toX, toY);
13461             }
13462         } else {
13463             fromX = moveList[target - 1][0] - AAA;
13464             fromY = moveList[target - 1][1] - ONE;
13465             if (target == currentMove + 1) {
13466                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13467             }
13468             if (appData.highlightLastMove) {
13469                 SetHighlights(fromX, fromY, toX, toY);
13470             }
13471         }
13472     }
13473     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13474         gameMode == Training || gameMode == PlayFromGameFile ||
13475         gameMode == AnalyzeFile) {
13476         while (currentMove < target) {
13477             SendMoveToProgram(currentMove++, &first);
13478         }
13479     } else {
13480         currentMove = target;
13481     }
13482
13483     if (gameMode == EditGame || gameMode == EndOfGame) {
13484         whiteTimeRemaining = timeRemaining[0][currentMove];
13485         blackTimeRemaining = timeRemaining[1][currentMove];
13486     }
13487     DisplayBothClocks();
13488     DisplayMove(currentMove - 1);
13489     DrawPosition(FALSE, boards[currentMove]);
13490     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13491     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13492         DisplayComment(currentMove - 1, commentList[currentMove]);
13493     }
13494 }
13495
13496
13497 void
13498 ForwardEvent()
13499 {
13500     if (gameMode == IcsExamining && !pausing) {
13501         SendToICS(ics_prefix);
13502         SendToICS("forward\n");
13503     } else {
13504         ForwardInner(currentMove + 1);
13505     }
13506 }
13507
13508 void
13509 ToEndEvent()
13510 {
13511     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13512         /* to optimze, we temporarily turn off analysis mode while we feed
13513          * the remaining moves to the engine. Otherwise we get analysis output
13514          * after each move.
13515          */
13516         if (first.analysisSupport) {
13517           SendToProgram("exit\nforce\n", &first);
13518           first.analyzing = FALSE;
13519         }
13520     }
13521
13522     if (gameMode == IcsExamining && !pausing) {
13523         SendToICS(ics_prefix);
13524         SendToICS("forward 999999\n");
13525     } else {
13526         ForwardInner(forwardMostMove);
13527     }
13528
13529     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13530         /* we have fed all the moves, so reactivate analysis mode */
13531         SendToProgram("analyze\n", &first);
13532         first.analyzing = TRUE;
13533         /*first.maybeThinking = TRUE;*/
13534         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13535     }
13536 }
13537
13538 void
13539 BackwardInner(target)
13540      int target;
13541 {
13542     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13543
13544     if (appData.debugMode)
13545         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13546                 target, currentMove, forwardMostMove);
13547
13548     if (gameMode == EditPosition) return;
13549     if (currentMove <= backwardMostMove) {
13550         ClearHighlights();
13551         DrawPosition(full_redraw, boards[currentMove]);
13552         return;
13553     }
13554     if (gameMode == PlayFromGameFile && !pausing)
13555       PauseEvent();
13556
13557     if (moveList[target][0]) {
13558         int fromX, fromY, toX, toY;
13559         toX = moveList[target][2] - AAA;
13560         toY = moveList[target][3] - ONE;
13561         if (moveList[target][1] == '@') {
13562             if (appData.highlightLastMove) {
13563                 SetHighlights(-1, -1, toX, toY);
13564             }
13565         } else {
13566             fromX = moveList[target][0] - AAA;
13567             fromY = moveList[target][1] - ONE;
13568             if (target == currentMove - 1) {
13569                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13570             }
13571             if (appData.highlightLastMove) {
13572                 SetHighlights(fromX, fromY, toX, toY);
13573             }
13574         }
13575     }
13576     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13577         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13578         while (currentMove > target) {
13579             SendToProgram("undo\n", &first);
13580             currentMove--;
13581         }
13582     } else {
13583         currentMove = target;
13584     }
13585
13586     if (gameMode == EditGame || gameMode == EndOfGame) {
13587         whiteTimeRemaining = timeRemaining[0][currentMove];
13588         blackTimeRemaining = timeRemaining[1][currentMove];
13589     }
13590     DisplayBothClocks();
13591     DisplayMove(currentMove - 1);
13592     DrawPosition(full_redraw, boards[currentMove]);
13593     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13594     // [HGM] PV info: routine tests if comment empty
13595     DisplayComment(currentMove - 1, commentList[currentMove]);
13596 }
13597
13598 void
13599 BackwardEvent()
13600 {
13601     if (gameMode == IcsExamining && !pausing) {
13602         SendToICS(ics_prefix);
13603         SendToICS("backward\n");
13604     } else {
13605         BackwardInner(currentMove - 1);
13606     }
13607 }
13608
13609 void
13610 ToStartEvent()
13611 {
13612     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13613         /* to optimize, we temporarily turn off analysis mode while we undo
13614          * all the moves. Otherwise we get analysis output after each undo.
13615          */
13616         if (first.analysisSupport) {
13617           SendToProgram("exit\nforce\n", &first);
13618           first.analyzing = FALSE;
13619         }
13620     }
13621
13622     if (gameMode == IcsExamining && !pausing) {
13623         SendToICS(ics_prefix);
13624         SendToICS("backward 999999\n");
13625     } else {
13626         BackwardInner(backwardMostMove);
13627     }
13628
13629     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13630         /* we have fed all the moves, so reactivate analysis mode */
13631         SendToProgram("analyze\n", &first);
13632         first.analyzing = TRUE;
13633         /*first.maybeThinking = TRUE;*/
13634         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13635     }
13636 }
13637
13638 void
13639 ToNrEvent(int to)
13640 {
13641   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13642   if (to >= forwardMostMove) to = forwardMostMove;
13643   if (to <= backwardMostMove) to = backwardMostMove;
13644   if (to < currentMove) {
13645     BackwardInner(to);
13646   } else {
13647     ForwardInner(to);
13648   }
13649 }
13650
13651 void
13652 RevertEvent(Boolean annotate)
13653 {
13654     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13655         return;
13656     }
13657     if (gameMode != IcsExamining) {
13658         DisplayError(_("You are not examining a game"), 0);
13659         return;
13660     }
13661     if (pausing) {
13662         DisplayError(_("You can't revert while pausing"), 0);
13663         return;
13664     }
13665     SendToICS(ics_prefix);
13666     SendToICS("revert\n");
13667 }
13668
13669 void
13670 RetractMoveEvent()
13671 {
13672     switch (gameMode) {
13673       case MachinePlaysWhite:
13674       case MachinePlaysBlack:
13675         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13676             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13677             return;
13678         }
13679         if (forwardMostMove < 2) return;
13680         currentMove = forwardMostMove = forwardMostMove - 2;
13681         whiteTimeRemaining = timeRemaining[0][currentMove];
13682         blackTimeRemaining = timeRemaining[1][currentMove];
13683         DisplayBothClocks();
13684         DisplayMove(currentMove - 1);
13685         ClearHighlights();/*!! could figure this out*/
13686         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13687         SendToProgram("remove\n", &first);
13688         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13689         break;
13690
13691       case BeginningOfGame:
13692       default:
13693         break;
13694
13695       case IcsPlayingWhite:
13696       case IcsPlayingBlack:
13697         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13698             SendToICS(ics_prefix);
13699             SendToICS("takeback 2\n");
13700         } else {
13701             SendToICS(ics_prefix);
13702             SendToICS("takeback 1\n");
13703         }
13704         break;
13705     }
13706 }
13707
13708 void
13709 MoveNowEvent()
13710 {
13711     ChessProgramState *cps;
13712
13713     switch (gameMode) {
13714       case MachinePlaysWhite:
13715         if (!WhiteOnMove(forwardMostMove)) {
13716             DisplayError(_("It is your turn"), 0);
13717             return;
13718         }
13719         cps = &first;
13720         break;
13721       case MachinePlaysBlack:
13722         if (WhiteOnMove(forwardMostMove)) {
13723             DisplayError(_("It is your turn"), 0);
13724             return;
13725         }
13726         cps = &first;
13727         break;
13728       case TwoMachinesPlay:
13729         if (WhiteOnMove(forwardMostMove) ==
13730             (first.twoMachinesColor[0] == 'w')) {
13731             cps = &first;
13732         } else {
13733             cps = &second;
13734         }
13735         break;
13736       case BeginningOfGame:
13737       default:
13738         return;
13739     }
13740     SendToProgram("?\n", cps);
13741 }
13742
13743 void
13744 TruncateGameEvent()
13745 {
13746     EditGameEvent();
13747     if (gameMode != EditGame) return;
13748     TruncateGame();
13749 }
13750
13751 void
13752 TruncateGame()
13753 {
13754     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13755     if (forwardMostMove > currentMove) {
13756         if (gameInfo.resultDetails != NULL) {
13757             free(gameInfo.resultDetails);
13758             gameInfo.resultDetails = NULL;
13759             gameInfo.result = GameUnfinished;
13760         }
13761         forwardMostMove = currentMove;
13762         HistorySet(parseList, backwardMostMove, forwardMostMove,
13763                    currentMove-1);
13764     }
13765 }
13766
13767 void
13768 HintEvent()
13769 {
13770     if (appData.noChessProgram) return;
13771     switch (gameMode) {
13772       case MachinePlaysWhite:
13773         if (WhiteOnMove(forwardMostMove)) {
13774             DisplayError(_("Wait until your turn"), 0);
13775             return;
13776         }
13777         break;
13778       case BeginningOfGame:
13779       case MachinePlaysBlack:
13780         if (!WhiteOnMove(forwardMostMove)) {
13781             DisplayError(_("Wait until your turn"), 0);
13782             return;
13783         }
13784         break;
13785       default:
13786         DisplayError(_("No hint available"), 0);
13787         return;
13788     }
13789     SendToProgram("hint\n", &first);
13790     hintRequested = TRUE;
13791 }
13792
13793 void
13794 BookEvent()
13795 {
13796     if (appData.noChessProgram) return;
13797     switch (gameMode) {
13798       case MachinePlaysWhite:
13799         if (WhiteOnMove(forwardMostMove)) {
13800             DisplayError(_("Wait until your turn"), 0);
13801             return;
13802         }
13803         break;
13804       case BeginningOfGame:
13805       case MachinePlaysBlack:
13806         if (!WhiteOnMove(forwardMostMove)) {
13807             DisplayError(_("Wait until your turn"), 0);
13808             return;
13809         }
13810         break;
13811       case EditPosition:
13812         EditPositionDone(TRUE);
13813         break;
13814       case TwoMachinesPlay:
13815         return;
13816       default:
13817         break;
13818     }
13819     SendToProgram("bk\n", &first);
13820     bookOutput[0] = NULLCHAR;
13821     bookRequested = TRUE;
13822 }
13823
13824 void
13825 AboutGameEvent()
13826 {
13827     char *tags = PGNTags(&gameInfo);
13828     TagsPopUp(tags, CmailMsg());
13829     free(tags);
13830 }
13831
13832 /* end button procedures */
13833
13834 void
13835 PrintPosition(fp, move)
13836      FILE *fp;
13837      int move;
13838 {
13839     int i, j;
13840
13841     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13842         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13843             char c = PieceToChar(boards[move][i][j]);
13844             fputc(c == 'x' ? '.' : c, fp);
13845             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13846         }
13847     }
13848     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13849       fprintf(fp, "white to play\n");
13850     else
13851       fprintf(fp, "black to play\n");
13852 }
13853
13854 void
13855 PrintOpponents(fp)
13856      FILE *fp;
13857 {
13858     if (gameInfo.white != NULL) {
13859         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13860     } else {
13861         fprintf(fp, "\n");
13862     }
13863 }
13864
13865 /* Find last component of program's own name, using some heuristics */
13866 void
13867 TidyProgramName(prog, host, buf)
13868      char *prog, *host, buf[MSG_SIZ];
13869 {
13870     char *p, *q;
13871     int local = (strcmp(host, "localhost") == 0);
13872     while (!local && (p = strchr(prog, ';')) != NULL) {
13873         p++;
13874         while (*p == ' ') p++;
13875         prog = p;
13876     }
13877     if (*prog == '"' || *prog == '\'') {
13878         q = strchr(prog + 1, *prog);
13879     } else {
13880         q = strchr(prog, ' ');
13881     }
13882     if (q == NULL) q = prog + strlen(prog);
13883     p = q;
13884     while (p >= prog && *p != '/' && *p != '\\') p--;
13885     p++;
13886     if(p == prog && *p == '"') p++;
13887     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13888     memcpy(buf, p, q - p);
13889     buf[q - p] = NULLCHAR;
13890     if (!local) {
13891         strcat(buf, "@");
13892         strcat(buf, host);
13893     }
13894 }
13895
13896 char *
13897 TimeControlTagValue()
13898 {
13899     char buf[MSG_SIZ];
13900     if (!appData.clockMode) {
13901       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13902     } else if (movesPerSession > 0) {
13903       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13904     } else if (timeIncrement == 0) {
13905       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13906     } else {
13907       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13908     }
13909     return StrSave(buf);
13910 }
13911
13912 void
13913 SetGameInfo()
13914 {
13915     /* This routine is used only for certain modes */
13916     VariantClass v = gameInfo.variant;
13917     ChessMove r = GameUnfinished;
13918     char *p = NULL;
13919
13920     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13921         r = gameInfo.result;
13922         p = gameInfo.resultDetails;
13923         gameInfo.resultDetails = NULL;
13924     }
13925     ClearGameInfo(&gameInfo);
13926     gameInfo.variant = v;
13927
13928     switch (gameMode) {
13929       case MachinePlaysWhite:
13930         gameInfo.event = StrSave( appData.pgnEventHeader );
13931         gameInfo.site = StrSave(HostName());
13932         gameInfo.date = PGNDate();
13933         gameInfo.round = StrSave("-");
13934         gameInfo.white = StrSave(first.tidy);
13935         gameInfo.black = StrSave(UserName());
13936         gameInfo.timeControl = TimeControlTagValue();
13937         break;
13938
13939       case MachinePlaysBlack:
13940         gameInfo.event = StrSave( appData.pgnEventHeader );
13941         gameInfo.site = StrSave(HostName());
13942         gameInfo.date = PGNDate();
13943         gameInfo.round = StrSave("-");
13944         gameInfo.white = StrSave(UserName());
13945         gameInfo.black = StrSave(first.tidy);
13946         gameInfo.timeControl = TimeControlTagValue();
13947         break;
13948
13949       case TwoMachinesPlay:
13950         gameInfo.event = StrSave( appData.pgnEventHeader );
13951         gameInfo.site = StrSave(HostName());
13952         gameInfo.date = PGNDate();
13953         if (roundNr > 0) {
13954             char buf[MSG_SIZ];
13955             snprintf(buf, MSG_SIZ, "%d", roundNr);
13956             gameInfo.round = StrSave(buf);
13957         } else {
13958             gameInfo.round = StrSave("-");
13959         }
13960         if (first.twoMachinesColor[0] == 'w') {
13961             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13962             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13963         } else {
13964             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13965             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13966         }
13967         gameInfo.timeControl = TimeControlTagValue();
13968         break;
13969
13970       case EditGame:
13971         gameInfo.event = StrSave("Edited game");
13972         gameInfo.site = StrSave(HostName());
13973         gameInfo.date = PGNDate();
13974         gameInfo.round = StrSave("-");
13975         gameInfo.white = StrSave("-");
13976         gameInfo.black = StrSave("-");
13977         gameInfo.result = r;
13978         gameInfo.resultDetails = p;
13979         break;
13980
13981       case EditPosition:
13982         gameInfo.event = StrSave("Edited position");
13983         gameInfo.site = StrSave(HostName());
13984         gameInfo.date = PGNDate();
13985         gameInfo.round = StrSave("-");
13986         gameInfo.white = StrSave("-");
13987         gameInfo.black = StrSave("-");
13988         break;
13989
13990       case IcsPlayingWhite:
13991       case IcsPlayingBlack:
13992       case IcsObserving:
13993       case IcsExamining:
13994         break;
13995
13996       case PlayFromGameFile:
13997         gameInfo.event = StrSave("Game from non-PGN file");
13998         gameInfo.site = StrSave(HostName());
13999         gameInfo.date = PGNDate();
14000         gameInfo.round = StrSave("-");
14001         gameInfo.white = StrSave("?");
14002         gameInfo.black = StrSave("?");
14003         break;
14004
14005       default:
14006         break;
14007     }
14008 }
14009
14010 void
14011 ReplaceComment(index, text)
14012      int index;
14013      char *text;
14014 {
14015     int len;
14016     char *p;
14017     float score;
14018
14019     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14020        pvInfoList[index-1].depth == len &&
14021        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14022        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14023     while (*text == '\n') text++;
14024     len = strlen(text);
14025     while (len > 0 && text[len - 1] == '\n') len--;
14026
14027     if (commentList[index] != NULL)
14028       free(commentList[index]);
14029
14030     if (len == 0) {
14031         commentList[index] = NULL;
14032         return;
14033     }
14034   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14035       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14036       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14037     commentList[index] = (char *) malloc(len + 2);
14038     strncpy(commentList[index], text, len);
14039     commentList[index][len] = '\n';
14040     commentList[index][len + 1] = NULLCHAR;
14041   } else {
14042     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14043     char *p;
14044     commentList[index] = (char *) malloc(len + 7);
14045     safeStrCpy(commentList[index], "{\n", 3);
14046     safeStrCpy(commentList[index]+2, text, len+1);
14047     commentList[index][len+2] = NULLCHAR;
14048     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14049     strcat(commentList[index], "\n}\n");
14050   }
14051 }
14052
14053 void
14054 CrushCRs(text)
14055      char *text;
14056 {
14057   char *p = text;
14058   char *q = text;
14059   char ch;
14060
14061   do {
14062     ch = *p++;
14063     if (ch == '\r') continue;
14064     *q++ = ch;
14065   } while (ch != '\0');
14066 }
14067
14068 void
14069 AppendComment(index, text, addBraces)
14070      int index;
14071      char *text;
14072      Boolean addBraces; // [HGM] braces: tells if we should add {}
14073 {
14074     int oldlen, len;
14075     char *old;
14076
14077 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14078     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14079
14080     CrushCRs(text);
14081     while (*text == '\n') text++;
14082     len = strlen(text);
14083     while (len > 0 && text[len - 1] == '\n') len--;
14084
14085     if (len == 0) return;
14086
14087     if (commentList[index] != NULL) {
14088         old = commentList[index];
14089         oldlen = strlen(old);
14090         while(commentList[index][oldlen-1] ==  '\n')
14091           commentList[index][--oldlen] = NULLCHAR;
14092         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14093         safeStrCpy(commentList[index], old, oldlen + len + 6);
14094         free(old);
14095         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14096         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14097           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14098           while (*text == '\n') { text++; len--; }
14099           commentList[index][--oldlen] = NULLCHAR;
14100       }
14101         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14102         else          strcat(commentList[index], "\n");
14103         strcat(commentList[index], text);
14104         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14105         else          strcat(commentList[index], "\n");
14106     } else {
14107         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14108         if(addBraces)
14109           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14110         else commentList[index][0] = NULLCHAR;
14111         strcat(commentList[index], text);
14112         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14113         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14114     }
14115 }
14116
14117 static char * FindStr( char * text, char * sub_text )
14118 {
14119     char * result = strstr( text, sub_text );
14120
14121     if( result != NULL ) {
14122         result += strlen( sub_text );
14123     }
14124
14125     return result;
14126 }
14127
14128 /* [AS] Try to extract PV info from PGN comment */
14129 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14130 char *GetInfoFromComment( int index, char * text )
14131 {
14132     char * sep = text, *p;
14133
14134     if( text != NULL && index > 0 ) {
14135         int score = 0;
14136         int depth = 0;
14137         int time = -1, sec = 0, deci;
14138         char * s_eval = FindStr( text, "[%eval " );
14139         char * s_emt = FindStr( text, "[%emt " );
14140
14141         if( s_eval != NULL || s_emt != NULL ) {
14142             /* New style */
14143             char delim;
14144
14145             if( s_eval != NULL ) {
14146                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14147                     return text;
14148                 }
14149
14150                 if( delim != ']' ) {
14151                     return text;
14152                 }
14153             }
14154
14155             if( s_emt != NULL ) {
14156             }
14157                 return text;
14158         }
14159         else {
14160             /* We expect something like: [+|-]nnn.nn/dd */
14161             int score_lo = 0;
14162
14163             if(*text != '{') return text; // [HGM] braces: must be normal comment
14164
14165             sep = strchr( text, '/' );
14166             if( sep == NULL || sep < (text+4) ) {
14167                 return text;
14168             }
14169
14170             p = text;
14171             if(p[1] == '(') { // comment starts with PV
14172                p = strchr(p, ')'); // locate end of PV
14173                if(p == NULL || sep < p+5) return text;
14174                // at this point we have something like "{(.*) +0.23/6 ..."
14175                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14176                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14177                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14178             }
14179             time = -1; sec = -1; deci = -1;
14180             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14181                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14182                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14183                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14184                 return text;
14185             }
14186
14187             if( score_lo < 0 || score_lo >= 100 ) {
14188                 return text;
14189             }
14190
14191             if(sec >= 0) time = 600*time + 10*sec; else
14192             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14193
14194             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14195
14196             /* [HGM] PV time: now locate end of PV info */
14197             while( *++sep >= '0' && *sep <= '9'); // strip depth
14198             if(time >= 0)
14199             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14200             if(sec >= 0)
14201             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14202             if(deci >= 0)
14203             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14204             while(*sep == ' ') sep++;
14205         }
14206
14207         if( depth <= 0 ) {
14208             return text;
14209         }
14210
14211         if( time < 0 ) {
14212             time = -1;
14213         }
14214
14215         pvInfoList[index-1].depth = depth;
14216         pvInfoList[index-1].score = score;
14217         pvInfoList[index-1].time  = 10*time; // centi-sec
14218         if(*sep == '}') *sep = 0; else *--sep = '{';
14219         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14220     }
14221     return sep;
14222 }
14223
14224 void
14225 SendToProgram(message, cps)
14226      char *message;
14227      ChessProgramState *cps;
14228 {
14229     int count, outCount, error;
14230     char buf[MSG_SIZ];
14231
14232     if (cps->pr == NULL) return;
14233     Attention(cps);
14234
14235     if (appData.debugMode) {
14236         TimeMark now;
14237         GetTimeMark(&now);
14238         fprintf(debugFP, "%ld >%-6s: %s",
14239                 SubtractTimeMarks(&now, &programStartTime),
14240                 cps->which, message);
14241     }
14242
14243     count = strlen(message);
14244     outCount = OutputToProcess(cps->pr, message, count, &error);
14245     if (outCount < count && !exiting
14246                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14247       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14248       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14249         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14250             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14251                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14252                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14253                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14254             } else {
14255                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14256                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14257                 gameInfo.result = res;
14258             }
14259             gameInfo.resultDetails = StrSave(buf);
14260         }
14261         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14262         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14263     }
14264 }
14265
14266 void
14267 ReceiveFromProgram(isr, closure, message, count, error)
14268      InputSourceRef isr;
14269      VOIDSTAR closure;
14270      char *message;
14271      int count;
14272      int error;
14273 {
14274     char *end_str;
14275     char buf[MSG_SIZ];
14276     ChessProgramState *cps = (ChessProgramState *)closure;
14277
14278     if (isr != cps->isr) return; /* Killed intentionally */
14279     if (count <= 0) {
14280         if (count == 0) {
14281             RemoveInputSource(cps->isr);
14282             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14283                     _(cps->which), cps->program);
14284         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14285                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14286                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14287                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14288                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14289                 } else {
14290                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14291                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14292                     gameInfo.result = res;
14293                 }
14294                 gameInfo.resultDetails = StrSave(buf);
14295             }
14296             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14297             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14298         } else {
14299             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14300                     _(cps->which), cps->program);
14301             RemoveInputSource(cps->isr);
14302
14303             /* [AS] Program is misbehaving badly... kill it */
14304             if( count == -2 ) {
14305                 DestroyChildProcess( cps->pr, 9 );
14306                 cps->pr = NoProc;
14307             }
14308
14309             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14310         }
14311         return;
14312     }
14313
14314     if ((end_str = strchr(message, '\r')) != NULL)
14315       *end_str = NULLCHAR;
14316     if ((end_str = strchr(message, '\n')) != NULL)
14317       *end_str = NULLCHAR;
14318
14319     if (appData.debugMode) {
14320         TimeMark now; int print = 1;
14321         char *quote = ""; char c; int i;
14322
14323         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14324                 char start = message[0];
14325                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14326                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14327                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14328                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14329                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14330                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14331                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14332                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14333                    sscanf(message, "hint: %c", &c)!=1 && 
14334                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14335                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14336                     print = (appData.engineComments >= 2);
14337                 }
14338                 message[0] = start; // restore original message
14339         }
14340         if(print) {
14341                 GetTimeMark(&now);
14342                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14343                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14344                         quote,
14345                         message);
14346         }
14347     }
14348
14349     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14350     if (appData.icsEngineAnalyze) {
14351         if (strstr(message, "whisper") != NULL ||
14352              strstr(message, "kibitz") != NULL ||
14353             strstr(message, "tellics") != NULL) return;
14354     }
14355
14356     HandleMachineMove(message, cps);
14357 }
14358
14359
14360 void
14361 SendTimeControl(cps, mps, tc, inc, sd, st)
14362      ChessProgramState *cps;
14363      int mps, inc, sd, st;
14364      long tc;
14365 {
14366     char buf[MSG_SIZ];
14367     int seconds;
14368
14369     if( timeControl_2 > 0 ) {
14370         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14371             tc = timeControl_2;
14372         }
14373     }
14374     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14375     inc /= cps->timeOdds;
14376     st  /= cps->timeOdds;
14377
14378     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14379
14380     if (st > 0) {
14381       /* Set exact time per move, normally using st command */
14382       if (cps->stKludge) {
14383         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14384         seconds = st % 60;
14385         if (seconds == 0) {
14386           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14387         } else {
14388           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14389         }
14390       } else {
14391         snprintf(buf, MSG_SIZ, "st %d\n", st);
14392       }
14393     } else {
14394       /* Set conventional or incremental time control, using level command */
14395       if (seconds == 0) {
14396         /* Note old gnuchess bug -- minutes:seconds used to not work.
14397            Fixed in later versions, but still avoid :seconds
14398            when seconds is 0. */
14399         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14400       } else {
14401         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14402                  seconds, inc/1000.);
14403       }
14404     }
14405     SendToProgram(buf, cps);
14406
14407     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14408     /* Orthogonally, limit search to given depth */
14409     if (sd > 0) {
14410       if (cps->sdKludge) {
14411         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14412       } else {
14413         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14414       }
14415       SendToProgram(buf, cps);
14416     }
14417
14418     if(cps->nps >= 0) { /* [HGM] nps */
14419         if(cps->supportsNPS == FALSE)
14420           cps->nps = -1; // don't use if engine explicitly says not supported!
14421         else {
14422           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14423           SendToProgram(buf, cps);
14424         }
14425     }
14426 }
14427
14428 ChessProgramState *WhitePlayer()
14429 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14430 {
14431     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14432        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14433         return &second;
14434     return &first;
14435 }
14436
14437 void
14438 SendTimeRemaining(cps, machineWhite)
14439      ChessProgramState *cps;
14440      int /*boolean*/ machineWhite;
14441 {
14442     char message[MSG_SIZ];
14443     long time, otime;
14444
14445     /* Note: this routine must be called when the clocks are stopped
14446        or when they have *just* been set or switched; otherwise
14447        it will be off by the time since the current tick started.
14448     */
14449     if (machineWhite) {
14450         time = whiteTimeRemaining / 10;
14451         otime = blackTimeRemaining / 10;
14452     } else {
14453         time = blackTimeRemaining / 10;
14454         otime = whiteTimeRemaining / 10;
14455     }
14456     /* [HGM] translate opponent's time by time-odds factor */
14457     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14458     if (appData.debugMode) {
14459         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14460     }
14461
14462     if (time <= 0) time = 1;
14463     if (otime <= 0) otime = 1;
14464
14465     snprintf(message, MSG_SIZ, "time %ld\n", time);
14466     SendToProgram(message, cps);
14467
14468     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14469     SendToProgram(message, cps);
14470 }
14471
14472 int
14473 BoolFeature(p, name, loc, cps)
14474      char **p;
14475      char *name;
14476      int *loc;
14477      ChessProgramState *cps;
14478 {
14479   char buf[MSG_SIZ];
14480   int len = strlen(name);
14481   int val;
14482
14483   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14484     (*p) += len + 1;
14485     sscanf(*p, "%d", &val);
14486     *loc = (val != 0);
14487     while (**p && **p != ' ')
14488       (*p)++;
14489     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14490     SendToProgram(buf, cps);
14491     return TRUE;
14492   }
14493   return FALSE;
14494 }
14495
14496 int
14497 IntFeature(p, name, loc, cps)
14498      char **p;
14499      char *name;
14500      int *loc;
14501      ChessProgramState *cps;
14502 {
14503   char buf[MSG_SIZ];
14504   int len = strlen(name);
14505   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14506     (*p) += len + 1;
14507     sscanf(*p, "%d", loc);
14508     while (**p && **p != ' ') (*p)++;
14509     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14510     SendToProgram(buf, cps);
14511     return TRUE;
14512   }
14513   return FALSE;
14514 }
14515
14516 int
14517 StringFeature(p, name, loc, cps)
14518      char **p;
14519      char *name;
14520      char loc[];
14521      ChessProgramState *cps;
14522 {
14523   char buf[MSG_SIZ];
14524   int len = strlen(name);
14525   if (strncmp((*p), name, len) == 0
14526       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14527     (*p) += len + 2;
14528     sscanf(*p, "%[^\"]", loc);
14529     while (**p && **p != '\"') (*p)++;
14530     if (**p == '\"') (*p)++;
14531     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14532     SendToProgram(buf, cps);
14533     return TRUE;
14534   }
14535   return FALSE;
14536 }
14537
14538 int
14539 ParseOption(Option *opt, ChessProgramState *cps)
14540 // [HGM] options: process the string that defines an engine option, and determine
14541 // name, type, default value, and allowed value range
14542 {
14543         char *p, *q, buf[MSG_SIZ];
14544         int n, min = (-1)<<31, max = 1<<31, def;
14545
14546         if(p = strstr(opt->name, " -spin ")) {
14547             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14548             if(max < min) max = min; // enforce consistency
14549             if(def < min) def = min;
14550             if(def > max) def = max;
14551             opt->value = def;
14552             opt->min = min;
14553             opt->max = max;
14554             opt->type = Spin;
14555         } else if((p = strstr(opt->name, " -slider "))) {
14556             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14557             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14558             if(max < min) max = min; // enforce consistency
14559             if(def < min) def = min;
14560             if(def > max) def = max;
14561             opt->value = def;
14562             opt->min = min;
14563             opt->max = max;
14564             opt->type = Spin; // Slider;
14565         } else if((p = strstr(opt->name, " -string "))) {
14566             opt->textValue = p+9;
14567             opt->type = TextBox;
14568         } else if((p = strstr(opt->name, " -file "))) {
14569             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14570             opt->textValue = p+7;
14571             opt->type = FileName; // FileName;
14572         } else if((p = strstr(opt->name, " -path "))) {
14573             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14574             opt->textValue = p+7;
14575             opt->type = PathName; // PathName;
14576         } else if(p = strstr(opt->name, " -check ")) {
14577             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14578             opt->value = (def != 0);
14579             opt->type = CheckBox;
14580         } else if(p = strstr(opt->name, " -combo ")) {
14581             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14582             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14583             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14584             opt->value = n = 0;
14585             while(q = StrStr(q, " /// ")) {
14586                 n++; *q = 0;    // count choices, and null-terminate each of them
14587                 q += 5;
14588                 if(*q == '*') { // remember default, which is marked with * prefix
14589                     q++;
14590                     opt->value = n;
14591                 }
14592                 cps->comboList[cps->comboCnt++] = q;
14593             }
14594             cps->comboList[cps->comboCnt++] = NULL;
14595             opt->max = n + 1;
14596             opt->type = ComboBox;
14597         } else if(p = strstr(opt->name, " -button")) {
14598             opt->type = Button;
14599         } else if(p = strstr(opt->name, " -save")) {
14600             opt->type = SaveButton;
14601         } else return FALSE;
14602         *p = 0; // terminate option name
14603         // now look if the command-line options define a setting for this engine option.
14604         if(cps->optionSettings && cps->optionSettings[0])
14605             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14606         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14607           snprintf(buf, MSG_SIZ, "option %s", p);
14608                 if(p = strstr(buf, ",")) *p = 0;
14609                 if(q = strchr(buf, '=')) switch(opt->type) {
14610                     case ComboBox:
14611                         for(n=0; n<opt->max; n++)
14612                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14613                         break;
14614                     case TextBox:
14615                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14616                         break;
14617                     case Spin:
14618                     case CheckBox:
14619                         opt->value = atoi(q+1);
14620                     default:
14621                         break;
14622                 }
14623                 strcat(buf, "\n");
14624                 SendToProgram(buf, cps);
14625         }
14626         return TRUE;
14627 }
14628
14629 void
14630 FeatureDone(cps, val)
14631      ChessProgramState* cps;
14632      int val;
14633 {
14634   DelayedEventCallback cb = GetDelayedEvent();
14635   if ((cb == InitBackEnd3 && cps == &first) ||
14636       (cb == SettingsMenuIfReady && cps == &second) ||
14637       (cb == LoadEngine) ||
14638       (cb == TwoMachinesEventIfReady)) {
14639     CancelDelayedEvent();
14640     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14641   }
14642   cps->initDone = val;
14643 }
14644
14645 /* Parse feature command from engine */
14646 void
14647 ParseFeatures(args, cps)
14648      char* args;
14649      ChessProgramState *cps;
14650 {
14651   char *p = args;
14652   char *q;
14653   int val;
14654   char buf[MSG_SIZ];
14655
14656   for (;;) {
14657     while (*p == ' ') p++;
14658     if (*p == NULLCHAR) return;
14659
14660     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14661     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14662     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14663     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14664     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14665     if (BoolFeature(&p, "reuse", &val, cps)) {
14666       /* Engine can disable reuse, but can't enable it if user said no */
14667       if (!val) cps->reuse = FALSE;
14668       continue;
14669     }
14670     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14671     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14672       if (gameMode == TwoMachinesPlay) {
14673         DisplayTwoMachinesTitle();
14674       } else {
14675         DisplayTitle("");
14676       }
14677       continue;
14678     }
14679     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14680     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14681     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14682     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14683     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14684     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14685     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14686     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14687     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14688     if (IntFeature(&p, "done", &val, cps)) {
14689       FeatureDone(cps, val);
14690       continue;
14691     }
14692     /* Added by Tord: */
14693     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14694     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14695     /* End of additions by Tord */
14696
14697     /* [HGM] added features: */
14698     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14699     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14700     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14701     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14702     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14703     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14704     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14705         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14706           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14707             SendToProgram(buf, cps);
14708             continue;
14709         }
14710         if(cps->nrOptions >= MAX_OPTIONS) {
14711             cps->nrOptions--;
14712             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14713             DisplayError(buf, 0);
14714         }
14715         continue;
14716     }
14717     /* End of additions by HGM */
14718
14719     /* unknown feature: complain and skip */
14720     q = p;
14721     while (*q && *q != '=') q++;
14722     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14723     SendToProgram(buf, cps);
14724     p = q;
14725     if (*p == '=') {
14726       p++;
14727       if (*p == '\"') {
14728         p++;
14729         while (*p && *p != '\"') p++;
14730         if (*p == '\"') p++;
14731       } else {
14732         while (*p && *p != ' ') p++;
14733       }
14734     }
14735   }
14736
14737 }
14738
14739 void
14740 PeriodicUpdatesEvent(newState)
14741      int newState;
14742 {
14743     if (newState == appData.periodicUpdates)
14744       return;
14745
14746     appData.periodicUpdates=newState;
14747
14748     /* Display type changes, so update it now */
14749 //    DisplayAnalysis();
14750
14751     /* Get the ball rolling again... */
14752     if (newState) {
14753         AnalysisPeriodicEvent(1);
14754         StartAnalysisClock();
14755     }
14756 }
14757
14758 void
14759 PonderNextMoveEvent(newState)
14760      int newState;
14761 {
14762     if (newState == appData.ponderNextMove) return;
14763     if (gameMode == EditPosition) EditPositionDone(TRUE);
14764     if (newState) {
14765         SendToProgram("hard\n", &first);
14766         if (gameMode == TwoMachinesPlay) {
14767             SendToProgram("hard\n", &second);
14768         }
14769     } else {
14770         SendToProgram("easy\n", &first);
14771         thinkOutput[0] = NULLCHAR;
14772         if (gameMode == TwoMachinesPlay) {
14773             SendToProgram("easy\n", &second);
14774         }
14775     }
14776     appData.ponderNextMove = newState;
14777 }
14778
14779 void
14780 NewSettingEvent(option, feature, command, value)
14781      char *command;
14782      int option, value, *feature;
14783 {
14784     char buf[MSG_SIZ];
14785
14786     if (gameMode == EditPosition) EditPositionDone(TRUE);
14787     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14788     if(feature == NULL || *feature) SendToProgram(buf, &first);
14789     if (gameMode == TwoMachinesPlay) {
14790         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14791     }
14792 }
14793
14794 void
14795 ShowThinkingEvent()
14796 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14797 {
14798     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14799     int newState = appData.showThinking
14800         // [HGM] thinking: other features now need thinking output as well
14801         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14802
14803     if (oldState == newState) return;
14804     oldState = newState;
14805     if (gameMode == EditPosition) EditPositionDone(TRUE);
14806     if (oldState) {
14807         SendToProgram("post\n", &first);
14808         if (gameMode == TwoMachinesPlay) {
14809             SendToProgram("post\n", &second);
14810         }
14811     } else {
14812         SendToProgram("nopost\n", &first);
14813         thinkOutput[0] = NULLCHAR;
14814         if (gameMode == TwoMachinesPlay) {
14815             SendToProgram("nopost\n", &second);
14816         }
14817     }
14818 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14819 }
14820
14821 void
14822 AskQuestionEvent(title, question, replyPrefix, which)
14823      char *title; char *question; char *replyPrefix; char *which;
14824 {
14825   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14826   if (pr == NoProc) return;
14827   AskQuestion(title, question, replyPrefix, pr);
14828 }
14829
14830 void
14831 TypeInEvent(char firstChar)
14832 {
14833     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14834         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14835         gameMode == AnalyzeMode || gameMode == EditGame || \r
14836         gameMode == EditPosition || gameMode == IcsExamining ||\r
14837         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14838         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14839                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14840                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14841         gameMode == Training) PopUpMoveDialog(firstChar);
14842 }
14843
14844 void
14845 TypeInDoneEvent(char *move)
14846 {
14847         Board board;
14848         int n, fromX, fromY, toX, toY;
14849         char promoChar;
14850         ChessMove moveType;\r
14851
14852         // [HGM] FENedit\r
14853         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14854                 EditPositionPasteFEN(move);\r
14855                 return;\r
14856         }\r
14857         // [HGM] movenum: allow move number to be typed in any mode\r
14858         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14859           ToNrEvent(2*n-1);\r
14860           return;\r
14861         }\r
14862
14863       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14864         gameMode != Training) {\r
14865         DisplayMoveError(_("Displayed move is not current"));\r
14866       } else {\r
14867         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14868           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14869         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14870         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14871           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14872           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14873         } else {\r
14874           DisplayMoveError(_("Could not parse move"));\r
14875         }
14876       }\r
14877 }\r
14878
14879 void
14880 DisplayMove(moveNumber)
14881      int moveNumber;
14882 {
14883     char message[MSG_SIZ];
14884     char res[MSG_SIZ];
14885     char cpThinkOutput[MSG_SIZ];
14886
14887     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14888
14889     if (moveNumber == forwardMostMove - 1 ||
14890         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14891
14892         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14893
14894         if (strchr(cpThinkOutput, '\n')) {
14895             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14896         }
14897     } else {
14898         *cpThinkOutput = NULLCHAR;
14899     }
14900
14901     /* [AS] Hide thinking from human user */
14902     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14903         *cpThinkOutput = NULLCHAR;
14904         if( thinkOutput[0] != NULLCHAR ) {
14905             int i;
14906
14907             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14908                 cpThinkOutput[i] = '.';
14909             }
14910             cpThinkOutput[i] = NULLCHAR;
14911             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14912         }
14913     }
14914
14915     if (moveNumber == forwardMostMove - 1 &&
14916         gameInfo.resultDetails != NULL) {
14917         if (gameInfo.resultDetails[0] == NULLCHAR) {
14918           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14919         } else {
14920           snprintf(res, MSG_SIZ, " {%s} %s",
14921                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14922         }
14923     } else {
14924         res[0] = NULLCHAR;
14925     }
14926
14927     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14928         DisplayMessage(res, cpThinkOutput);
14929     } else {
14930       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14931                 WhiteOnMove(moveNumber) ? " " : ".. ",
14932                 parseList[moveNumber], res);
14933         DisplayMessage(message, cpThinkOutput);
14934     }
14935 }
14936
14937 void
14938 DisplayComment(moveNumber, text)
14939      int moveNumber;
14940      char *text;
14941 {
14942     char title[MSG_SIZ];
14943     char buf[8000]; // comment can be long!
14944     int score, depth;
14945
14946     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14947       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14948     } else {
14949       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14950               WhiteOnMove(moveNumber) ? " " : ".. ",
14951               parseList[moveNumber]);
14952     }
14953     // [HGM] PV info: display PV info together with (or as) comment
14954     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14955       if(text == NULL) text = "";
14956       score = pvInfoList[moveNumber].score;
14957       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14958               depth, (pvInfoList[moveNumber].time+50)/100, text);
14959       text = buf;
14960     }
14961     if (text != NULL && (appData.autoDisplayComment || commentUp))
14962         CommentPopUp(title, text);
14963 }
14964
14965 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14966  * might be busy thinking or pondering.  It can be omitted if your
14967  * gnuchess is configured to stop thinking immediately on any user
14968  * input.  However, that gnuchess feature depends on the FIONREAD
14969  * ioctl, which does not work properly on some flavors of Unix.
14970  */
14971 void
14972 Attention(cps)
14973      ChessProgramState *cps;
14974 {
14975 #if ATTENTION
14976     if (!cps->useSigint) return;
14977     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14978     switch (gameMode) {
14979       case MachinePlaysWhite:
14980       case MachinePlaysBlack:
14981       case TwoMachinesPlay:
14982       case IcsPlayingWhite:
14983       case IcsPlayingBlack:
14984       case AnalyzeMode:
14985       case AnalyzeFile:
14986         /* Skip if we know it isn't thinking */
14987         if (!cps->maybeThinking) return;
14988         if (appData.debugMode)
14989           fprintf(debugFP, "Interrupting %s\n", cps->which);
14990         InterruptChildProcess(cps->pr);
14991         cps->maybeThinking = FALSE;
14992         break;
14993       default:
14994         break;
14995     }
14996 #endif /*ATTENTION*/
14997 }
14998
14999 int
15000 CheckFlags()
15001 {
15002     if (whiteTimeRemaining <= 0) {
15003         if (!whiteFlag) {
15004             whiteFlag = TRUE;
15005             if (appData.icsActive) {
15006                 if (appData.autoCallFlag &&
15007                     gameMode == IcsPlayingBlack && !blackFlag) {
15008                   SendToICS(ics_prefix);
15009                   SendToICS("flag\n");
15010                 }
15011             } else {
15012                 if (blackFlag) {
15013                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15014                 } else {
15015                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15016                     if (appData.autoCallFlag) {
15017                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15018                         return TRUE;
15019                     }
15020                 }
15021             }
15022         }
15023     }
15024     if (blackTimeRemaining <= 0) {
15025         if (!blackFlag) {
15026             blackFlag = TRUE;
15027             if (appData.icsActive) {
15028                 if (appData.autoCallFlag &&
15029                     gameMode == IcsPlayingWhite && !whiteFlag) {
15030                   SendToICS(ics_prefix);
15031                   SendToICS("flag\n");
15032                 }
15033             } else {
15034                 if (whiteFlag) {
15035                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15036                 } else {
15037                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15038                     if (appData.autoCallFlag) {
15039                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15040                         return TRUE;
15041                     }
15042                 }
15043             }
15044         }
15045     }
15046     return FALSE;
15047 }
15048
15049 void
15050 CheckTimeControl()
15051 {
15052     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15053         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15054
15055     /*
15056      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15057      */
15058     if ( !WhiteOnMove(forwardMostMove) ) {
15059         /* White made time control */
15060         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15061         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15062         /* [HGM] time odds: correct new time quota for time odds! */
15063                                             / WhitePlayer()->timeOdds;
15064         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15065     } else {
15066         lastBlack -= blackTimeRemaining;
15067         /* Black made time control */
15068         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15069                                             / WhitePlayer()->other->timeOdds;
15070         lastWhite = whiteTimeRemaining;
15071     }
15072 }
15073
15074 void
15075 DisplayBothClocks()
15076 {
15077     int wom = gameMode == EditPosition ?
15078       !blackPlaysFirst : WhiteOnMove(currentMove);
15079     DisplayWhiteClock(whiteTimeRemaining, wom);
15080     DisplayBlackClock(blackTimeRemaining, !wom);
15081 }
15082
15083
15084 /* Timekeeping seems to be a portability nightmare.  I think everyone
15085    has ftime(), but I'm really not sure, so I'm including some ifdefs
15086    to use other calls if you don't.  Clocks will be less accurate if
15087    you have neither ftime nor gettimeofday.
15088 */
15089
15090 /* VS 2008 requires the #include outside of the function */
15091 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15092 #include <sys/timeb.h>
15093 #endif
15094
15095 /* Get the current time as a TimeMark */
15096 void
15097 GetTimeMark(tm)
15098      TimeMark *tm;
15099 {
15100 #if HAVE_GETTIMEOFDAY
15101
15102     struct timeval timeVal;
15103     struct timezone timeZone;
15104
15105     gettimeofday(&timeVal, &timeZone);
15106     tm->sec = (long) timeVal.tv_sec;
15107     tm->ms = (int) (timeVal.tv_usec / 1000L);
15108
15109 #else /*!HAVE_GETTIMEOFDAY*/
15110 #if HAVE_FTIME
15111
15112 // include <sys/timeb.h> / moved to just above start of function
15113     struct timeb timeB;
15114
15115     ftime(&timeB);
15116     tm->sec = (long) timeB.time;
15117     tm->ms = (int) timeB.millitm;
15118
15119 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15120     tm->sec = (long) time(NULL);
15121     tm->ms = 0;
15122 #endif
15123 #endif
15124 }
15125
15126 /* Return the difference in milliseconds between two
15127    time marks.  We assume the difference will fit in a long!
15128 */
15129 long
15130 SubtractTimeMarks(tm2, tm1)
15131      TimeMark *tm2, *tm1;
15132 {
15133     return 1000L*(tm2->sec - tm1->sec) +
15134            (long) (tm2->ms - tm1->ms);
15135 }
15136
15137
15138 /*
15139  * Code to manage the game clocks.
15140  *
15141  * In tournament play, black starts the clock and then white makes a move.
15142  * We give the human user a slight advantage if he is playing white---the
15143  * clocks don't run until he makes his first move, so it takes zero time.
15144  * Also, we don't account for network lag, so we could get out of sync
15145  * with GNU Chess's clock -- but then, referees are always right.
15146  */
15147
15148 static TimeMark tickStartTM;
15149 static long intendedTickLength;
15150
15151 long
15152 NextTickLength(timeRemaining)
15153      long timeRemaining;
15154 {
15155     long nominalTickLength, nextTickLength;
15156
15157     if (timeRemaining > 0L && timeRemaining <= 10000L)
15158       nominalTickLength = 100L;
15159     else
15160       nominalTickLength = 1000L;
15161     nextTickLength = timeRemaining % nominalTickLength;
15162     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15163
15164     return nextTickLength;
15165 }
15166
15167 /* Adjust clock one minute up or down */
15168 void
15169 AdjustClock(Boolean which, int dir)
15170 {
15171     if(which) blackTimeRemaining += 60000*dir;
15172     else      whiteTimeRemaining += 60000*dir;
15173     DisplayBothClocks();
15174 }
15175
15176 /* Stop clocks and reset to a fresh time control */
15177 void
15178 ResetClocks()
15179 {
15180     (void) StopClockTimer();
15181     if (appData.icsActive) {
15182         whiteTimeRemaining = blackTimeRemaining = 0;
15183     } else if (searchTime) {
15184         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15185         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15186     } else { /* [HGM] correct new time quote for time odds */
15187         whiteTC = blackTC = fullTimeControlString;
15188         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15189         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15190     }
15191     if (whiteFlag || blackFlag) {
15192         DisplayTitle("");
15193         whiteFlag = blackFlag = FALSE;
15194     }
15195     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15196     DisplayBothClocks();
15197 }
15198
15199 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15200
15201 /* Decrement running clock by amount of time that has passed */
15202 void
15203 DecrementClocks()
15204 {
15205     long timeRemaining;
15206     long lastTickLength, fudge;
15207     TimeMark now;
15208
15209     if (!appData.clockMode) return;
15210     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15211
15212     GetTimeMark(&now);
15213
15214     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15215
15216     /* Fudge if we woke up a little too soon */
15217     fudge = intendedTickLength - lastTickLength;
15218     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15219
15220     if (WhiteOnMove(forwardMostMove)) {
15221         if(whiteNPS >= 0) lastTickLength = 0;
15222         timeRemaining = whiteTimeRemaining -= lastTickLength;
15223         if(timeRemaining < 0 && !appData.icsActive) {
15224             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15225             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15226                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15227                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15228             }
15229         }
15230         DisplayWhiteClock(whiteTimeRemaining - fudge,
15231                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15232     } else {
15233         if(blackNPS >= 0) lastTickLength = 0;
15234         timeRemaining = blackTimeRemaining -= lastTickLength;
15235         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15236             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15237             if(suddenDeath) {
15238                 blackStartMove = forwardMostMove;
15239                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15240             }
15241         }
15242         DisplayBlackClock(blackTimeRemaining - fudge,
15243                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15244     }
15245     if (CheckFlags()) return;
15246
15247     tickStartTM = now;
15248     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15249     StartClockTimer(intendedTickLength);
15250
15251     /* if the time remaining has fallen below the alarm threshold, sound the
15252      * alarm. if the alarm has sounded and (due to a takeback or time control
15253      * with increment) the time remaining has increased to a level above the
15254      * threshold, reset the alarm so it can sound again.
15255      */
15256
15257     if (appData.icsActive && appData.icsAlarm) {
15258
15259         /* make sure we are dealing with the user's clock */
15260         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15261                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15262            )) return;
15263
15264         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15265             alarmSounded = FALSE;
15266         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15267             PlayAlarmSound();
15268             alarmSounded = TRUE;
15269         }
15270     }
15271 }
15272
15273
15274 /* A player has just moved, so stop the previously running
15275    clock and (if in clock mode) start the other one.
15276    We redisplay both clocks in case we're in ICS mode, because
15277    ICS gives us an update to both clocks after every move.
15278    Note that this routine is called *after* forwardMostMove
15279    is updated, so the last fractional tick must be subtracted
15280    from the color that is *not* on move now.
15281 */
15282 void
15283 SwitchClocks(int newMoveNr)
15284 {
15285     long lastTickLength;
15286     TimeMark now;
15287     int flagged = FALSE;
15288
15289     GetTimeMark(&now);
15290
15291     if (StopClockTimer() && appData.clockMode) {
15292         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15293         if (!WhiteOnMove(forwardMostMove)) {
15294             if(blackNPS >= 0) lastTickLength = 0;
15295             blackTimeRemaining -= lastTickLength;
15296            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15297 //         if(pvInfoList[forwardMostMove].time == -1)
15298                  pvInfoList[forwardMostMove].time =               // use GUI time
15299                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15300         } else {
15301            if(whiteNPS >= 0) lastTickLength = 0;
15302            whiteTimeRemaining -= lastTickLength;
15303            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15304 //         if(pvInfoList[forwardMostMove].time == -1)
15305                  pvInfoList[forwardMostMove].time =
15306                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15307         }
15308         flagged = CheckFlags();
15309     }
15310     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15311     CheckTimeControl();
15312
15313     if (flagged || !appData.clockMode) return;
15314
15315     switch (gameMode) {
15316       case MachinePlaysBlack:
15317       case MachinePlaysWhite:
15318       case BeginningOfGame:
15319         if (pausing) return;
15320         break;
15321
15322       case EditGame:
15323       case PlayFromGameFile:
15324       case IcsExamining:
15325         return;
15326
15327       default:
15328         break;
15329     }
15330
15331     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15332         if(WhiteOnMove(forwardMostMove))
15333              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15334         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15335     }
15336
15337     tickStartTM = now;
15338     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15339       whiteTimeRemaining : blackTimeRemaining);
15340     StartClockTimer(intendedTickLength);
15341 }
15342
15343
15344 /* Stop both clocks */
15345 void
15346 StopClocks()
15347 {
15348     long lastTickLength;
15349     TimeMark now;
15350
15351     if (!StopClockTimer()) return;
15352     if (!appData.clockMode) return;
15353
15354     GetTimeMark(&now);
15355
15356     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15357     if (WhiteOnMove(forwardMostMove)) {
15358         if(whiteNPS >= 0) lastTickLength = 0;
15359         whiteTimeRemaining -= lastTickLength;
15360         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15361     } else {
15362         if(blackNPS >= 0) lastTickLength = 0;
15363         blackTimeRemaining -= lastTickLength;
15364         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15365     }
15366     CheckFlags();
15367 }
15368
15369 /* Start clock of player on move.  Time may have been reset, so
15370    if clock is already running, stop and restart it. */
15371 void
15372 StartClocks()
15373 {
15374     (void) StopClockTimer(); /* in case it was running already */
15375     DisplayBothClocks();
15376     if (CheckFlags()) return;
15377
15378     if (!appData.clockMode) return;
15379     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15380
15381     GetTimeMark(&tickStartTM);
15382     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15383       whiteTimeRemaining : blackTimeRemaining);
15384
15385    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15386     whiteNPS = blackNPS = -1;
15387     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15388        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15389         whiteNPS = first.nps;
15390     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15391        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15392         blackNPS = first.nps;
15393     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15394         whiteNPS = second.nps;
15395     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15396         blackNPS = second.nps;
15397     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15398
15399     StartClockTimer(intendedTickLength);
15400 }
15401
15402 char *
15403 TimeString(ms)
15404      long ms;
15405 {
15406     long second, minute, hour, day;
15407     char *sign = "";
15408     static char buf[32];
15409
15410     if (ms > 0 && ms <= 9900) {
15411       /* convert milliseconds to tenths, rounding up */
15412       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15413
15414       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15415       return buf;
15416     }
15417
15418     /* convert milliseconds to seconds, rounding up */
15419     /* use floating point to avoid strangeness of integer division
15420        with negative dividends on many machines */
15421     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15422
15423     if (second < 0) {
15424         sign = "-";
15425         second = -second;
15426     }
15427
15428     day = second / (60 * 60 * 24);
15429     second = second % (60 * 60 * 24);
15430     hour = second / (60 * 60);
15431     second = second % (60 * 60);
15432     minute = second / 60;
15433     second = second % 60;
15434
15435     if (day > 0)
15436       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15437               sign, day, hour, minute, second);
15438     else if (hour > 0)
15439       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15440     else
15441       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15442
15443     return buf;
15444 }
15445
15446
15447 /*
15448  * This is necessary because some C libraries aren't ANSI C compliant yet.
15449  */
15450 char *
15451 StrStr(string, match)
15452      char *string, *match;
15453 {
15454     int i, length;
15455
15456     length = strlen(match);
15457
15458     for (i = strlen(string) - length; i >= 0; i--, string++)
15459       if (!strncmp(match, string, length))
15460         return string;
15461
15462     return NULL;
15463 }
15464
15465 char *
15466 StrCaseStr(string, match)
15467      char *string, *match;
15468 {
15469     int i, j, length;
15470
15471     length = strlen(match);
15472
15473     for (i = strlen(string) - length; i >= 0; i--, string++) {
15474         for (j = 0; j < length; j++) {
15475             if (ToLower(match[j]) != ToLower(string[j]))
15476               break;
15477         }
15478         if (j == length) return string;
15479     }
15480
15481     return NULL;
15482 }
15483
15484 #ifndef _amigados
15485 int
15486 StrCaseCmp(s1, s2)
15487      char *s1, *s2;
15488 {
15489     char c1, c2;
15490
15491     for (;;) {
15492         c1 = ToLower(*s1++);
15493         c2 = ToLower(*s2++);
15494         if (c1 > c2) return 1;
15495         if (c1 < c2) return -1;
15496         if (c1 == NULLCHAR) return 0;
15497     }
15498 }
15499
15500
15501 int
15502 ToLower(c)
15503      int c;
15504 {
15505     return isupper(c) ? tolower(c) : c;
15506 }
15507
15508
15509 int
15510 ToUpper(c)
15511      int c;
15512 {
15513     return islower(c) ? toupper(c) : c;
15514 }
15515 #endif /* !_amigados    */
15516
15517 char *
15518 StrSave(s)
15519      char *s;
15520 {
15521   char *ret;
15522
15523   if ((ret = (char *) malloc(strlen(s) + 1)))
15524     {
15525       safeStrCpy(ret, s, strlen(s)+1);
15526     }
15527   return ret;
15528 }
15529
15530 char *
15531 StrSavePtr(s, savePtr)
15532      char *s, **savePtr;
15533 {
15534     if (*savePtr) {
15535         free(*savePtr);
15536     }
15537     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15538       safeStrCpy(*savePtr, s, strlen(s)+1);
15539     }
15540     return(*savePtr);
15541 }
15542
15543 char *
15544 PGNDate()
15545 {
15546     time_t clock;
15547     struct tm *tm;
15548     char buf[MSG_SIZ];
15549
15550     clock = time((time_t *)NULL);
15551     tm = localtime(&clock);
15552     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15553             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15554     return StrSave(buf);
15555 }
15556
15557
15558 char *
15559 PositionToFEN(move, overrideCastling)
15560      int move;
15561      char *overrideCastling;
15562 {
15563     int i, j, fromX, fromY, toX, toY;
15564     int whiteToPlay;
15565     char buf[128];
15566     char *p, *q;
15567     int emptycount;
15568     ChessSquare piece;
15569
15570     whiteToPlay = (gameMode == EditPosition) ?
15571       !blackPlaysFirst : (move % 2 == 0);
15572     p = buf;
15573
15574     /* Piece placement data */
15575     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15576         emptycount = 0;
15577         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15578             if (boards[move][i][j] == EmptySquare) {
15579                 emptycount++;
15580             } else { ChessSquare piece = boards[move][i][j];
15581                 if (emptycount > 0) {
15582                     if(emptycount<10) /* [HGM] can be >= 10 */
15583                         *p++ = '0' + emptycount;
15584                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15585                     emptycount = 0;
15586                 }
15587                 if(PieceToChar(piece) == '+') {
15588                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15589                     *p++ = '+';
15590                     piece = (ChessSquare)(DEMOTED piece);
15591                 }
15592                 *p++ = PieceToChar(piece);
15593                 if(p[-1] == '~') {
15594                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15595                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15596                     *p++ = '~';
15597                 }
15598             }
15599         }
15600         if (emptycount > 0) {
15601             if(emptycount<10) /* [HGM] can be >= 10 */
15602                 *p++ = '0' + emptycount;
15603             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15604             emptycount = 0;
15605         }
15606         *p++ = '/';
15607     }
15608     *(p - 1) = ' ';
15609
15610     /* [HGM] print Crazyhouse or Shogi holdings */
15611     if( gameInfo.holdingsWidth ) {
15612         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15613         q = p;
15614         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15615             piece = boards[move][i][BOARD_WIDTH-1];
15616             if( piece != EmptySquare )
15617               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15618                   *p++ = PieceToChar(piece);
15619         }
15620         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15621             piece = boards[move][BOARD_HEIGHT-i-1][0];
15622             if( piece != EmptySquare )
15623               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15624                   *p++ = PieceToChar(piece);
15625         }
15626
15627         if( q == p ) *p++ = '-';
15628         *p++ = ']';
15629         *p++ = ' ';
15630     }
15631
15632     /* Active color */
15633     *p++ = whiteToPlay ? 'w' : 'b';
15634     *p++ = ' ';
15635
15636   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15637     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15638   } else {
15639   if(nrCastlingRights) {
15640      q = p;
15641      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15642        /* [HGM] write directly from rights */
15643            if(boards[move][CASTLING][2] != NoRights &&
15644               boards[move][CASTLING][0] != NoRights   )
15645                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15646            if(boards[move][CASTLING][2] != NoRights &&
15647               boards[move][CASTLING][1] != NoRights   )
15648                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15649            if(boards[move][CASTLING][5] != NoRights &&
15650               boards[move][CASTLING][3] != NoRights   )
15651                 *p++ = boards[move][CASTLING][3] + AAA;
15652            if(boards[move][CASTLING][5] != NoRights &&
15653               boards[move][CASTLING][4] != NoRights   )
15654                 *p++ = boards[move][CASTLING][4] + AAA;
15655      } else {
15656
15657         /* [HGM] write true castling rights */
15658         if( nrCastlingRights == 6 ) {
15659             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15660                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15661             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15662                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15663             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15664                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15665             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15666                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15667         }
15668      }
15669      if (q == p) *p++ = '-'; /* No castling rights */
15670      *p++ = ' ';
15671   }
15672
15673   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15674      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15675     /* En passant target square */
15676     if (move > backwardMostMove) {
15677         fromX = moveList[move - 1][0] - AAA;
15678         fromY = moveList[move - 1][1] - ONE;
15679         toX = moveList[move - 1][2] - AAA;
15680         toY = moveList[move - 1][3] - ONE;
15681         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15682             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15683             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15684             fromX == toX) {
15685             /* 2-square pawn move just happened */
15686             *p++ = toX + AAA;
15687             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15688         } else {
15689             *p++ = '-';
15690         }
15691     } else if(move == backwardMostMove) {
15692         // [HGM] perhaps we should always do it like this, and forget the above?
15693         if((signed char)boards[move][EP_STATUS] >= 0) {
15694             *p++ = boards[move][EP_STATUS] + AAA;
15695             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15696         } else {
15697             *p++ = '-';
15698         }
15699     } else {
15700         *p++ = '-';
15701     }
15702     *p++ = ' ';
15703   }
15704   }
15705
15706     /* [HGM] find reversible plies */
15707     {   int i = 0, j=move;
15708
15709         if (appData.debugMode) { int k;
15710             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15711             for(k=backwardMostMove; k<=forwardMostMove; k++)
15712                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15713
15714         }
15715
15716         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15717         if( j == backwardMostMove ) i += initialRulePlies;
15718         sprintf(p, "%d ", i);
15719         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15720     }
15721     /* Fullmove number */
15722     sprintf(p, "%d", (move / 2) + 1);
15723
15724     return StrSave(buf);
15725 }
15726
15727 Boolean
15728 ParseFEN(board, blackPlaysFirst, fen)
15729     Board board;
15730      int *blackPlaysFirst;
15731      char *fen;
15732 {
15733     int i, j;
15734     char *p, c;
15735     int emptycount;
15736     ChessSquare piece;
15737
15738     p = fen;
15739
15740     /* [HGM] by default clear Crazyhouse holdings, if present */
15741     if(gameInfo.holdingsWidth) {
15742        for(i=0; i<BOARD_HEIGHT; i++) {
15743            board[i][0]             = EmptySquare; /* black holdings */
15744            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15745            board[i][1]             = (ChessSquare) 0; /* black counts */
15746            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15747        }
15748     }
15749
15750     /* Piece placement data */
15751     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15752         j = 0;
15753         for (;;) {
15754             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15755                 if (*p == '/') p++;
15756                 emptycount = gameInfo.boardWidth - j;
15757                 while (emptycount--)
15758                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15759                 break;
15760 #if(BOARD_FILES >= 10)
15761             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15762                 p++; emptycount=10;
15763                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15764                 while (emptycount--)
15765                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15766 #endif
15767             } else if (isdigit(*p)) {
15768                 emptycount = *p++ - '0';
15769                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15770                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15771                 while (emptycount--)
15772                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15773             } else if (*p == '+' || isalpha(*p)) {
15774                 if (j >= gameInfo.boardWidth) return FALSE;
15775                 if(*p=='+') {
15776                     piece = CharToPiece(*++p);
15777                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15778                     piece = (ChessSquare) (PROMOTED piece ); p++;
15779                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15780                 } else piece = CharToPiece(*p++);
15781
15782                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15783                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15784                     piece = (ChessSquare) (PROMOTED piece);
15785                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15786                     p++;
15787                 }
15788                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15789             } else {
15790                 return FALSE;
15791             }
15792         }
15793     }
15794     while (*p == '/' || *p == ' ') p++;
15795
15796     /* [HGM] look for Crazyhouse holdings here */
15797     while(*p==' ') p++;
15798     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15799         if(*p == '[') p++;
15800         if(*p == '-' ) p++; /* empty holdings */ else {
15801             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15802             /* if we would allow FEN reading to set board size, we would   */
15803             /* have to add holdings and shift the board read so far here   */
15804             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15805                 p++;
15806                 if((int) piece >= (int) BlackPawn ) {
15807                     i = (int)piece - (int)BlackPawn;
15808                     i = PieceToNumber((ChessSquare)i);
15809                     if( i >= gameInfo.holdingsSize ) return FALSE;
15810                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15811                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15812                 } else {
15813                     i = (int)piece - (int)WhitePawn;
15814                     i = PieceToNumber((ChessSquare)i);
15815                     if( i >= gameInfo.holdingsSize ) return FALSE;
15816                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15817                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15818                 }
15819             }
15820         }
15821         if(*p == ']') p++;
15822     }
15823
15824     while(*p == ' ') p++;
15825
15826     /* Active color */
15827     c = *p++;
15828     if(appData.colorNickNames) {
15829       if( c == appData.colorNickNames[0] ) c = 'w'; else
15830       if( c == appData.colorNickNames[1] ) c = 'b';
15831     }
15832     switch (c) {
15833       case 'w':
15834         *blackPlaysFirst = FALSE;
15835         break;
15836       case 'b':
15837         *blackPlaysFirst = TRUE;
15838         break;
15839       default:
15840         return FALSE;
15841     }
15842
15843     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15844     /* return the extra info in global variiables             */
15845
15846     /* set defaults in case FEN is incomplete */
15847     board[EP_STATUS] = EP_UNKNOWN;
15848     for(i=0; i<nrCastlingRights; i++ ) {
15849         board[CASTLING][i] =
15850             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15851     }   /* assume possible unless obviously impossible */
15852     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15853     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15854     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15855                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15856     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15857     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15858     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15859                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15860     FENrulePlies = 0;
15861
15862     while(*p==' ') p++;
15863     if(nrCastlingRights) {
15864       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15865           /* castling indicator present, so default becomes no castlings */
15866           for(i=0; i<nrCastlingRights; i++ ) {
15867                  board[CASTLING][i] = NoRights;
15868           }
15869       }
15870       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15871              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15872              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15873              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15874         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15875
15876         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15877             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15878             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15879         }
15880         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15881             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15882         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15883                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15884         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15885                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15886         switch(c) {
15887           case'K':
15888               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15889               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15890               board[CASTLING][2] = whiteKingFile;
15891               break;
15892           case'Q':
15893               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15894               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15895               board[CASTLING][2] = whiteKingFile;
15896               break;
15897           case'k':
15898               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15899               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15900               board[CASTLING][5] = blackKingFile;
15901               break;
15902           case'q':
15903               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15904               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15905               board[CASTLING][5] = blackKingFile;
15906           case '-':
15907               break;
15908           default: /* FRC castlings */
15909               if(c >= 'a') { /* black rights */
15910                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15911                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15912                   if(i == BOARD_RGHT) break;
15913                   board[CASTLING][5] = i;
15914                   c -= AAA;
15915                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15916                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15917                   if(c > i)
15918                       board[CASTLING][3] = c;
15919                   else
15920                       board[CASTLING][4] = c;
15921               } else { /* white rights */
15922                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15923                     if(board[0][i] == WhiteKing) break;
15924                   if(i == BOARD_RGHT) break;
15925                   board[CASTLING][2] = i;
15926                   c -= AAA - 'a' + 'A';
15927                   if(board[0][c] >= WhiteKing) break;
15928                   if(c > i)
15929                       board[CASTLING][0] = c;
15930                   else
15931                       board[CASTLING][1] = c;
15932               }
15933         }
15934       }
15935       for(i=0; i<nrCastlingRights; i++)
15936         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15937     if (appData.debugMode) {
15938         fprintf(debugFP, "FEN castling rights:");
15939         for(i=0; i<nrCastlingRights; i++)
15940         fprintf(debugFP, " %d", board[CASTLING][i]);
15941         fprintf(debugFP, "\n");
15942     }
15943
15944       while(*p==' ') p++;
15945     }
15946
15947     /* read e.p. field in games that know e.p. capture */
15948     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15949        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15950       if(*p=='-') {
15951         p++; board[EP_STATUS] = EP_NONE;
15952       } else {
15953          char c = *p++ - AAA;
15954
15955          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15956          if(*p >= '0' && *p <='9') p++;
15957          board[EP_STATUS] = c;
15958       }
15959     }
15960
15961
15962     if(sscanf(p, "%d", &i) == 1) {
15963         FENrulePlies = i; /* 50-move ply counter */
15964         /* (The move number is still ignored)    */
15965     }
15966
15967     return TRUE;
15968 }
15969
15970 void
15971 EditPositionPasteFEN(char *fen)
15972 {
15973   if (fen != NULL) {
15974     Board initial_position;
15975
15976     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15977       DisplayError(_("Bad FEN position in clipboard"), 0);
15978       return ;
15979     } else {
15980       int savedBlackPlaysFirst = blackPlaysFirst;
15981       EditPositionEvent();
15982       blackPlaysFirst = savedBlackPlaysFirst;
15983       CopyBoard(boards[0], initial_position);
15984       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15985       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15986       DisplayBothClocks();
15987       DrawPosition(FALSE, boards[currentMove]);
15988     }
15989   }
15990 }
15991
15992 static char cseq[12] = "\\   ";
15993
15994 Boolean set_cont_sequence(char *new_seq)
15995 {
15996     int len;
15997     Boolean ret;
15998
15999     // handle bad attempts to set the sequence
16000         if (!new_seq)
16001                 return 0; // acceptable error - no debug
16002
16003     len = strlen(new_seq);
16004     ret = (len > 0) && (len < sizeof(cseq));
16005     if (ret)
16006       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16007     else if (appData.debugMode)
16008       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16009     return ret;
16010 }
16011
16012 /*
16013     reformat a source message so words don't cross the width boundary.  internal
16014     newlines are not removed.  returns the wrapped size (no null character unless
16015     included in source message).  If dest is NULL, only calculate the size required
16016     for the dest buffer.  lp argument indicats line position upon entry, and it's
16017     passed back upon exit.
16018 */
16019 int wrap(char *dest, char *src, int count, int width, int *lp)
16020 {
16021     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16022
16023     cseq_len = strlen(cseq);
16024     old_line = line = *lp;
16025     ansi = len = clen = 0;
16026
16027     for (i=0; i < count; i++)
16028     {
16029         if (src[i] == '\033')
16030             ansi = 1;
16031
16032         // if we hit the width, back up
16033         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16034         {
16035             // store i & len in case the word is too long
16036             old_i = i, old_len = len;
16037
16038             // find the end of the last word
16039             while (i && src[i] != ' ' && src[i] != '\n')
16040             {
16041                 i--;
16042                 len--;
16043             }
16044
16045             // word too long?  restore i & len before splitting it
16046             if ((old_i-i+clen) >= width)
16047             {
16048                 i = old_i;
16049                 len = old_len;
16050             }
16051
16052             // extra space?
16053             if (i && src[i-1] == ' ')
16054                 len--;
16055
16056             if (src[i] != ' ' && src[i] != '\n')
16057             {
16058                 i--;
16059                 if (len)
16060                     len--;
16061             }
16062
16063             // now append the newline and continuation sequence
16064             if (dest)
16065                 dest[len] = '\n';
16066             len++;
16067             if (dest)
16068                 strncpy(dest+len, cseq, cseq_len);
16069             len += cseq_len;
16070             line = cseq_len;
16071             clen = cseq_len;
16072             continue;
16073         }
16074
16075         if (dest)
16076             dest[len] = src[i];
16077         len++;
16078         if (!ansi)
16079             line++;
16080         if (src[i] == '\n')
16081             line = 0;
16082         if (src[i] == 'm')
16083             ansi = 0;
16084     }
16085     if (dest && appData.debugMode)
16086     {
16087         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16088             count, width, line, len, *lp);
16089         show_bytes(debugFP, src, count);
16090         fprintf(debugFP, "\ndest: ");
16091         show_bytes(debugFP, dest, len);
16092         fprintf(debugFP, "\n");
16093     }
16094     *lp = dest ? line : old_line;
16095
16096     return len;
16097 }
16098
16099 // [HGM] vari: routines for shelving variations
16100
16101 void
16102 PushInner(int firstMove, int lastMove)
16103 {
16104         int i, j, nrMoves = lastMove - firstMove;
16105
16106         // push current tail of game on stack
16107         savedResult[storedGames] = gameInfo.result;
16108         savedDetails[storedGames] = gameInfo.resultDetails;
16109         gameInfo.resultDetails = NULL;
16110         savedFirst[storedGames] = firstMove;
16111         savedLast [storedGames] = lastMove;
16112         savedFramePtr[storedGames] = framePtr;
16113         framePtr -= nrMoves; // reserve space for the boards
16114         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16115             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16116             for(j=0; j<MOVE_LEN; j++)
16117                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16118             for(j=0; j<2*MOVE_LEN; j++)
16119                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16120             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16121             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16122             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16123             pvInfoList[firstMove+i-1].depth = 0;
16124             commentList[framePtr+i] = commentList[firstMove+i];
16125             commentList[firstMove+i] = NULL;
16126         }
16127
16128         storedGames++;
16129         forwardMostMove = firstMove; // truncate game so we can start variation
16130 }
16131
16132 void
16133 PushTail(int firstMove, int lastMove)
16134 {
16135         if(appData.icsActive) { // only in local mode
16136                 forwardMostMove = currentMove; // mimic old ICS behavior
16137                 return;
16138         }
16139         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16140
16141         PushInner(firstMove, lastMove);
16142         if(storedGames == 1) GreyRevert(FALSE);
16143 }
16144
16145 void
16146 PopInner(Boolean annotate)
16147 {
16148         int i, j, nrMoves;
16149         char buf[8000], moveBuf[20];
16150
16151         storedGames--;
16152         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16153         nrMoves = savedLast[storedGames] - currentMove;
16154         if(annotate) {
16155                 int cnt = 10;
16156                 if(!WhiteOnMove(currentMove))
16157                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16158                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16159                 for(i=currentMove; i<forwardMostMove; i++) {
16160                         if(WhiteOnMove(i))
16161                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16162                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16163                         strcat(buf, moveBuf);
16164                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16165                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16166                 }
16167                 strcat(buf, ")");
16168         }
16169         for(i=1; i<=nrMoves; i++) { // copy last variation back
16170             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16171             for(j=0; j<MOVE_LEN; j++)
16172                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16173             for(j=0; j<2*MOVE_LEN; j++)
16174                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16175             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16176             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16177             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16178             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16179             commentList[currentMove+i] = commentList[framePtr+i];
16180             commentList[framePtr+i] = NULL;
16181         }
16182         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16183         framePtr = savedFramePtr[storedGames];
16184         gameInfo.result = savedResult[storedGames];
16185         if(gameInfo.resultDetails != NULL) {
16186             free(gameInfo.resultDetails);
16187       }
16188         gameInfo.resultDetails = savedDetails[storedGames];
16189         forwardMostMove = currentMove + nrMoves;
16190 }
16191
16192 Boolean
16193 PopTail(Boolean annotate)
16194 {
16195         if(appData.icsActive) return FALSE; // only in local mode
16196         if(!storedGames) return FALSE; // sanity
16197         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16198
16199         PopInner(annotate);
16200
16201         if(storedGames == 0) GreyRevert(TRUE);
16202         return TRUE;
16203 }
16204
16205 void
16206 CleanupTail()
16207 {       // remove all shelved variations
16208         int i;
16209         for(i=0; i<storedGames; i++) {
16210             if(savedDetails[i])
16211                 free(savedDetails[i]);
16212             savedDetails[i] = NULL;
16213         }
16214         for(i=framePtr; i<MAX_MOVES; i++) {
16215                 if(commentList[i]) free(commentList[i]);
16216                 commentList[i] = NULL;
16217         }
16218         framePtr = MAX_MOVES-1;
16219         storedGames = 0;
16220 }
16221
16222 void
16223 LoadVariation(int index, char *text)
16224 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16225         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16226         int level = 0, move;
16227
16228         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16229         // first find outermost bracketing variation
16230         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16231             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16232                 if(*p == '{') wait = '}'; else
16233                 if(*p == '[') wait = ']'; else
16234                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16235                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16236             }
16237             if(*p == wait) wait = NULLCHAR; // closing ]} found
16238             p++;
16239         }
16240         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16241         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16242         end[1] = NULLCHAR; // clip off comment beyond variation
16243         ToNrEvent(currentMove-1);
16244         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16245         // kludge: use ParsePV() to append variation to game
16246         move = currentMove;
16247         ParsePV(start, TRUE);
16248         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16249         ClearPremoveHighlights();
16250         CommentPopDown();
16251         ToNrEvent(currentMove+1);
16252 }
16253