Fix error message on engine load
[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
253 extern int tinyLayout, smallLayout;
254 ChessProgramStats programStats;
255 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
256 int endPV = -1;
257 static int exiting = 0; /* [HGM] moved to top */
258 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
259 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
260 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
261 int partnerHighlight[2];
262 Boolean partnerBoardValid = 0;
263 char partnerStatus[MSG_SIZ];
264 Boolean partnerUp;
265 Boolean originalFlip;
266 Boolean twoBoards = 0;
267 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
268 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
269 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
270 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
271 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
272 int opponentKibitzes;
273 int lastSavedGame; /* [HGM] save: ID of game */
274 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
275 extern int chatCount;
276 int chattingPartner;
277 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
278 char lastMsg[MSG_SIZ];
279 ChessSquare pieceSweep = EmptySquare;
280 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
281 int promoDefaultAltered;
282
283 /* States for ics_getting_history */
284 #define H_FALSE 0
285 #define H_REQUESTED 1
286 #define H_GOT_REQ_HEADER 2
287 #define H_GOT_UNREQ_HEADER 3
288 #define H_GETTING_MOVES 4
289 #define H_GOT_UNWANTED_HEADER 5
290
291 /* whosays values for GameEnds */
292 #define GE_ICS 0
293 #define GE_ENGINE 1
294 #define GE_PLAYER 2
295 #define GE_FILE 3
296 #define GE_XBOARD 4
297 #define GE_ENGINE1 5
298 #define GE_ENGINE2 6
299
300 /* Maximum number of games in a cmail message */
301 #define CMAIL_MAX_GAMES 20
302
303 /* Different types of move when calling RegisterMove */
304 #define CMAIL_MOVE   0
305 #define CMAIL_RESIGN 1
306 #define CMAIL_DRAW   2
307 #define CMAIL_ACCEPT 3
308
309 /* Different types of result to remember for each game */
310 #define CMAIL_NOT_RESULT 0
311 #define CMAIL_OLD_RESULT 1
312 #define CMAIL_NEW_RESULT 2
313
314 /* Telnet protocol constants */
315 #define TN_WILL 0373
316 #define TN_WONT 0374
317 #define TN_DO   0375
318 #define TN_DONT 0376
319 #define TN_IAC  0377
320 #define TN_ECHO 0001
321 #define TN_SGA  0003
322 #define TN_PORT 23
323
324 char*
325 safeStrCpy( char *dst, const char *src, size_t count )
326 { // [HGM] made safe
327   int i;
328   assert( dst != NULL );
329   assert( src != NULL );
330   assert( count > 0 );
331
332   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
333   if(  i == count && dst[count-1] != NULLCHAR)
334     {
335       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
336       if(appData.debugMode)
337       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
338     }
339
340   return dst;
341 }
342
343 /* Some compiler can't cast u64 to double
344  * This function do the job for us:
345
346  * We use the highest bit for cast, this only
347  * works if the highest bit is not
348  * in use (This should not happen)
349  *
350  * We used this for all compiler
351  */
352 double
353 u64ToDouble(u64 value)
354 {
355   double r;
356   u64 tmp = value & u64Const(0x7fffffffffffffff);
357   r = (double)(s64)tmp;
358   if (value & u64Const(0x8000000000000000))
359        r +=  9.2233720368547758080e18; /* 2^63 */
360  return r;
361 }
362
363 /* Fake up flags for now, as we aren't keeping track of castling
364    availability yet. [HGM] Change of logic: the flag now only
365    indicates the type of castlings allowed by the rule of the game.
366    The actual rights themselves are maintained in the array
367    castlingRights, as part of the game history, and are not probed
368    by this function.
369  */
370 int
371 PosFlags(index)
372 {
373   int flags = F_ALL_CASTLE_OK;
374   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
375   switch (gameInfo.variant) {
376   case VariantSuicide:
377     flags &= ~F_ALL_CASTLE_OK;
378   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
379     flags |= F_IGNORE_CHECK;
380   case VariantLosers:
381     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
382     break;
383   case VariantAtomic:
384     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
385     break;
386   case VariantKriegspiel:
387     flags |= F_KRIEGSPIEL_CAPTURE;
388     break;
389   case VariantCapaRandom:
390   case VariantFischeRandom:
391     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
392   case VariantNoCastle:
393   case VariantShatranj:
394   case VariantCourier:
395   case VariantMakruk:
396     flags &= ~F_ALL_CASTLE_OK;
397     break;
398   default:
399     break;
400   }
401   return flags;
402 }
403
404 FILE *gameFileFP, *debugFP;
405
406 /*
407     [AS] Note: sometimes, the sscanf() function is used to parse the input
408     into a fixed-size buffer. Because of this, we must be prepared to
409     receive strings as long as the size of the input buffer, which is currently
410     set to 4K for Windows and 8K for the rest.
411     So, we must either allocate sufficiently large buffers here, or
412     reduce the size of the input buffer in the input reading part.
413 */
414
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
418
419 ChessProgramState first, second;
420
421 /* premove variables */
422 int premoveToX = 0;
423 int premoveToY = 0;
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
427 int gotPremove = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
430
431 char *ics_prefix = "$";
432 int ics_type = ICS_GENERIC;
433
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey; // [HGM] set by mouse handler
461
462 int have_sent_ICS_logon = 0;
463 int movesPerSession;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 int shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void CleanupTail P((void));
510
511 ChessSquare  FIDEArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515         BlackKing, BlackBishop, BlackKnight, BlackRook }
516 };
517
518 ChessSquare twoKingsArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522         BlackKing, BlackKing, BlackKnight, BlackRook }
523 };
524
525 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
527         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
528     { BlackRook, BlackMan, BlackBishop, BlackQueen,
529         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
530 };
531
532 ChessSquare SpartanArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
536         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
537 };
538
539 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
543         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
544 };
545
546 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
547     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
548         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
549     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
550         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
551 };
552
553 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
555         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
556     { BlackRook, BlackKnight, BlackMan, BlackFerz,
557         BlackKing, BlackMan, BlackKnight, BlackRook }
558 };
559
560
561 #if (BOARD_FILES>=10)
562 ChessSquare ShogiArray[2][BOARD_FILES] = {
563     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
564         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
565     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
566         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
567 };
568
569 ChessSquare XiangqiArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
571         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
573         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 };
575
576 ChessSquare CapablancaArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
578         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
580         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
581 };
582
583 ChessSquare GreatArray[2][BOARD_FILES] = {
584     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
585         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
586     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
587         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
588 };
589
590 ChessSquare JanusArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
592         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
593     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
594         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
595 };
596
597 #ifdef GOTHIC
598 ChessSquare GothicArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
600         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
602         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
603 };
604 #else // !GOTHIC
605 #define GothicArray CapablancaArray
606 #endif // !GOTHIC
607
608 #ifdef FALCON
609 ChessSquare FalconArray[2][BOARD_FILES] = {
610     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
611         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
612     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
613         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
614 };
615 #else // !FALCON
616 #define FalconArray CapablancaArray
617 #endif // !FALCON
618
619 #else // !(BOARD_FILES>=10)
620 #define XiangqiPosition FIDEArray
621 #define CapablancaArray FIDEArray
622 #define GothicArray FIDEArray
623 #define GreatArray FIDEArray
624 #endif // !(BOARD_FILES>=10)
625
626 #if (BOARD_FILES>=12)
627 ChessSquare CourierArray[2][BOARD_FILES] = {
628     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
629         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
630     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
631         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
632 };
633 #else // !(BOARD_FILES>=12)
634 #define CourierArray CapablancaArray
635 #endif // !(BOARD_FILES>=12)
636
637
638 Board initialPosition;
639
640
641 /* Convert str to a rating. Checks for special cases of "----",
642
643    "++++", etc. Also strips ()'s */
644 int
645 string_to_rating(str)
646   char *str;
647 {
648   while(*str && !isdigit(*str)) ++str;
649   if (!*str)
650     return 0;   /* One of the special "no rating" cases */
651   else
652     return atoi(str);
653 }
654
655 void
656 ClearProgramStats()
657 {
658     /* Init programStats */
659     programStats.movelist[0] = 0;
660     programStats.depth = 0;
661     programStats.nr_moves = 0;
662     programStats.moves_left = 0;
663     programStats.nodes = 0;
664     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
665     programStats.score = 0;
666     programStats.got_only_move = 0;
667     programStats.got_fail = 0;
668     programStats.line_is_book = 0;
669 }
670
671 void
672 CommonEngineInit()
673 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674     if (appData.firstPlaysBlack) {
675         first.twoMachinesColor = "black\n";
676         second.twoMachinesColor = "white\n";
677     } else {
678         first.twoMachinesColor = "white\n";
679         second.twoMachinesColor = "black\n";
680     }
681
682     first.other = &second;
683     second.other = &first;
684
685     { float norm = 1;
686         if(appData.timeOddsMode) {
687             norm = appData.timeOdds[0];
688             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
689         }
690         first.timeOdds  = appData.timeOdds[0]/norm;
691         second.timeOdds = appData.timeOdds[1]/norm;
692     }
693
694     if(programVersion) free(programVersion);
695     if (appData.noChessProgram) {
696         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697         sprintf(programVersion, "%s", PACKAGE_STRING);
698     } else {
699       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
702     }
703 }
704
705 void
706 UnloadEngine(ChessProgramState *cps)
707 {
708         /* Kill off first chess program */
709         if (cps->isr != NULL)
710           RemoveInputSource(cps->isr);
711         cps->isr = NULL;
712
713         if (cps->pr != NoProc) {
714             ExitAnalyzeMode();
715             DoSleep( appData.delayBeforeQuit );
716             SendToProgram("quit\n", cps);
717             DoSleep( appData.delayAfterQuit );
718             DestroyChildProcess(cps->pr, cps->useSigterm);
719         }
720         cps->pr = NoProc;
721         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
722 }
723
724 void
725 ClearOptions(ChessProgramState *cps)
726 {
727     int i;
728     cps->nrOptions = cps->comboCnt = 0;
729     for(i=0; i<MAX_OPTIONS; i++) {
730         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731         cps->option[i].textValue = 0;
732     }
733 }
734
735 char *engineNames[] = {
736 "first",
737 "second"
738 };
739
740 void
741 InitEngine(ChessProgramState *cps, int n)
742 {   // [HGM] all engine initialiation put in a function that does one engine
743
744     ClearOptions(cps);
745
746     cps->which = engineNames[n];
747     cps->maybeThinking = FALSE;
748     cps->pr = NoProc;
749     cps->isr = NULL;
750     cps->sendTime = 2;
751     cps->sendDrawOffers = 1;
752
753     cps->program = appData.chessProgram[n];
754     cps->host = appData.host[n];
755     cps->dir = appData.directory[n];
756     cps->initString = appData.engInitString[n];
757     cps->computerString = appData.computerString[n];
758     cps->useSigint  = TRUE;
759     cps->useSigterm = TRUE;
760     cps->reuse = appData.reuse[n];
761     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
762     cps->useSetboard = FALSE;
763     cps->useSAN = FALSE;
764     cps->usePing = FALSE;
765     cps->lastPing = 0;
766     cps->lastPong = 0;
767     cps->usePlayother = FALSE;
768     cps->useColors = TRUE;
769     cps->useUsermove = FALSE;
770     cps->sendICS = FALSE;
771     cps->sendName = appData.icsActive;
772     cps->sdKludge = FALSE;
773     cps->stKludge = FALSE;
774     TidyProgramName(cps->program, cps->host, cps->tidy);
775     cps->matchWins = 0;
776     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777     cps->analysisSupport = 2; /* detect */
778     cps->analyzing = FALSE;
779     cps->initDone = FALSE;
780
781     /* New features added by Tord: */
782     cps->useFEN960 = FALSE;
783     cps->useOOCastle = TRUE;
784     /* End of new features added by Tord. */
785     cps->fenOverride  = appData.fenOverride[n];
786
787     /* [HGM] time odds: set factor for each machine */
788     cps->timeOdds  = appData.timeOdds[n];
789
790     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791     cps->accumulateTC = appData.accumulateTC[n];
792     cps->maxNrOfSessions = 1;
793
794     /* [HGM] debug */
795     cps->debug = FALSE;
796     cps->supportsNPS = UNKNOWN;
797
798     /* [HGM] options */
799     cps->optionSettings  = appData.engOptions[n];
800
801     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
802     cps->isUCI = appData.isUCI[n]; /* [AS] */
803     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
804
805     if (appData.protocolVersion[n] > PROTOVER
806         || appData.protocolVersion[n] < 1)
807       {
808         char buf[MSG_SIZ];
809         int len;
810
811         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
812                        appData.protocolVersion[n]);
813         if( (len > MSG_SIZ) && appData.debugMode )
814           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
815
816         DisplayFatalError(buf, 0, 2);
817       }
818     else
819       {
820         cps->protocolVersion = appData.protocolVersion[n];
821       }
822
823     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
824 }
825
826 ChessProgramState *savCps;
827
828 void
829 LoadEngine()
830 {
831     int i;
832     if(WaitForEngine(savCps, LoadEngine)) return;
833     CommonEngineInit(); // recalculate time odds
834     if(gameInfo.variant != StringToVariant(appData.variant)) {
835         // we changed variant when loading the engine; this forces us to reset
836         Reset(TRUE, savCps != &first);
837         EditGameEvent(); // for consistency with other path, as Reset changes mode
838     }
839     InitChessProgram(savCps, FALSE);
840     SendToProgram("force\n", savCps);
841     DisplayMessage("", "");
842     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
843     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
844     ThawUI();
845     SetGNUMode();
846 }
847
848 void
849 ReplaceEngine(ChessProgramState *cps, int n)
850 {
851     EditGameEvent();
852     UnloadEngine(cps);
853     appData.noChessProgram = FALSE;
854     appData.clockMode = TRUE;
855     InitEngine(cps, n);
856     if(n) return; // only startup first engine immediately; second can wait
857     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
858     LoadEngine();
859 }
860
861 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
862 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
863
864 void
865 Load(ChessProgramState *cps, int i)
866 {
867     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
868     if(engineLine[0]) { // an engine was selected from the combo box
869         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
870         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
871         ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
872         ParseArgsFromString(buf);
873         SwapEngines(i);
874         ReplaceEngine(cps, i);
875         return;
876     }
877     p = engineName;
878     while(q = strchr(p, SLASH)) p = q+1;
879     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
880     if(engineDir[0] != NULLCHAR)
881         appData.directory[i] = engineDir;
882     else if(p != engineName) { // derive directory from engine path, when not given
883         p[-1] = 0;
884         appData.directory[i] = strdup(engineName);
885         p[-1] = SLASH;
886     } else appData.directory[i] = ".";
887     if(params[0]) {
888         snprintf(command, MSG_SIZ, "%s %s", p, params);
889         p = command;
890     }
891     appData.chessProgram[i] = strdup(p);
892     appData.isUCI[i] = isUCI;
893     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
894     appData.hasOwnBookUCI[i] = hasBook;
895     if(addToList) {
896         int len;
897         q = firstChessProgramNames;
898         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
899         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i], 
900                         v1 ? " -firstProtocolVersion 1" : "",
901                         hasBook ? "" : " -fNoOwnBookUCI",
902                         isUCI ? " -fUCI" : "",
903                         storeVariant ? " -variant " : "",
904                         storeVariant ? VariantName(gameInfo.variant) : "");
905         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
906         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
907         if(q)   free(q);
908     }
909     ReplaceEngine(cps, i);
910 }
911
912 void
913 InitBackEnd1()
914 {
915     int matched, min, sec;
916
917     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
918     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
919
920     GetTimeMark(&programStartTime);
921     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
922     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
923
924     ClearProgramStats();
925     programStats.ok_to_send = 1;
926     programStats.seen_stat = 0;
927
928     /*
929      * Initialize game list
930      */
931     ListNew(&gameList);
932
933
934     /*
935      * Internet chess server status
936      */
937     if (appData.icsActive) {
938         appData.matchMode = FALSE;
939         appData.matchGames = 0;
940 #if ZIPPY
941         appData.noChessProgram = !appData.zippyPlay;
942 #else
943         appData.zippyPlay = FALSE;
944         appData.zippyTalk = FALSE;
945         appData.noChessProgram = TRUE;
946 #endif
947         if (*appData.icsHelper != NULLCHAR) {
948             appData.useTelnet = TRUE;
949             appData.telnetProgram = appData.icsHelper;
950         }
951     } else {
952         appData.zippyTalk = appData.zippyPlay = FALSE;
953     }
954
955     /* [AS] Initialize pv info list [HGM] and game state */
956     {
957         int i, j;
958
959         for( i=0; i<=framePtr; i++ ) {
960             pvInfoList[i].depth = -1;
961             boards[i][EP_STATUS] = EP_NONE;
962             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
963         }
964     }
965
966     /*
967      * Parse timeControl resource
968      */
969     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
970                           appData.movesPerSession)) {
971         char buf[MSG_SIZ];
972         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
973         DisplayFatalError(buf, 0, 2);
974     }
975
976     /*
977      * Parse searchTime resource
978      */
979     if (*appData.searchTime != NULLCHAR) {
980         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
981         if (matched == 1) {
982             searchTime = min * 60;
983         } else if (matched == 2) {
984             searchTime = min * 60 + sec;
985         } else {
986             char buf[MSG_SIZ];
987             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
988             DisplayFatalError(buf, 0, 2);
989         }
990     }
991
992     /* [AS] Adjudication threshold */
993     adjudicateLossThreshold = appData.adjudicateLossThreshold;
994
995     InitEngine(&first, 0);
996     InitEngine(&second, 1);
997     CommonEngineInit();
998
999     if (appData.icsActive) {
1000         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1001     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1002         appData.clockMode = FALSE;
1003         first.sendTime = second.sendTime = 0;
1004     }
1005
1006 #if ZIPPY
1007     /* Override some settings from environment variables, for backward
1008        compatibility.  Unfortunately it's not feasible to have the env
1009        vars just set defaults, at least in xboard.  Ugh.
1010     */
1011     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1012       ZippyInit();
1013     }
1014 #endif
1015
1016     if (!appData.icsActive) {
1017       char buf[MSG_SIZ];
1018       int len;
1019
1020       /* Check for variants that are supported only in ICS mode,
1021          or not at all.  Some that are accepted here nevertheless
1022          have bugs; see comments below.
1023       */
1024       VariantClass variant = StringToVariant(appData.variant);
1025       switch (variant) {
1026       case VariantBughouse:     /* need four players and two boards */
1027       case VariantKriegspiel:   /* need to hide pieces and move details */
1028         /* case VariantFischeRandom: (Fabien: moved below) */
1029         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1030         if( (len > MSG_SIZ) && appData.debugMode )
1031           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1032
1033         DisplayFatalError(buf, 0, 2);
1034         return;
1035
1036       case VariantUnknown:
1037       case VariantLoadable:
1038       case Variant29:
1039       case Variant30:
1040       case Variant31:
1041       case Variant32:
1042       case Variant33:
1043       case Variant34:
1044       case Variant35:
1045       case Variant36:
1046       default:
1047         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), 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 VariantXiangqi:    /* [HGM] repetition rules not implemented */
1055       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1056       case VariantGothic:     /* [HGM] should work */
1057       case VariantCapablanca: /* [HGM] should work */
1058       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1059       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1060       case VariantKnightmate: /* [HGM] should work */
1061       case VariantCylinder:   /* [HGM] untested */
1062       case VariantFalcon:     /* [HGM] untested */
1063       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1064                                  offboard interposition not understood */
1065       case VariantNormal:     /* definitely works! */
1066       case VariantWildCastle: /* pieces not automatically shuffled */
1067       case VariantNoCastle:   /* pieces not automatically shuffled */
1068       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1069       case VariantLosers:     /* should work except for win condition,
1070                                  and doesn't know captures are mandatory */
1071       case VariantSuicide:    /* should work except for win condition,
1072                                  and doesn't know captures are mandatory */
1073       case VariantGiveaway:   /* should work except for win condition,
1074                                  and doesn't know captures are mandatory */
1075       case VariantTwoKings:   /* should work */
1076       case VariantAtomic:     /* should work except for win condition */
1077       case Variant3Check:     /* should work except for win condition */
1078       case VariantShatranj:   /* should work except for all win conditions */
1079       case VariantMakruk:     /* should work except for daw countdown */
1080       case VariantBerolina:   /* might work if TestLegality is off */
1081       case VariantCapaRandom: /* should work */
1082       case VariantJanus:      /* should work */
1083       case VariantSuper:      /* experimental */
1084       case VariantGreat:      /* experimental, requires legality testing to be off */
1085       case VariantSChess:     /* S-Chess, should work */
1086       case VariantSpartan:    /* should work */
1087         break;
1088       }
1089     }
1090
1091 }
1092
1093 int NextIntegerFromString( char ** str, long * value )
1094 {
1095     int result = -1;
1096     char * s = *str;
1097
1098     while( *s == ' ' || *s == '\t' ) {
1099         s++;
1100     }
1101
1102     *value = 0;
1103
1104     if( *s >= '0' && *s <= '9' ) {
1105         while( *s >= '0' && *s <= '9' ) {
1106             *value = *value * 10 + (*s - '0');
1107             s++;
1108         }
1109
1110         result = 0;
1111     }
1112
1113     *str = s;
1114
1115     return result;
1116 }
1117
1118 int NextTimeControlFromString( char ** str, long * value )
1119 {
1120     long temp;
1121     int result = NextIntegerFromString( str, &temp );
1122
1123     if( result == 0 ) {
1124         *value = temp * 60; /* Minutes */
1125         if( **str == ':' ) {
1126             (*str)++;
1127             result = NextIntegerFromString( str, &temp );
1128             *value += temp; /* Seconds */
1129         }
1130     }
1131
1132     return result;
1133 }
1134
1135 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1136 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1137     int result = -1, type = 0; long temp, temp2;
1138
1139     if(**str != ':') return -1; // old params remain in force!
1140     (*str)++;
1141     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1142     if( NextIntegerFromString( str, &temp ) ) return -1;
1143     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1144
1145     if(**str != '/') {
1146         /* time only: incremental or sudden-death time control */
1147         if(**str == '+') { /* increment follows; read it */
1148             (*str)++;
1149             if(**str == '!') type = *(*str)++; // Bronstein TC
1150             if(result = NextIntegerFromString( str, &temp2)) return -1;
1151             *inc = temp2 * 1000;
1152             if(**str == '.') { // read fraction of increment
1153                 char *start = ++(*str);
1154                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1155                 temp2 *= 1000;
1156                 while(start++ < *str) temp2 /= 10;
1157                 *inc += temp2;
1158             }
1159         } else *inc = 0;
1160         *moves = 0; *tc = temp * 1000; *incType = type;
1161         return 0;
1162     }
1163
1164     (*str)++; /* classical time control */
1165     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1166
1167     if(result == 0) {
1168         *moves = temp;
1169         *tc    = temp2 * 1000;
1170         *inc   = 0;
1171         *incType = type;
1172     }
1173     return result;
1174 }
1175
1176 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1177 {   /* [HGM] get time to add from the multi-session time-control string */
1178     int incType, moves=1; /* kludge to force reading of first session */
1179     long time, increment;
1180     char *s = tcString;
1181
1182     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1183     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1184     do {
1185         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1186         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1187         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1188         if(movenr == -1) return time;    /* last move before new session     */
1189         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1190         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1191         if(!moves) return increment;     /* current session is incremental   */
1192         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1193     } while(movenr >= -1);               /* try again for next session       */
1194
1195     return 0; // no new time quota on this move
1196 }
1197
1198 int
1199 ParseTimeControl(tc, ti, mps)
1200      char *tc;
1201      float ti;
1202      int mps;
1203 {
1204   long tc1;
1205   long tc2;
1206   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1207   int min, sec=0;
1208
1209   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1210   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1211       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1212   if(ti > 0) {
1213
1214     if(mps)
1215       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1216     else 
1217       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1218   } else {
1219     if(mps)
1220       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1221     else 
1222       snprintf(buf, MSG_SIZ, ":%s", mytc);
1223   }
1224   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1225   
1226   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1227     return FALSE;
1228   }
1229
1230   if( *tc == '/' ) {
1231     /* Parse second time control */
1232     tc++;
1233
1234     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1235       return FALSE;
1236     }
1237
1238     if( tc2 == 0 ) {
1239       return FALSE;
1240     }
1241
1242     timeControl_2 = tc2 * 1000;
1243   }
1244   else {
1245     timeControl_2 = 0;
1246   }
1247
1248   if( tc1 == 0 ) {
1249     return FALSE;
1250   }
1251
1252   timeControl = tc1 * 1000;
1253
1254   if (ti >= 0) {
1255     timeIncrement = ti * 1000;  /* convert to ms */
1256     movesPerSession = 0;
1257   } else {
1258     timeIncrement = 0;
1259     movesPerSession = mps;
1260   }
1261   return TRUE;
1262 }
1263
1264 void
1265 InitBackEnd2()
1266 {
1267     if (appData.debugMode) {
1268         fprintf(debugFP, "%s\n", programVersion);
1269     }
1270
1271     set_cont_sequence(appData.wrapContSeq);
1272     if (appData.matchGames > 0) {
1273         appData.matchMode = TRUE;
1274     } else if (appData.matchMode) {
1275         appData.matchGames = 1;
1276     }
1277     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1278         appData.matchGames = appData.sameColorGames;
1279     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1280         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1281         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1282     }
1283     Reset(TRUE, FALSE);
1284     if (appData.noChessProgram || first.protocolVersion == 1) {
1285       InitBackEnd3();
1286     } else {
1287       /* kludge: allow timeout for initial "feature" commands */
1288       FreezeUI();
1289       DisplayMessage("", _("Starting chess program"));
1290       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1291     }
1292 }
1293
1294 int
1295 CalculateIndex(int index, int gameNr)
1296 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1297     int res;
1298     if(index > 0) return index; // fixed nmber
1299     if(index == 0) return 1;
1300     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1301     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1302     return res;
1303 }
1304
1305 int
1306 LoadGameOrPosition(int gameNr)
1307 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1308     if (*appData.loadGameFile != NULLCHAR) {
1309         if (!LoadGameFromFile(appData.loadGameFile,
1310                 CalculateIndex(appData.loadGameIndex, gameNr),
1311                               appData.loadGameFile, FALSE)) {
1312             DisplayFatalError(_("Bad game file"), 0, 1);
1313             return 0;
1314         }
1315     } else if (*appData.loadPositionFile != NULLCHAR) {
1316         if (!LoadPositionFromFile(appData.loadPositionFile,
1317                 CalculateIndex(appData.loadPositionIndex, gameNr),
1318                                   appData.loadPositionFile)) {
1319             DisplayFatalError(_("Bad position file"), 0, 1);
1320             return 0;
1321         }
1322     }
1323     return 1;
1324 }
1325
1326 void
1327 ReserveGame(int gameNr, char resChar)
1328 {
1329     FILE *tf = fopen(appData.tourneyFile, "r+");
1330     char *p, *q, c, buf[MSG_SIZ];
1331     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1332     safeStrCpy(buf, lastMsg, MSG_SIZ);
1333     DisplayMessage(_("Pick new game"), "");
1334     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1335     ParseArgsFromFile(tf);
1336     p = q = appData.results;
1337     if(appData.debugMode) {
1338       char *r = appData.participants;
1339       fprintf(debugFP, "results = '%s'\n", p);
1340       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1341       fprintf(debugFP, "\n");
1342     }
1343     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1344     nextGame = q - p;
1345     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1346     safeStrCpy(q, p, strlen(p) + 2);
1347     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1348     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1349     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1350         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1351         q[nextGame] = '*';
1352     }
1353     fseek(tf, -(strlen(p)+4), SEEK_END);
1354     c = fgetc(tf);
1355     if(c != '"') // depending on DOS or Unix line endings we can be one off
1356          fseek(tf, -(strlen(p)+2), SEEK_END);
1357     else fseek(tf, -(strlen(p)+3), SEEK_END);
1358     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1359     DisplayMessage(buf, "");
1360     free(p); appData.results = q;
1361     if(nextGame <= appData.matchGames && resChar != ' ' &&
1362        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1363         UnloadEngine(&first);  // next game belongs to other pairing;
1364         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1365     }
1366 }
1367
1368 void
1369 MatchEvent(int mode)
1370 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1371         int dummy;
1372         if(matchMode) { // already in match mode: switch it off
1373             appData.matchGames = matchGame; // kludge to let match terminate after next game.
1374             ModeHighlight(); // kludgey way to remove checkmark...
1375             return;
1376         }
1377         if(gameMode != BeginningOfGame) {
1378             DisplayError(_("You can only start a match from the initial position."), 0);
1379             return;
1380         }
1381         appData.matchGames = appData.defaultMatchGames;
1382         /* Set up machine vs. machine match */
1383         nextGame = 0;
1384         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1385         if(appData.tourneyFile[0]) {
1386             ReserveGame(-1, 0);
1387             if(nextGame > appData.matchGames) {
1388                 char buf[MSG_SIZ];
1389                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1390                 DisplayError(buf, 0);
1391                 appData.tourneyFile[0] = 0;
1392                 return;
1393             }
1394         } else
1395         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1396             DisplayFatalError(_("Can't have a match with no chess programs"),
1397                               0, 2);
1398             return;
1399         }
1400         matchMode = mode;
1401         matchGame = roundNr = 1;
1402         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1403         NextMatchGame();
1404 }
1405
1406 void
1407 InitBackEnd3 P((void))
1408 {
1409     GameMode initialMode;
1410     char buf[MSG_SIZ];
1411     int err, len;
1412
1413     InitChessProgram(&first, startedFromSetupPosition);
1414
1415     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1416         free(programVersion);
1417         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1418         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1419     }
1420
1421     if (appData.icsActive) {
1422 #ifdef WIN32
1423         /* [DM] Make a console window if needed [HGM] merged ifs */
1424         ConsoleCreate();
1425 #endif
1426         err = establish();
1427         if (err != 0)
1428           {
1429             if (*appData.icsCommPort != NULLCHAR)
1430               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1431                              appData.icsCommPort);
1432             else
1433               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1434                         appData.icsHost, appData.icsPort);
1435
1436             if( (len > MSG_SIZ) && appData.debugMode )
1437               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1438
1439             DisplayFatalError(buf, err, 1);
1440             return;
1441         }
1442         SetICSMode();
1443         telnetISR =
1444           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1445         fromUserISR =
1446           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1447         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1448             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1449     } else if (appData.noChessProgram) {
1450         SetNCPMode();
1451     } else {
1452         SetGNUMode();
1453     }
1454
1455     if (*appData.cmailGameName != NULLCHAR) {
1456         SetCmailMode();
1457         OpenLoopback(&cmailPR);
1458         cmailISR =
1459           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1460     }
1461
1462     ThawUI();
1463     DisplayMessage("", "");
1464     if (StrCaseCmp(appData.initialMode, "") == 0) {
1465       initialMode = BeginningOfGame;
1466       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1467         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1468         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1469         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1470         ModeHighlight();
1471       }
1472     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1473       initialMode = TwoMachinesPlay;
1474     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1475       initialMode = AnalyzeFile;
1476     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1477       initialMode = AnalyzeMode;
1478     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1479       initialMode = MachinePlaysWhite;
1480     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1481       initialMode = MachinePlaysBlack;
1482     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1483       initialMode = EditGame;
1484     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1485       initialMode = EditPosition;
1486     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1487       initialMode = Training;
1488     } else {
1489       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1490       if( (len > MSG_SIZ) && appData.debugMode )
1491         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1492
1493       DisplayFatalError(buf, 0, 2);
1494       return;
1495     }
1496
1497     if (appData.matchMode) {
1498         if(appData.tourneyFile[0]) { // start tourney from command line
1499             FILE *f;
1500             if(f = fopen(appData.tourneyFile, "r")) {
1501                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1502                 fclose(f);
1503             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1504         }
1505         MatchEvent(TRUE);
1506     } else if (*appData.cmailGameName != NULLCHAR) {
1507         /* Set up cmail mode */
1508         ReloadCmailMsgEvent(TRUE);
1509     } else {
1510         /* Set up other modes */
1511         if (initialMode == AnalyzeFile) {
1512           if (*appData.loadGameFile == NULLCHAR) {
1513             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1514             return;
1515           }
1516         }
1517         if (*appData.loadGameFile != NULLCHAR) {
1518             (void) LoadGameFromFile(appData.loadGameFile,
1519                                     appData.loadGameIndex,
1520                                     appData.loadGameFile, TRUE);
1521         } else if (*appData.loadPositionFile != NULLCHAR) {
1522             (void) LoadPositionFromFile(appData.loadPositionFile,
1523                                         appData.loadPositionIndex,
1524                                         appData.loadPositionFile);
1525             /* [HGM] try to make self-starting even after FEN load */
1526             /* to allow automatic setup of fairy variants with wtm */
1527             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1528                 gameMode = BeginningOfGame;
1529                 setboardSpoiledMachineBlack = 1;
1530             }
1531             /* [HGM] loadPos: make that every new game uses the setup */
1532             /* from file as long as we do not switch variant          */
1533             if(!blackPlaysFirst) {
1534                 startedFromPositionFile = TRUE;
1535                 CopyBoard(filePosition, boards[0]);
1536             }
1537         }
1538         if (initialMode == AnalyzeMode) {
1539           if (appData.noChessProgram) {
1540             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1541             return;
1542           }
1543           if (appData.icsActive) {
1544             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1545             return;
1546           }
1547           AnalyzeModeEvent();
1548         } else if (initialMode == AnalyzeFile) {
1549           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1550           ShowThinkingEvent();
1551           AnalyzeFileEvent();
1552           AnalysisPeriodicEvent(1);
1553         } else if (initialMode == MachinePlaysWhite) {
1554           if (appData.noChessProgram) {
1555             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1556                               0, 2);
1557             return;
1558           }
1559           if (appData.icsActive) {
1560             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1561                               0, 2);
1562             return;
1563           }
1564           MachineWhiteEvent();
1565         } else if (initialMode == MachinePlaysBlack) {
1566           if (appData.noChessProgram) {
1567             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1568                               0, 2);
1569             return;
1570           }
1571           if (appData.icsActive) {
1572             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1573                               0, 2);
1574             return;
1575           }
1576           MachineBlackEvent();
1577         } else if (initialMode == TwoMachinesPlay) {
1578           if (appData.noChessProgram) {
1579             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1580                               0, 2);
1581             return;
1582           }
1583           if (appData.icsActive) {
1584             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1585                               0, 2);
1586             return;
1587           }
1588           TwoMachinesEvent();
1589         } else if (initialMode == EditGame) {
1590           EditGameEvent();
1591         } else if (initialMode == EditPosition) {
1592           EditPositionEvent();
1593         } else if (initialMode == Training) {
1594           if (*appData.loadGameFile == NULLCHAR) {
1595             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1596             return;
1597           }
1598           TrainingEvent();
1599         }
1600     }
1601 }
1602
1603 /*
1604  * Establish will establish a contact to a remote host.port.
1605  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1606  *  used to talk to the host.
1607  * Returns 0 if okay, error code if not.
1608  */
1609 int
1610 establish()
1611 {
1612     char buf[MSG_SIZ];
1613
1614     if (*appData.icsCommPort != NULLCHAR) {
1615         /* Talk to the host through a serial comm port */
1616         return OpenCommPort(appData.icsCommPort, &icsPR);
1617
1618     } else if (*appData.gateway != NULLCHAR) {
1619         if (*appData.remoteShell == NULLCHAR) {
1620             /* Use the rcmd protocol to run telnet program on a gateway host */
1621             snprintf(buf, sizeof(buf), "%s %s %s",
1622                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1623             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1624
1625         } else {
1626             /* Use the rsh program to run telnet program on a gateway host */
1627             if (*appData.remoteUser == NULLCHAR) {
1628                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1629                         appData.gateway, appData.telnetProgram,
1630                         appData.icsHost, appData.icsPort);
1631             } else {
1632                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1633                         appData.remoteShell, appData.gateway,
1634                         appData.remoteUser, appData.telnetProgram,
1635                         appData.icsHost, appData.icsPort);
1636             }
1637             return StartChildProcess(buf, "", &icsPR);
1638
1639         }
1640     } else if (appData.useTelnet) {
1641         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1642
1643     } else {
1644         /* TCP socket interface differs somewhat between
1645            Unix and NT; handle details in the front end.
1646            */
1647         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1648     }
1649 }
1650
1651 void EscapeExpand(char *p, char *q)
1652 {       // [HGM] initstring: routine to shape up string arguments
1653         while(*p++ = *q++) if(p[-1] == '\\')
1654             switch(*q++) {
1655                 case 'n': p[-1] = '\n'; break;
1656                 case 'r': p[-1] = '\r'; break;
1657                 case 't': p[-1] = '\t'; break;
1658                 case '\\': p[-1] = '\\'; break;
1659                 case 0: *p = 0; return;
1660                 default: p[-1] = q[-1]; break;
1661             }
1662 }
1663
1664 void
1665 show_bytes(fp, buf, count)
1666      FILE *fp;
1667      char *buf;
1668      int count;
1669 {
1670     while (count--) {
1671         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1672             fprintf(fp, "\\%03o", *buf & 0xff);
1673         } else {
1674             putc(*buf, fp);
1675         }
1676         buf++;
1677     }
1678     fflush(fp);
1679 }
1680
1681 /* Returns an errno value */
1682 int
1683 OutputMaybeTelnet(pr, message, count, outError)
1684      ProcRef pr;
1685      char *message;
1686      int count;
1687      int *outError;
1688 {
1689     char buf[8192], *p, *q, *buflim;
1690     int left, newcount, outcount;
1691
1692     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1693         *appData.gateway != NULLCHAR) {
1694         if (appData.debugMode) {
1695             fprintf(debugFP, ">ICS: ");
1696             show_bytes(debugFP, message, count);
1697             fprintf(debugFP, "\n");
1698         }
1699         return OutputToProcess(pr, message, count, outError);
1700     }
1701
1702     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1703     p = message;
1704     q = buf;
1705     left = count;
1706     newcount = 0;
1707     while (left) {
1708         if (q >= buflim) {
1709             if (appData.debugMode) {
1710                 fprintf(debugFP, ">ICS: ");
1711                 show_bytes(debugFP, buf, newcount);
1712                 fprintf(debugFP, "\n");
1713             }
1714             outcount = OutputToProcess(pr, buf, newcount, outError);
1715             if (outcount < newcount) return -1; /* to be sure */
1716             q = buf;
1717             newcount = 0;
1718         }
1719         if (*p == '\n') {
1720             *q++ = '\r';
1721             newcount++;
1722         } else if (((unsigned char) *p) == TN_IAC) {
1723             *q++ = (char) TN_IAC;
1724             newcount ++;
1725         }
1726         *q++ = *p++;
1727         newcount++;
1728         left--;
1729     }
1730     if (appData.debugMode) {
1731         fprintf(debugFP, ">ICS: ");
1732         show_bytes(debugFP, buf, newcount);
1733         fprintf(debugFP, "\n");
1734     }
1735     outcount = OutputToProcess(pr, buf, newcount, outError);
1736     if (outcount < newcount) return -1; /* to be sure */
1737     return count;
1738 }
1739
1740 void
1741 read_from_player(isr, closure, message, count, error)
1742      InputSourceRef isr;
1743      VOIDSTAR closure;
1744      char *message;
1745      int count;
1746      int error;
1747 {
1748     int outError, outCount;
1749     static int gotEof = 0;
1750
1751     /* Pass data read from player on to ICS */
1752     if (count > 0) {
1753         gotEof = 0;
1754         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1755         if (outCount < count) {
1756             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1757         }
1758     } else if (count < 0) {
1759         RemoveInputSource(isr);
1760         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1761     } else if (gotEof++ > 0) {
1762         RemoveInputSource(isr);
1763         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1764     }
1765 }
1766
1767 void
1768 KeepAlive()
1769 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1770     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1771     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1772     SendToICS("date\n");
1773     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1774 }
1775
1776 /* added routine for printf style output to ics */
1777 void ics_printf(char *format, ...)
1778 {
1779     char buffer[MSG_SIZ];
1780     va_list args;
1781
1782     va_start(args, format);
1783     vsnprintf(buffer, sizeof(buffer), format, args);
1784     buffer[sizeof(buffer)-1] = '\0';
1785     SendToICS(buffer);
1786     va_end(args);
1787 }
1788
1789 void
1790 SendToICS(s)
1791      char *s;
1792 {
1793     int count, outCount, outError;
1794
1795     if (icsPR == NULL) return;
1796
1797     count = strlen(s);
1798     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1799     if (outCount < count) {
1800         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1801     }
1802 }
1803
1804 /* This is used for sending logon scripts to the ICS. Sending
1805    without a delay causes problems when using timestamp on ICC
1806    (at least on my machine). */
1807 void
1808 SendToICSDelayed(s,msdelay)
1809      char *s;
1810      long msdelay;
1811 {
1812     int count, outCount, outError;
1813
1814     if (icsPR == NULL) return;
1815
1816     count = strlen(s);
1817     if (appData.debugMode) {
1818         fprintf(debugFP, ">ICS: ");
1819         show_bytes(debugFP, s, count);
1820         fprintf(debugFP, "\n");
1821     }
1822     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1823                                       msdelay);
1824     if (outCount < count) {
1825         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1826     }
1827 }
1828
1829
1830 /* Remove all highlighting escape sequences in s
1831    Also deletes any suffix starting with '('
1832    */
1833 char *
1834 StripHighlightAndTitle(s)
1835      char *s;
1836 {
1837     static char retbuf[MSG_SIZ];
1838     char *p = retbuf;
1839
1840     while (*s != NULLCHAR) {
1841         while (*s == '\033') {
1842             while (*s != NULLCHAR && !isalpha(*s)) s++;
1843             if (*s != NULLCHAR) s++;
1844         }
1845         while (*s != NULLCHAR && *s != '\033') {
1846             if (*s == '(' || *s == '[') {
1847                 *p = NULLCHAR;
1848                 return retbuf;
1849             }
1850             *p++ = *s++;
1851         }
1852     }
1853     *p = NULLCHAR;
1854     return retbuf;
1855 }
1856
1857 /* Remove all highlighting escape sequences in s */
1858 char *
1859 StripHighlight(s)
1860      char *s;
1861 {
1862     static char retbuf[MSG_SIZ];
1863     char *p = retbuf;
1864
1865     while (*s != NULLCHAR) {
1866         while (*s == '\033') {
1867             while (*s != NULLCHAR && !isalpha(*s)) s++;
1868             if (*s != NULLCHAR) s++;
1869         }
1870         while (*s != NULLCHAR && *s != '\033') {
1871             *p++ = *s++;
1872         }
1873     }
1874     *p = NULLCHAR;
1875     return retbuf;
1876 }
1877
1878 char *variantNames[] = VARIANT_NAMES;
1879 char *
1880 VariantName(v)
1881      VariantClass v;
1882 {
1883     return variantNames[v];
1884 }
1885
1886
1887 /* Identify a variant from the strings the chess servers use or the
1888    PGN Variant tag names we use. */
1889 VariantClass
1890 StringToVariant(e)
1891      char *e;
1892 {
1893     char *p;
1894     int wnum = -1;
1895     VariantClass v = VariantNormal;
1896     int i, found = FALSE;
1897     char buf[MSG_SIZ];
1898     int len;
1899
1900     if (!e) return v;
1901
1902     /* [HGM] skip over optional board-size prefixes */
1903     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1904         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1905         while( *e++ != '_');
1906     }
1907
1908     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1909         v = VariantNormal;
1910         found = TRUE;
1911     } else
1912     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1913       if (StrCaseStr(e, variantNames[i])) {
1914         v = (VariantClass) i;
1915         found = TRUE;
1916         break;
1917       }
1918     }
1919
1920     if (!found) {
1921       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1922           || StrCaseStr(e, "wild/fr")
1923           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1924         v = VariantFischeRandom;
1925       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1926                  (i = 1, p = StrCaseStr(e, "w"))) {
1927         p += i;
1928         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1929         if (isdigit(*p)) {
1930           wnum = atoi(p);
1931         } else {
1932           wnum = -1;
1933         }
1934         switch (wnum) {
1935         case 0: /* FICS only, actually */
1936         case 1:
1937           /* Castling legal even if K starts on d-file */
1938           v = VariantWildCastle;
1939           break;
1940         case 2:
1941         case 3:
1942         case 4:
1943           /* Castling illegal even if K & R happen to start in
1944              normal positions. */
1945           v = VariantNoCastle;
1946           break;
1947         case 5:
1948         case 7:
1949         case 8:
1950         case 10:
1951         case 11:
1952         case 12:
1953         case 13:
1954         case 14:
1955         case 15:
1956         case 18:
1957         case 19:
1958           /* Castling legal iff K & R start in normal positions */
1959           v = VariantNormal;
1960           break;
1961         case 6:
1962         case 20:
1963         case 21:
1964           /* Special wilds for position setup; unclear what to do here */
1965           v = VariantLoadable;
1966           break;
1967         case 9:
1968           /* Bizarre ICC game */
1969           v = VariantTwoKings;
1970           break;
1971         case 16:
1972           v = VariantKriegspiel;
1973           break;
1974         case 17:
1975           v = VariantLosers;
1976           break;
1977         case 22:
1978           v = VariantFischeRandom;
1979           break;
1980         case 23:
1981           v = VariantCrazyhouse;
1982           break;
1983         case 24:
1984           v = VariantBughouse;
1985           break;
1986         case 25:
1987           v = Variant3Check;
1988           break;
1989         case 26:
1990           /* Not quite the same as FICS suicide! */
1991           v = VariantGiveaway;
1992           break;
1993         case 27:
1994           v = VariantAtomic;
1995           break;
1996         case 28:
1997           v = VariantShatranj;
1998           break;
1999
2000         /* Temporary names for future ICC types.  The name *will* change in
2001            the next xboard/WinBoard release after ICC defines it. */
2002         case 29:
2003           v = Variant29;
2004           break;
2005         case 30:
2006           v = Variant30;
2007           break;
2008         case 31:
2009           v = Variant31;
2010           break;
2011         case 32:
2012           v = Variant32;
2013           break;
2014         case 33:
2015           v = Variant33;
2016           break;
2017         case 34:
2018           v = Variant34;
2019           break;
2020         case 35:
2021           v = Variant35;
2022           break;
2023         case 36:
2024           v = Variant36;
2025           break;
2026         case 37:
2027           v = VariantShogi;
2028           break;
2029         case 38:
2030           v = VariantXiangqi;
2031           break;
2032         case 39:
2033           v = VariantCourier;
2034           break;
2035         case 40:
2036           v = VariantGothic;
2037           break;
2038         case 41:
2039           v = VariantCapablanca;
2040           break;
2041         case 42:
2042           v = VariantKnightmate;
2043           break;
2044         case 43:
2045           v = VariantFairy;
2046           break;
2047         case 44:
2048           v = VariantCylinder;
2049           break;
2050         case 45:
2051           v = VariantFalcon;
2052           break;
2053         case 46:
2054           v = VariantCapaRandom;
2055           break;
2056         case 47:
2057           v = VariantBerolina;
2058           break;
2059         case 48:
2060           v = VariantJanus;
2061           break;
2062         case 49:
2063           v = VariantSuper;
2064           break;
2065         case 50:
2066           v = VariantGreat;
2067           break;
2068         case -1:
2069           /* Found "wild" or "w" in the string but no number;
2070              must assume it's normal chess. */
2071           v = VariantNormal;
2072           break;
2073         default:
2074           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2075           if( (len > MSG_SIZ) && appData.debugMode )
2076             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2077
2078           DisplayError(buf, 0);
2079           v = VariantUnknown;
2080           break;
2081         }
2082       }
2083     }
2084     if (appData.debugMode) {
2085       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2086               e, wnum, VariantName(v));
2087     }
2088     return v;
2089 }
2090
2091 static int leftover_start = 0, leftover_len = 0;
2092 char star_match[STAR_MATCH_N][MSG_SIZ];
2093
2094 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2095    advance *index beyond it, and set leftover_start to the new value of
2096    *index; else return FALSE.  If pattern contains the character '*', it
2097    matches any sequence of characters not containing '\r', '\n', or the
2098    character following the '*' (if any), and the matched sequence(s) are
2099    copied into star_match.
2100    */
2101 int
2102 looking_at(buf, index, pattern)
2103      char *buf;
2104      int *index;
2105      char *pattern;
2106 {
2107     char *bufp = &buf[*index], *patternp = pattern;
2108     int star_count = 0;
2109     char *matchp = star_match[0];
2110
2111     for (;;) {
2112         if (*patternp == NULLCHAR) {
2113             *index = leftover_start = bufp - buf;
2114             *matchp = NULLCHAR;
2115             return TRUE;
2116         }
2117         if (*bufp == NULLCHAR) return FALSE;
2118         if (*patternp == '*') {
2119             if (*bufp == *(patternp + 1)) {
2120                 *matchp = NULLCHAR;
2121                 matchp = star_match[++star_count];
2122                 patternp += 2;
2123                 bufp++;
2124                 continue;
2125             } else if (*bufp == '\n' || *bufp == '\r') {
2126                 patternp++;
2127                 if (*patternp == NULLCHAR)
2128                   continue;
2129                 else
2130                   return FALSE;
2131             } else {
2132                 *matchp++ = *bufp++;
2133                 continue;
2134             }
2135         }
2136         if (*patternp != *bufp) return FALSE;
2137         patternp++;
2138         bufp++;
2139     }
2140 }
2141
2142 void
2143 SendToPlayer(data, length)
2144      char *data;
2145      int length;
2146 {
2147     int error, outCount;
2148     outCount = OutputToProcess(NoProc, data, length, &error);
2149     if (outCount < length) {
2150         DisplayFatalError(_("Error writing to display"), error, 1);
2151     }
2152 }
2153
2154 void
2155 PackHolding(packed, holding)
2156      char packed[];
2157      char *holding;
2158 {
2159     char *p = holding;
2160     char *q = packed;
2161     int runlength = 0;
2162     int curr = 9999;
2163     do {
2164         if (*p == curr) {
2165             runlength++;
2166         } else {
2167             switch (runlength) {
2168               case 0:
2169                 break;
2170               case 1:
2171                 *q++ = curr;
2172                 break;
2173               case 2:
2174                 *q++ = curr;
2175                 *q++ = curr;
2176                 break;
2177               default:
2178                 sprintf(q, "%d", runlength);
2179                 while (*q) q++;
2180                 *q++ = curr;
2181                 break;
2182             }
2183             runlength = 1;
2184             curr = *p;
2185         }
2186     } while (*p++);
2187     *q = NULLCHAR;
2188 }
2189
2190 /* Telnet protocol requests from the front end */
2191 void
2192 TelnetRequest(ddww, option)
2193      unsigned char ddww, option;
2194 {
2195     unsigned char msg[3];
2196     int outCount, outError;
2197
2198     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2199
2200     if (appData.debugMode) {
2201         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2202         switch (ddww) {
2203           case TN_DO:
2204             ddwwStr = "DO";
2205             break;
2206           case TN_DONT:
2207             ddwwStr = "DONT";
2208             break;
2209           case TN_WILL:
2210             ddwwStr = "WILL";
2211             break;
2212           case TN_WONT:
2213             ddwwStr = "WONT";
2214             break;
2215           default:
2216             ddwwStr = buf1;
2217             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2218             break;
2219         }
2220         switch (option) {
2221           case TN_ECHO:
2222             optionStr = "ECHO";
2223             break;
2224           default:
2225             optionStr = buf2;
2226             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2227             break;
2228         }
2229         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2230     }
2231     msg[0] = TN_IAC;
2232     msg[1] = ddww;
2233     msg[2] = option;
2234     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2235     if (outCount < 3) {
2236         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2237     }
2238 }
2239
2240 void
2241 DoEcho()
2242 {
2243     if (!appData.icsActive) return;
2244     TelnetRequest(TN_DO, TN_ECHO);
2245 }
2246
2247 void
2248 DontEcho()
2249 {
2250     if (!appData.icsActive) return;
2251     TelnetRequest(TN_DONT, TN_ECHO);
2252 }
2253
2254 void
2255 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2256 {
2257     /* put the holdings sent to us by the server on the board holdings area */
2258     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2259     char p;
2260     ChessSquare piece;
2261
2262     if(gameInfo.holdingsWidth < 2)  return;
2263     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2264         return; // prevent overwriting by pre-board holdings
2265
2266     if( (int)lowestPiece >= BlackPawn ) {
2267         holdingsColumn = 0;
2268         countsColumn = 1;
2269         holdingsStartRow = BOARD_HEIGHT-1;
2270         direction = -1;
2271     } else {
2272         holdingsColumn = BOARD_WIDTH-1;
2273         countsColumn = BOARD_WIDTH-2;
2274         holdingsStartRow = 0;
2275         direction = 1;
2276     }
2277
2278     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2279         board[i][holdingsColumn] = EmptySquare;
2280         board[i][countsColumn]   = (ChessSquare) 0;
2281     }
2282     while( (p=*holdings++) != NULLCHAR ) {
2283         piece = CharToPiece( ToUpper(p) );
2284         if(piece == EmptySquare) continue;
2285         /*j = (int) piece - (int) WhitePawn;*/
2286         j = PieceToNumber(piece);
2287         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2288         if(j < 0) continue;               /* should not happen */
2289         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2290         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2291         board[holdingsStartRow+j*direction][countsColumn]++;
2292     }
2293 }
2294
2295
2296 void
2297 VariantSwitch(Board board, VariantClass newVariant)
2298 {
2299    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2300    static Board oldBoard;
2301
2302    startedFromPositionFile = FALSE;
2303    if(gameInfo.variant == newVariant) return;
2304
2305    /* [HGM] This routine is called each time an assignment is made to
2306     * gameInfo.variant during a game, to make sure the board sizes
2307     * are set to match the new variant. If that means adding or deleting
2308     * holdings, we shift the playing board accordingly
2309     * This kludge is needed because in ICS observe mode, we get boards
2310     * of an ongoing game without knowing the variant, and learn about the
2311     * latter only later. This can be because of the move list we requested,
2312     * in which case the game history is refilled from the beginning anyway,
2313     * but also when receiving holdings of a crazyhouse game. In the latter
2314     * case we want to add those holdings to the already received position.
2315     */
2316
2317
2318    if (appData.debugMode) {
2319      fprintf(debugFP, "Switch board from %s to %s\n",
2320              VariantName(gameInfo.variant), VariantName(newVariant));
2321      setbuf(debugFP, NULL);
2322    }
2323    shuffleOpenings = 0;       /* [HGM] shuffle */
2324    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2325    switch(newVariant)
2326      {
2327      case VariantShogi:
2328        newWidth = 9;  newHeight = 9;
2329        gameInfo.holdingsSize = 7;
2330      case VariantBughouse:
2331      case VariantCrazyhouse:
2332        newHoldingsWidth = 2; break;
2333      case VariantGreat:
2334        newWidth = 10;
2335      case VariantSuper:
2336        newHoldingsWidth = 2;
2337        gameInfo.holdingsSize = 8;
2338        break;
2339      case VariantGothic:
2340      case VariantCapablanca:
2341      case VariantCapaRandom:
2342        newWidth = 10;
2343      default:
2344        newHoldingsWidth = gameInfo.holdingsSize = 0;
2345      };
2346
2347    if(newWidth  != gameInfo.boardWidth  ||
2348       newHeight != gameInfo.boardHeight ||
2349       newHoldingsWidth != gameInfo.holdingsWidth ) {
2350
2351      /* shift position to new playing area, if needed */
2352      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2353        for(i=0; i<BOARD_HEIGHT; i++)
2354          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2355            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2356              board[i][j];
2357        for(i=0; i<newHeight; i++) {
2358          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2359          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2360        }
2361      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2362        for(i=0; i<BOARD_HEIGHT; i++)
2363          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2364            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2365              board[i][j];
2366      }
2367      gameInfo.boardWidth  = newWidth;
2368      gameInfo.boardHeight = newHeight;
2369      gameInfo.holdingsWidth = newHoldingsWidth;
2370      gameInfo.variant = newVariant;
2371      InitDrawingSizes(-2, 0);
2372    } else gameInfo.variant = newVariant;
2373    CopyBoard(oldBoard, board);   // remember correctly formatted board
2374      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2375    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2376 }
2377
2378 static int loggedOn = FALSE;
2379
2380 /*-- Game start info cache: --*/
2381 int gs_gamenum;
2382 char gs_kind[MSG_SIZ];
2383 static char player1Name[128] = "";
2384 static char player2Name[128] = "";
2385 static char cont_seq[] = "\n\\   ";
2386 static int player1Rating = -1;
2387 static int player2Rating = -1;
2388 /*----------------------------*/
2389
2390 ColorClass curColor = ColorNormal;
2391 int suppressKibitz = 0;
2392
2393 // [HGM] seekgraph
2394 Boolean soughtPending = FALSE;
2395 Boolean seekGraphUp;
2396 #define MAX_SEEK_ADS 200
2397 #define SQUARE 0x80
2398 char *seekAdList[MAX_SEEK_ADS];
2399 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2400 float tcList[MAX_SEEK_ADS];
2401 char colorList[MAX_SEEK_ADS];
2402 int nrOfSeekAds = 0;
2403 int minRating = 1010, maxRating = 2800;
2404 int hMargin = 10, vMargin = 20, h, w;
2405 extern int squareSize, lineGap;
2406
2407 void
2408 PlotSeekAd(int i)
2409 {
2410         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2411         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2412         if(r < minRating+100 && r >=0 ) r = minRating+100;
2413         if(r > maxRating) r = maxRating;
2414         if(tc < 1.) tc = 1.;
2415         if(tc > 95.) tc = 95.;
2416         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2417         y = ((double)r - minRating)/(maxRating - minRating)
2418             * (h-vMargin-squareSize/8-1) + vMargin;
2419         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2420         if(strstr(seekAdList[i], " u ")) color = 1;
2421         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2422            !strstr(seekAdList[i], "bullet") &&
2423            !strstr(seekAdList[i], "blitz") &&
2424            !strstr(seekAdList[i], "standard") ) color = 2;
2425         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2426         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2427 }
2428
2429 void
2430 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2431 {
2432         char buf[MSG_SIZ], *ext = "";
2433         VariantClass v = StringToVariant(type);
2434         if(strstr(type, "wild")) {
2435             ext = type + 4; // append wild number
2436             if(v == VariantFischeRandom) type = "chess960"; else
2437             if(v == VariantLoadable) type = "setup"; else
2438             type = VariantName(v);
2439         }
2440         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2441         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2442             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2443             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2444             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2445             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2446             seekNrList[nrOfSeekAds] = nr;
2447             zList[nrOfSeekAds] = 0;
2448             seekAdList[nrOfSeekAds++] = StrSave(buf);
2449             if(plot) PlotSeekAd(nrOfSeekAds-1);
2450         }
2451 }
2452
2453 void
2454 EraseSeekDot(int i)
2455 {
2456     int x = xList[i], y = yList[i], d=squareSize/4, k;
2457     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2458     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2459     // now replot every dot that overlapped
2460     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2461         int xx = xList[k], yy = yList[k];
2462         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2463             DrawSeekDot(xx, yy, colorList[k]);
2464     }
2465 }
2466
2467 void
2468 RemoveSeekAd(int nr)
2469 {
2470         int i;
2471         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2472             EraseSeekDot(i);
2473             if(seekAdList[i]) free(seekAdList[i]);
2474             seekAdList[i] = seekAdList[--nrOfSeekAds];
2475             seekNrList[i] = seekNrList[nrOfSeekAds];
2476             ratingList[i] = ratingList[nrOfSeekAds];
2477             colorList[i]  = colorList[nrOfSeekAds];
2478             tcList[i] = tcList[nrOfSeekAds];
2479             xList[i]  = xList[nrOfSeekAds];
2480             yList[i]  = yList[nrOfSeekAds];
2481             zList[i]  = zList[nrOfSeekAds];
2482             seekAdList[nrOfSeekAds] = NULL;
2483             break;
2484         }
2485 }
2486
2487 Boolean
2488 MatchSoughtLine(char *line)
2489 {
2490     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2491     int nr, base, inc, u=0; char dummy;
2492
2493     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2494        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2495        (u=1) &&
2496        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2497         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2498         // match: compact and save the line
2499         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2500         return TRUE;
2501     }
2502     return FALSE;
2503 }
2504
2505 int
2506 DrawSeekGraph()
2507 {
2508     int i;
2509     if(!seekGraphUp) return FALSE;
2510     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2511     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2512
2513     DrawSeekBackground(0, 0, w, h);
2514     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2515     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2516     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2517         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2518         yy = h-1-yy;
2519         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2520         if(i%500 == 0) {
2521             char buf[MSG_SIZ];
2522             snprintf(buf, MSG_SIZ, "%d", i);
2523             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2524         }
2525     }
2526     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2527     for(i=1; i<100; i+=(i<10?1:5)) {
2528         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2529         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2530         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2531             char buf[MSG_SIZ];
2532             snprintf(buf, MSG_SIZ, "%d", i);
2533             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2534         }
2535     }
2536     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2537     return TRUE;
2538 }
2539
2540 int SeekGraphClick(ClickType click, int x, int y, int moving)
2541 {
2542     static int lastDown = 0, displayed = 0, lastSecond;
2543     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2544         if(click == Release || moving) return FALSE;
2545         nrOfSeekAds = 0;
2546         soughtPending = TRUE;
2547         SendToICS(ics_prefix);
2548         SendToICS("sought\n"); // should this be "sought all"?
2549     } else { // issue challenge based on clicked ad
2550         int dist = 10000; int i, closest = 0, second = 0;
2551         for(i=0; i<nrOfSeekAds; i++) {
2552             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2553             if(d < dist) { dist = d; closest = i; }
2554             second += (d - zList[i] < 120); // count in-range ads
2555             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2556         }
2557         if(dist < 120) {
2558             char buf[MSG_SIZ];
2559             second = (second > 1);
2560             if(displayed != closest || second != lastSecond) {
2561                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2562                 lastSecond = second; displayed = closest;
2563             }
2564             if(click == Press) {
2565                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2566                 lastDown = closest;
2567                 return TRUE;
2568             } // on press 'hit', only show info
2569             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2570             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2571             SendToICS(ics_prefix);
2572             SendToICS(buf);
2573             return TRUE; // let incoming board of started game pop down the graph
2574         } else if(click == Release) { // release 'miss' is ignored
2575             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2576             if(moving == 2) { // right up-click
2577                 nrOfSeekAds = 0; // refresh graph
2578                 soughtPending = TRUE;
2579                 SendToICS(ics_prefix);
2580                 SendToICS("sought\n"); // should this be "sought all"?
2581             }
2582             return TRUE;
2583         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2584         // press miss or release hit 'pop down' seek graph
2585         seekGraphUp = FALSE;
2586         DrawPosition(TRUE, NULL);
2587     }
2588     return TRUE;
2589 }
2590
2591 void
2592 read_from_ics(isr, closure, data, count, error)
2593      InputSourceRef isr;
2594      VOIDSTAR closure;
2595      char *data;
2596      int count;
2597      int error;
2598 {
2599 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2600 #define STARTED_NONE 0
2601 #define STARTED_MOVES 1
2602 #define STARTED_BOARD 2
2603 #define STARTED_OBSERVE 3
2604 #define STARTED_HOLDINGS 4
2605 #define STARTED_CHATTER 5
2606 #define STARTED_COMMENT 6
2607 #define STARTED_MOVES_NOHIDE 7
2608
2609     static int started = STARTED_NONE;
2610     static char parse[20000];
2611     static int parse_pos = 0;
2612     static char buf[BUF_SIZE + 1];
2613     static int firstTime = TRUE, intfSet = FALSE;
2614     static ColorClass prevColor = ColorNormal;
2615     static int savingComment = FALSE;
2616     static int cmatch = 0; // continuation sequence match
2617     char *bp;
2618     char str[MSG_SIZ];
2619     int i, oldi;
2620     int buf_len;
2621     int next_out;
2622     int tkind;
2623     int backup;    /* [DM] For zippy color lines */
2624     char *p;
2625     char talker[MSG_SIZ]; // [HGM] chat
2626     int channel;
2627
2628     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2629
2630     if (appData.debugMode) {
2631       if (!error) {
2632         fprintf(debugFP, "<ICS: ");
2633         show_bytes(debugFP, data, count);
2634         fprintf(debugFP, "\n");
2635       }
2636     }
2637
2638     if (appData.debugMode) { int f = forwardMostMove;
2639         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2640                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2641                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2642     }
2643     if (count > 0) {
2644         /* If last read ended with a partial line that we couldn't parse,
2645            prepend it to the new read and try again. */
2646         if (leftover_len > 0) {
2647             for (i=0; i<leftover_len; i++)
2648               buf[i] = buf[leftover_start + i];
2649         }
2650
2651     /* copy new characters into the buffer */
2652     bp = buf + leftover_len;
2653     buf_len=leftover_len;
2654     for (i=0; i<count; i++)
2655     {
2656         // ignore these
2657         if (data[i] == '\r')
2658             continue;
2659
2660         // join lines split by ICS?
2661         if (!appData.noJoin)
2662         {
2663             /*
2664                 Joining just consists of finding matches against the
2665                 continuation sequence, and discarding that sequence
2666                 if found instead of copying it.  So, until a match
2667                 fails, there's nothing to do since it might be the
2668                 complete sequence, and thus, something we don't want
2669                 copied.
2670             */
2671             if (data[i] == cont_seq[cmatch])
2672             {
2673                 cmatch++;
2674                 if (cmatch == strlen(cont_seq))
2675                 {
2676                     cmatch = 0; // complete match.  just reset the counter
2677
2678                     /*
2679                         it's possible for the ICS to not include the space
2680                         at the end of the last word, making our [correct]
2681                         join operation fuse two separate words.  the server
2682                         does this when the space occurs at the width setting.
2683                     */
2684                     if (!buf_len || buf[buf_len-1] != ' ')
2685                     {
2686                         *bp++ = ' ';
2687                         buf_len++;
2688                     }
2689                 }
2690                 continue;
2691             }
2692             else if (cmatch)
2693             {
2694                 /*
2695                     match failed, so we have to copy what matched before
2696                     falling through and copying this character.  In reality,
2697                     this will only ever be just the newline character, but
2698                     it doesn't hurt to be precise.
2699                 */
2700                 strncpy(bp, cont_seq, cmatch);
2701                 bp += cmatch;
2702                 buf_len += cmatch;
2703                 cmatch = 0;
2704             }
2705         }
2706
2707         // copy this char
2708         *bp++ = data[i];
2709         buf_len++;
2710     }
2711
2712         buf[buf_len] = NULLCHAR;
2713 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2714         next_out = 0;
2715         leftover_start = 0;
2716
2717         i = 0;
2718         while (i < buf_len) {
2719             /* Deal with part of the TELNET option negotiation
2720                protocol.  We refuse to do anything beyond the
2721                defaults, except that we allow the WILL ECHO option,
2722                which ICS uses to turn off password echoing when we are
2723                directly connected to it.  We reject this option
2724                if localLineEditing mode is on (always on in xboard)
2725                and we are talking to port 23, which might be a real
2726                telnet server that will try to keep WILL ECHO on permanently.
2727              */
2728             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2729                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2730                 unsigned char option;
2731                 oldi = i;
2732                 switch ((unsigned char) buf[++i]) {
2733                   case TN_WILL:
2734                     if (appData.debugMode)
2735                       fprintf(debugFP, "\n<WILL ");
2736                     switch (option = (unsigned char) buf[++i]) {
2737                       case TN_ECHO:
2738                         if (appData.debugMode)
2739                           fprintf(debugFP, "ECHO ");
2740                         /* Reply only if this is a change, according
2741                            to the protocol rules. */
2742                         if (remoteEchoOption) break;
2743                         if (appData.localLineEditing &&
2744                             atoi(appData.icsPort) == TN_PORT) {
2745                             TelnetRequest(TN_DONT, TN_ECHO);
2746                         } else {
2747                             EchoOff();
2748                             TelnetRequest(TN_DO, TN_ECHO);
2749                             remoteEchoOption = TRUE;
2750                         }
2751                         break;
2752                       default:
2753                         if (appData.debugMode)
2754                           fprintf(debugFP, "%d ", option);
2755                         /* Whatever this is, we don't want it. */
2756                         TelnetRequest(TN_DONT, option);
2757                         break;
2758                     }
2759                     break;
2760                   case TN_WONT:
2761                     if (appData.debugMode)
2762                       fprintf(debugFP, "\n<WONT ");
2763                     switch (option = (unsigned char) buf[++i]) {
2764                       case TN_ECHO:
2765                         if (appData.debugMode)
2766                           fprintf(debugFP, "ECHO ");
2767                         /* Reply only if this is a change, according
2768                            to the protocol rules. */
2769                         if (!remoteEchoOption) break;
2770                         EchoOn();
2771                         TelnetRequest(TN_DONT, TN_ECHO);
2772                         remoteEchoOption = FALSE;
2773                         break;
2774                       default:
2775                         if (appData.debugMode)
2776                           fprintf(debugFP, "%d ", (unsigned char) option);
2777                         /* Whatever this is, it must already be turned
2778                            off, because we never agree to turn on
2779                            anything non-default, so according to the
2780                            protocol rules, we don't reply. */
2781                         break;
2782                     }
2783                     break;
2784                   case TN_DO:
2785                     if (appData.debugMode)
2786                       fprintf(debugFP, "\n<DO ");
2787                     switch (option = (unsigned char) buf[++i]) {
2788                       default:
2789                         /* Whatever this is, we refuse to do it. */
2790                         if (appData.debugMode)
2791                           fprintf(debugFP, "%d ", option);
2792                         TelnetRequest(TN_WONT, option);
2793                         break;
2794                     }
2795                     break;
2796                   case TN_DONT:
2797                     if (appData.debugMode)
2798                       fprintf(debugFP, "\n<DONT ");
2799                     switch (option = (unsigned char) buf[++i]) {
2800                       default:
2801                         if (appData.debugMode)
2802                           fprintf(debugFP, "%d ", option);
2803                         /* Whatever this is, we are already not doing
2804                            it, because we never agree to do anything
2805                            non-default, so according to the protocol
2806                            rules, we don't reply. */
2807                         break;
2808                     }
2809                     break;
2810                   case TN_IAC:
2811                     if (appData.debugMode)
2812                       fprintf(debugFP, "\n<IAC ");
2813                     /* Doubled IAC; pass it through */
2814                     i--;
2815                     break;
2816                   default:
2817                     if (appData.debugMode)
2818                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2819                     /* Drop all other telnet commands on the floor */
2820                     break;
2821                 }
2822                 if (oldi > next_out)
2823                   SendToPlayer(&buf[next_out], oldi - next_out);
2824                 if (++i > next_out)
2825                   next_out = i;
2826                 continue;
2827             }
2828
2829             /* OK, this at least will *usually* work */
2830             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2831                 loggedOn = TRUE;
2832             }
2833
2834             if (loggedOn && !intfSet) {
2835                 if (ics_type == ICS_ICC) {
2836                   snprintf(str, MSG_SIZ,
2837                           "/set-quietly interface %s\n/set-quietly style 12\n",
2838                           programVersion);
2839                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2840                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2841                 } else if (ics_type == ICS_CHESSNET) {
2842                   snprintf(str, MSG_SIZ, "/style 12\n");
2843                 } else {
2844                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2845                   strcat(str, programVersion);
2846                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2847                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2848                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2849 #ifdef WIN32
2850                   strcat(str, "$iset nohighlight 1\n");
2851 #endif
2852                   strcat(str, "$iset lock 1\n$style 12\n");
2853                 }
2854                 SendToICS(str);
2855                 NotifyFrontendLogin();
2856                 intfSet = TRUE;
2857             }
2858
2859             if (started == STARTED_COMMENT) {
2860                 /* Accumulate characters in comment */
2861                 parse[parse_pos++] = buf[i];
2862                 if (buf[i] == '\n') {
2863                     parse[parse_pos] = NULLCHAR;
2864                     if(chattingPartner>=0) {
2865                         char mess[MSG_SIZ];
2866                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2867                         OutputChatMessage(chattingPartner, mess);
2868                         chattingPartner = -1;
2869                         next_out = i+1; // [HGM] suppress printing in ICS window
2870                     } else
2871                     if(!suppressKibitz) // [HGM] kibitz
2872                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2873                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2874                         int nrDigit = 0, nrAlph = 0, j;
2875                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2876                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2877                         parse[parse_pos] = NULLCHAR;
2878                         // try to be smart: if it does not look like search info, it should go to
2879                         // ICS interaction window after all, not to engine-output window.
2880                         for(j=0; j<parse_pos; j++) { // count letters and digits
2881                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2882                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2883                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2884                         }
2885                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2886                             int depth=0; float score;
2887                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2888                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2889                                 pvInfoList[forwardMostMove-1].depth = depth;
2890                                 pvInfoList[forwardMostMove-1].score = 100*score;
2891                             }
2892                             OutputKibitz(suppressKibitz, parse);
2893                         } else {
2894                             char tmp[MSG_SIZ];
2895                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2896                             SendToPlayer(tmp, strlen(tmp));
2897                         }
2898                         next_out = i+1; // [HGM] suppress printing in ICS window
2899                     }
2900                     started = STARTED_NONE;
2901                 } else {
2902                     /* Don't match patterns against characters in comment */
2903                     i++;
2904                     continue;
2905                 }
2906             }
2907             if (started == STARTED_CHATTER) {
2908                 if (buf[i] != '\n') {
2909                     /* Don't match patterns against characters in chatter */
2910                     i++;
2911                     continue;
2912                 }
2913                 started = STARTED_NONE;
2914                 if(suppressKibitz) next_out = i+1;
2915             }
2916
2917             /* Kludge to deal with rcmd protocol */
2918             if (firstTime && looking_at(buf, &i, "\001*")) {
2919                 DisplayFatalError(&buf[1], 0, 1);
2920                 continue;
2921             } else {
2922                 firstTime = FALSE;
2923             }
2924
2925             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2926                 ics_type = ICS_ICC;
2927                 ics_prefix = "/";
2928                 if (appData.debugMode)
2929                   fprintf(debugFP, "ics_type %d\n", ics_type);
2930                 continue;
2931             }
2932             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2933                 ics_type = ICS_FICS;
2934                 ics_prefix = "$";
2935                 if (appData.debugMode)
2936                   fprintf(debugFP, "ics_type %d\n", ics_type);
2937                 continue;
2938             }
2939             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2940                 ics_type = ICS_CHESSNET;
2941                 ics_prefix = "/";
2942                 if (appData.debugMode)
2943                   fprintf(debugFP, "ics_type %d\n", ics_type);
2944                 continue;
2945             }
2946
2947             if (!loggedOn &&
2948                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2949                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2950                  looking_at(buf, &i, "will be \"*\""))) {
2951               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2952               continue;
2953             }
2954
2955             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2956               char buf[MSG_SIZ];
2957               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2958               DisplayIcsInteractionTitle(buf);
2959               have_set_title = TRUE;
2960             }
2961
2962             /* skip finger notes */
2963             if (started == STARTED_NONE &&
2964                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2965                  (buf[i] == '1' && buf[i+1] == '0')) &&
2966                 buf[i+2] == ':' && buf[i+3] == ' ') {
2967               started = STARTED_CHATTER;
2968               i += 3;
2969               continue;
2970             }
2971
2972             oldi = i;
2973             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2974             if(appData.seekGraph) {
2975                 if(soughtPending && MatchSoughtLine(buf+i)) {
2976                     i = strstr(buf+i, "rated") - buf;
2977                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2978                     next_out = leftover_start = i;
2979                     started = STARTED_CHATTER;
2980                     suppressKibitz = TRUE;
2981                     continue;
2982                 }
2983                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2984                         && looking_at(buf, &i, "* ads displayed")) {
2985                     soughtPending = FALSE;
2986                     seekGraphUp = TRUE;
2987                     DrawSeekGraph();
2988                     continue;
2989                 }
2990                 if(appData.autoRefresh) {
2991                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2992                         int s = (ics_type == ICS_ICC); // ICC format differs
2993                         if(seekGraphUp)
2994                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2995                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2996                         looking_at(buf, &i, "*% "); // eat prompt
2997                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2998                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2999                         next_out = i; // suppress
3000                         continue;
3001                     }
3002                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3003                         char *p = star_match[0];
3004                         while(*p) {
3005                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3006                             while(*p && *p++ != ' '); // next
3007                         }
3008                         looking_at(buf, &i, "*% "); // eat prompt
3009                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3010                         next_out = i;
3011                         continue;
3012                     }
3013                 }
3014             }
3015
3016             /* skip formula vars */
3017             if (started == STARTED_NONE &&
3018                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3019               started = STARTED_CHATTER;
3020               i += 3;
3021               continue;
3022             }
3023
3024             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3025             if (appData.autoKibitz && started == STARTED_NONE &&
3026                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3027                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3028                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3029                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3030                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3031                         suppressKibitz = TRUE;
3032                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3033                         next_out = i;
3034                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3035                                 && (gameMode == IcsPlayingWhite)) ||
3036                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3037                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3038                             started = STARTED_CHATTER; // own kibitz we simply discard
3039                         else {
3040                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3041                             parse_pos = 0; parse[0] = NULLCHAR;
3042                             savingComment = TRUE;
3043                             suppressKibitz = gameMode != IcsObserving ? 2 :
3044                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3045                         }
3046                         continue;
3047                 } else
3048                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3049                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3050                          && atoi(star_match[0])) {
3051                     // suppress the acknowledgements of our own autoKibitz
3052                     char *p;
3053                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3054                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3055                     SendToPlayer(star_match[0], strlen(star_match[0]));
3056                     if(looking_at(buf, &i, "*% ")) // eat prompt
3057                         suppressKibitz = FALSE;
3058                     next_out = i;
3059                     continue;
3060                 }
3061             } // [HGM] kibitz: end of patch
3062
3063             // [HGM] chat: intercept tells by users for which we have an open chat window
3064             channel = -1;
3065             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3066                                            looking_at(buf, &i, "* whispers:") ||
3067                                            looking_at(buf, &i, "* kibitzes:") ||
3068                                            looking_at(buf, &i, "* shouts:") ||
3069                                            looking_at(buf, &i, "* c-shouts:") ||
3070                                            looking_at(buf, &i, "--> * ") ||
3071                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3072                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3073                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3074                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3075                 int p;
3076                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3077                 chattingPartner = -1;
3078
3079                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3080                 for(p=0; p<MAX_CHAT; p++) {
3081                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3082                     talker[0] = '['; strcat(talker, "] ");
3083                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3084                     chattingPartner = p; break;
3085                     }
3086                 } else
3087                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3088                 for(p=0; p<MAX_CHAT; p++) {
3089                     if(!strcmp("kibitzes", chatPartner[p])) {
3090                         talker[0] = '['; strcat(talker, "] ");
3091                         chattingPartner = p; break;
3092                     }
3093                 } else
3094                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3095                 for(p=0; p<MAX_CHAT; p++) {
3096                     if(!strcmp("whispers", chatPartner[p])) {
3097                         talker[0] = '['; strcat(talker, "] ");
3098                         chattingPartner = p; break;
3099                     }
3100                 } else
3101                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3102                   if(buf[i-8] == '-' && buf[i-3] == 't')
3103                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3104                     if(!strcmp("c-shouts", chatPartner[p])) {
3105                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3106                         chattingPartner = p; break;
3107                     }
3108                   }
3109                   if(chattingPartner < 0)
3110                   for(p=0; p<MAX_CHAT; p++) {
3111                     if(!strcmp("shouts", chatPartner[p])) {
3112                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3113                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3114                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3115                         chattingPartner = p; break;
3116                     }
3117                   }
3118                 }
3119                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3120                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3121                     talker[0] = 0; Colorize(ColorTell, FALSE);
3122                     chattingPartner = p; break;
3123                 }
3124                 if(chattingPartner<0) i = oldi; else {
3125                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3126                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3127                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3128                     started = STARTED_COMMENT;
3129                     parse_pos = 0; parse[0] = NULLCHAR;
3130                     savingComment = 3 + chattingPartner; // counts as TRUE
3131                     suppressKibitz = TRUE;
3132                     continue;
3133                 }
3134             } // [HGM] chat: end of patch
3135
3136           backup = i;
3137             if (appData.zippyTalk || appData.zippyPlay) {
3138                 /* [DM] Backup address for color zippy lines */
3139 #if ZIPPY
3140                if (loggedOn == TRUE)
3141                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3142                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3143 #endif
3144             } // [DM] 'else { ' deleted
3145                 if (
3146                     /* Regular tells and says */
3147                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3148                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3149                     looking_at(buf, &i, "* says: ") ||
3150                     /* Don't color "message" or "messages" output */
3151                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3152                     looking_at(buf, &i, "*. * at *:*: ") ||
3153                     looking_at(buf, &i, "--* (*:*): ") ||
3154                     /* Message notifications (same color as tells) */
3155                     looking_at(buf, &i, "* has left a message ") ||
3156                     looking_at(buf, &i, "* just sent you a message:\n") ||
3157                     /* Whispers and kibitzes */
3158                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3159                     looking_at(buf, &i, "* kibitzes: ") ||
3160                     /* Channel tells */
3161                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3162
3163                   if (tkind == 1 && strchr(star_match[0], ':')) {
3164                       /* Avoid "tells you:" spoofs in channels */
3165                      tkind = 3;
3166                   }
3167                   if (star_match[0][0] == NULLCHAR ||
3168                       strchr(star_match[0], ' ') ||
3169                       (tkind == 3 && strchr(star_match[1], ' '))) {
3170                     /* Reject bogus matches */
3171                     i = oldi;
3172                   } else {
3173                     if (appData.colorize) {
3174                       if (oldi > next_out) {
3175                         SendToPlayer(&buf[next_out], oldi - next_out);
3176                         next_out = oldi;
3177                       }
3178                       switch (tkind) {
3179                       case 1:
3180                         Colorize(ColorTell, FALSE);
3181                         curColor = ColorTell;
3182                         break;
3183                       case 2:
3184                         Colorize(ColorKibitz, FALSE);
3185                         curColor = ColorKibitz;
3186                         break;
3187                       case 3:
3188                         p = strrchr(star_match[1], '(');
3189                         if (p == NULL) {
3190                           p = star_match[1];
3191                         } else {
3192                           p++;
3193                         }
3194                         if (atoi(p) == 1) {
3195                           Colorize(ColorChannel1, FALSE);
3196                           curColor = ColorChannel1;
3197                         } else {
3198                           Colorize(ColorChannel, FALSE);
3199                           curColor = ColorChannel;
3200                         }
3201                         break;
3202                       case 5:
3203                         curColor = ColorNormal;
3204                         break;
3205                       }
3206                     }
3207                     if (started == STARTED_NONE && appData.autoComment &&
3208                         (gameMode == IcsObserving ||
3209                          gameMode == IcsPlayingWhite ||
3210                          gameMode == IcsPlayingBlack)) {
3211                       parse_pos = i - oldi;
3212                       memcpy(parse, &buf[oldi], parse_pos);
3213                       parse[parse_pos] = NULLCHAR;
3214                       started = STARTED_COMMENT;
3215                       savingComment = TRUE;
3216                     } else {
3217                       started = STARTED_CHATTER;
3218                       savingComment = FALSE;
3219                     }
3220                     loggedOn = TRUE;
3221                     continue;
3222                   }
3223                 }
3224
3225                 if (looking_at(buf, &i, "* s-shouts: ") ||
3226                     looking_at(buf, &i, "* c-shouts: ")) {
3227                     if (appData.colorize) {
3228                         if (oldi > next_out) {
3229                             SendToPlayer(&buf[next_out], oldi - next_out);
3230                             next_out = oldi;
3231                         }
3232                         Colorize(ColorSShout, FALSE);
3233                         curColor = ColorSShout;
3234                     }
3235                     loggedOn = TRUE;
3236                     started = STARTED_CHATTER;
3237                     continue;
3238                 }
3239
3240                 if (looking_at(buf, &i, "--->")) {
3241                     loggedOn = TRUE;
3242                     continue;
3243                 }
3244
3245                 if (looking_at(buf, &i, "* shouts: ") ||
3246                     looking_at(buf, &i, "--> ")) {
3247                     if (appData.colorize) {
3248                         if (oldi > next_out) {
3249                             SendToPlayer(&buf[next_out], oldi - next_out);
3250                             next_out = oldi;
3251                         }
3252                         Colorize(ColorShout, FALSE);
3253                         curColor = ColorShout;
3254                     }
3255                     loggedOn = TRUE;
3256                     started = STARTED_CHATTER;
3257                     continue;
3258                 }
3259
3260                 if (looking_at( buf, &i, "Challenge:")) {
3261                     if (appData.colorize) {
3262                         if (oldi > next_out) {
3263                             SendToPlayer(&buf[next_out], oldi - next_out);
3264                             next_out = oldi;
3265                         }
3266                         Colorize(ColorChallenge, FALSE);
3267                         curColor = ColorChallenge;
3268                     }
3269                     loggedOn = TRUE;
3270                     continue;
3271                 }
3272
3273                 if (looking_at(buf, &i, "* offers you") ||
3274                     looking_at(buf, &i, "* offers to be") ||
3275                     looking_at(buf, &i, "* would like to") ||
3276                     looking_at(buf, &i, "* requests to") ||
3277                     looking_at(buf, &i, "Your opponent offers") ||
3278                     looking_at(buf, &i, "Your opponent requests")) {
3279
3280                     if (appData.colorize) {
3281                         if (oldi > next_out) {
3282                             SendToPlayer(&buf[next_out], oldi - next_out);
3283                             next_out = oldi;
3284                         }
3285                         Colorize(ColorRequest, FALSE);
3286                         curColor = ColorRequest;
3287                     }
3288                     continue;
3289                 }
3290
3291                 if (looking_at(buf, &i, "* (*) seeking")) {
3292                     if (appData.colorize) {
3293                         if (oldi > next_out) {
3294                             SendToPlayer(&buf[next_out], oldi - next_out);
3295                             next_out = oldi;
3296                         }
3297                         Colorize(ColorSeek, FALSE);
3298                         curColor = ColorSeek;
3299                     }
3300                     continue;
3301             }
3302
3303           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3304
3305             if (looking_at(buf, &i, "\\   ")) {
3306                 if (prevColor != ColorNormal) {
3307                     if (oldi > next_out) {
3308                         SendToPlayer(&buf[next_out], oldi - next_out);
3309                         next_out = oldi;
3310                     }
3311                     Colorize(prevColor, TRUE);
3312                     curColor = prevColor;
3313                 }
3314                 if (savingComment) {
3315                     parse_pos = i - oldi;
3316                     memcpy(parse, &buf[oldi], parse_pos);
3317                     parse[parse_pos] = NULLCHAR;
3318                     started = STARTED_COMMENT;
3319                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3320                         chattingPartner = savingComment - 3; // kludge to remember the box
3321                 } else {
3322                     started = STARTED_CHATTER;
3323                 }
3324                 continue;
3325             }
3326
3327             if (looking_at(buf, &i, "Black Strength :") ||
3328                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3329                 looking_at(buf, &i, "<10>") ||
3330                 looking_at(buf, &i, "#@#")) {
3331                 /* Wrong board style */
3332                 loggedOn = TRUE;
3333                 SendToICS(ics_prefix);
3334                 SendToICS("set style 12\n");
3335                 SendToICS(ics_prefix);
3336                 SendToICS("refresh\n");
3337                 continue;
3338             }
3339
3340             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3341                 ICSInitScript();
3342                 have_sent_ICS_logon = 1;
3343                 continue;
3344             }
3345
3346             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3347                 (looking_at(buf, &i, "\n<12> ") ||
3348                  looking_at(buf, &i, "<12> "))) {
3349                 loggedOn = TRUE;
3350                 if (oldi > next_out) {
3351                     SendToPlayer(&buf[next_out], oldi - next_out);
3352                 }
3353                 next_out = i;
3354                 started = STARTED_BOARD;
3355                 parse_pos = 0;
3356                 continue;
3357             }
3358
3359             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3360                 looking_at(buf, &i, "<b1> ")) {
3361                 if (oldi > next_out) {
3362                     SendToPlayer(&buf[next_out], oldi - next_out);
3363                 }
3364                 next_out = i;
3365                 started = STARTED_HOLDINGS;
3366                 parse_pos = 0;
3367                 continue;
3368             }
3369
3370             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3371                 loggedOn = TRUE;
3372                 /* Header for a move list -- first line */
3373
3374                 switch (ics_getting_history) {
3375                   case H_FALSE:
3376                     switch (gameMode) {
3377                       case IcsIdle:
3378                       case BeginningOfGame:
3379                         /* User typed "moves" or "oldmoves" while we
3380                            were idle.  Pretend we asked for these
3381                            moves and soak them up so user can step
3382                            through them and/or save them.
3383                            */
3384                         Reset(FALSE, TRUE);
3385                         gameMode = IcsObserving;
3386                         ModeHighlight();
3387                         ics_gamenum = -1;
3388                         ics_getting_history = H_GOT_UNREQ_HEADER;
3389                         break;
3390                       case EditGame: /*?*/
3391                       case EditPosition: /*?*/
3392                         /* Should above feature work in these modes too? */
3393                         /* For now it doesn't */
3394                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3395                         break;
3396                       default:
3397                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3398                         break;
3399                     }
3400                     break;
3401                   case H_REQUESTED:
3402                     /* Is this the right one? */
3403                     if (gameInfo.white && gameInfo.black &&
3404                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3405                         strcmp(gameInfo.black, star_match[2]) == 0) {
3406                         /* All is well */
3407                         ics_getting_history = H_GOT_REQ_HEADER;
3408                     }
3409                     break;
3410                   case H_GOT_REQ_HEADER:
3411                   case H_GOT_UNREQ_HEADER:
3412                   case H_GOT_UNWANTED_HEADER:
3413                   case H_GETTING_MOVES:
3414                     /* Should not happen */
3415                     DisplayError(_("Error gathering move list: two headers"), 0);
3416                     ics_getting_history = H_FALSE;
3417                     break;
3418                 }
3419
3420                 /* Save player ratings into gameInfo if needed */
3421                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3422                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3423                     (gameInfo.whiteRating == -1 ||
3424                      gameInfo.blackRating == -1)) {
3425
3426                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3427                     gameInfo.blackRating = string_to_rating(star_match[3]);
3428                     if (appData.debugMode)
3429                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3430                               gameInfo.whiteRating, gameInfo.blackRating);
3431                 }
3432                 continue;
3433             }
3434
3435             if (looking_at(buf, &i,
3436               "* * match, initial time: * minute*, increment: * second")) {
3437                 /* Header for a move list -- second line */
3438                 /* Initial board will follow if this is a wild game */
3439                 if (gameInfo.event != NULL) free(gameInfo.event);
3440                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3441                 gameInfo.event = StrSave(str);
3442                 /* [HGM] we switched variant. Translate boards if needed. */
3443                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3444                 continue;
3445             }
3446
3447             if (looking_at(buf, &i, "Move  ")) {
3448                 /* Beginning of a move list */
3449                 switch (ics_getting_history) {
3450                   case H_FALSE:
3451                     /* Normally should not happen */
3452                     /* Maybe user hit reset while we were parsing */
3453                     break;
3454                   case H_REQUESTED:
3455                     /* Happens if we are ignoring a move list that is not
3456                      * the one we just requested.  Common if the user
3457                      * tries to observe two games without turning off
3458                      * getMoveList */
3459                     break;
3460                   case H_GETTING_MOVES:
3461                     /* Should not happen */
3462                     DisplayError(_("Error gathering move list: nested"), 0);
3463                     ics_getting_history = H_FALSE;
3464                     break;
3465                   case H_GOT_REQ_HEADER:
3466                     ics_getting_history = H_GETTING_MOVES;
3467                     started = STARTED_MOVES;
3468                     parse_pos = 0;
3469                     if (oldi > next_out) {
3470                         SendToPlayer(&buf[next_out], oldi - next_out);
3471                     }
3472                     break;
3473                   case H_GOT_UNREQ_HEADER:
3474                     ics_getting_history = H_GETTING_MOVES;
3475                     started = STARTED_MOVES_NOHIDE;
3476                     parse_pos = 0;
3477                     break;
3478                   case H_GOT_UNWANTED_HEADER:
3479                     ics_getting_history = H_FALSE;
3480                     break;
3481                 }
3482                 continue;
3483             }
3484
3485             if (looking_at(buf, &i, "% ") ||
3486                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3487                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3488                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3489                     soughtPending = FALSE;
3490                     seekGraphUp = TRUE;
3491                     DrawSeekGraph();
3492                 }
3493                 if(suppressKibitz) next_out = i;
3494                 savingComment = FALSE;
3495                 suppressKibitz = 0;
3496                 switch (started) {
3497                   case STARTED_MOVES:
3498                   case STARTED_MOVES_NOHIDE:
3499                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3500                     parse[parse_pos + i - oldi] = NULLCHAR;
3501                     ParseGameHistory(parse);
3502 #if ZIPPY
3503                     if (appData.zippyPlay && first.initDone) {
3504                         FeedMovesToProgram(&first, forwardMostMove);
3505                         if (gameMode == IcsPlayingWhite) {
3506                             if (WhiteOnMove(forwardMostMove)) {
3507                                 if (first.sendTime) {
3508                                   if (first.useColors) {
3509                                     SendToProgram("black\n", &first);
3510                                   }
3511                                   SendTimeRemaining(&first, TRUE);
3512                                 }
3513                                 if (first.useColors) {
3514                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3515                                 }
3516                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3517                                 first.maybeThinking = TRUE;
3518                             } else {
3519                                 if (first.usePlayother) {
3520                                   if (first.sendTime) {
3521                                     SendTimeRemaining(&first, TRUE);
3522                                   }
3523                                   SendToProgram("playother\n", &first);
3524                                   firstMove = FALSE;
3525                                 } else {
3526                                   firstMove = TRUE;
3527                                 }
3528                             }
3529                         } else if (gameMode == IcsPlayingBlack) {
3530                             if (!WhiteOnMove(forwardMostMove)) {
3531                                 if (first.sendTime) {
3532                                   if (first.useColors) {
3533                                     SendToProgram("white\n", &first);
3534                                   }
3535                                   SendTimeRemaining(&first, FALSE);
3536                                 }
3537                                 if (first.useColors) {
3538                                   SendToProgram("black\n", &first);
3539                                 }
3540                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3541                                 first.maybeThinking = TRUE;
3542                             } else {
3543                                 if (first.usePlayother) {
3544                                   if (first.sendTime) {
3545                                     SendTimeRemaining(&first, FALSE);
3546                                   }
3547                                   SendToProgram("playother\n", &first);
3548                                   firstMove = FALSE;
3549                                 } else {
3550                                   firstMove = TRUE;
3551                                 }
3552                             }
3553                         }
3554                     }
3555 #endif
3556                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3557                         /* Moves came from oldmoves or moves command
3558                            while we weren't doing anything else.
3559                            */
3560                         currentMove = forwardMostMove;
3561                         ClearHighlights();/*!!could figure this out*/
3562                         flipView = appData.flipView;
3563                         DrawPosition(TRUE, boards[currentMove]);
3564                         DisplayBothClocks();
3565                         snprintf(str, MSG_SIZ, "%s vs. %s",
3566                                 gameInfo.white, gameInfo.black);
3567                         DisplayTitle(str);
3568                         gameMode = IcsIdle;
3569                     } else {
3570                         /* Moves were history of an active game */
3571                         if (gameInfo.resultDetails != NULL) {
3572                             free(gameInfo.resultDetails);
3573                             gameInfo.resultDetails = NULL;
3574                         }
3575                     }
3576                     HistorySet(parseList, backwardMostMove,
3577                                forwardMostMove, currentMove-1);
3578                     DisplayMove(currentMove - 1);
3579                     if (started == STARTED_MOVES) next_out = i;
3580                     started = STARTED_NONE;
3581                     ics_getting_history = H_FALSE;
3582                     break;
3583
3584                   case STARTED_OBSERVE:
3585                     started = STARTED_NONE;
3586                     SendToICS(ics_prefix);
3587                     SendToICS("refresh\n");
3588                     break;
3589
3590                   default:
3591                     break;
3592                 }
3593                 if(bookHit) { // [HGM] book: simulate book reply
3594                     static char bookMove[MSG_SIZ]; // a bit generous?
3595
3596                     programStats.nodes = programStats.depth = programStats.time =
3597                     programStats.score = programStats.got_only_move = 0;
3598                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3599
3600                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3601                     strcat(bookMove, bookHit);
3602                     HandleMachineMove(bookMove, &first);
3603                 }
3604                 continue;
3605             }
3606
3607             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3608                  started == STARTED_HOLDINGS ||
3609                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3610                 /* Accumulate characters in move list or board */
3611                 parse[parse_pos++] = buf[i];
3612             }
3613
3614             /* Start of game messages.  Mostly we detect start of game
3615                when the first board image arrives.  On some versions
3616                of the ICS, though, we need to do a "refresh" after starting
3617                to observe in order to get the current board right away. */
3618             if (looking_at(buf, &i, "Adding game * to observation list")) {
3619                 started = STARTED_OBSERVE;
3620                 continue;
3621             }
3622
3623             /* Handle auto-observe */
3624             if (appData.autoObserve &&
3625                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3626                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3627                 char *player;
3628                 /* Choose the player that was highlighted, if any. */
3629                 if (star_match[0][0] == '\033' ||
3630                     star_match[1][0] != '\033') {
3631                     player = star_match[0];
3632                 } else {
3633                     player = star_match[2];
3634                 }
3635                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3636                         ics_prefix, StripHighlightAndTitle(player));
3637                 SendToICS(str);
3638
3639                 /* Save ratings from notify string */
3640                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3641                 player1Rating = string_to_rating(star_match[1]);
3642                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3643                 player2Rating = string_to_rating(star_match[3]);
3644
3645                 if (appData.debugMode)
3646                   fprintf(debugFP,
3647                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3648                           player1Name, player1Rating,
3649                           player2Name, player2Rating);
3650
3651                 continue;
3652             }
3653
3654             /* Deal with automatic examine mode after a game,
3655                and with IcsObserving -> IcsExamining transition */
3656             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3657                 looking_at(buf, &i, "has made you an examiner of game *")) {
3658
3659                 int gamenum = atoi(star_match[0]);
3660                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3661                     gamenum == ics_gamenum) {
3662                     /* We were already playing or observing this game;
3663                        no need to refetch history */
3664                     gameMode = IcsExamining;
3665                     if (pausing) {
3666                         pauseExamForwardMostMove = forwardMostMove;
3667                     } else if (currentMove < forwardMostMove) {
3668                         ForwardInner(forwardMostMove);
3669                     }
3670                 } else {
3671                     /* I don't think this case really can happen */
3672                     SendToICS(ics_prefix);
3673                     SendToICS("refresh\n");
3674                 }
3675                 continue;
3676             }
3677
3678             /* Error messages */
3679 //          if (ics_user_moved) {
3680             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3681                 if (looking_at(buf, &i, "Illegal move") ||
3682                     looking_at(buf, &i, "Not a legal move") ||
3683                     looking_at(buf, &i, "Your king is in check") ||
3684                     looking_at(buf, &i, "It isn't your turn") ||
3685                     looking_at(buf, &i, "It is not your move")) {
3686                     /* Illegal move */
3687                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3688                         currentMove = forwardMostMove-1;
3689                         DisplayMove(currentMove - 1); /* before DMError */
3690                         DrawPosition(FALSE, boards[currentMove]);
3691                         SwitchClocks(forwardMostMove-1); // [HGM] race
3692                         DisplayBothClocks();
3693                     }
3694                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3695                     ics_user_moved = 0;
3696                     continue;
3697                 }
3698             }
3699
3700             if (looking_at(buf, &i, "still have time") ||
3701                 looking_at(buf, &i, "not out of time") ||
3702                 looking_at(buf, &i, "either player is out of time") ||
3703                 looking_at(buf, &i, "has timeseal; checking")) {
3704                 /* We must have called his flag a little too soon */
3705                 whiteFlag = blackFlag = FALSE;
3706                 continue;
3707             }
3708
3709             if (looking_at(buf, &i, "added * seconds to") ||
3710                 looking_at(buf, &i, "seconds were added to")) {
3711                 /* Update the clocks */
3712                 SendToICS(ics_prefix);
3713                 SendToICS("refresh\n");
3714                 continue;
3715             }
3716
3717             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3718                 ics_clock_paused = TRUE;
3719                 StopClocks();
3720                 continue;
3721             }
3722
3723             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3724                 ics_clock_paused = FALSE;
3725                 StartClocks();
3726                 continue;
3727             }
3728
3729             /* Grab player ratings from the Creating: message.
3730                Note we have to check for the special case when
3731                the ICS inserts things like [white] or [black]. */
3732             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3733                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3734                 /* star_matches:
3735                    0    player 1 name (not necessarily white)
3736                    1    player 1 rating
3737                    2    empty, white, or black (IGNORED)
3738                    3    player 2 name (not necessarily black)
3739                    4    player 2 rating
3740
3741                    The names/ratings are sorted out when the game
3742                    actually starts (below).
3743                 */
3744                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3745                 player1Rating = string_to_rating(star_match[1]);
3746                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3747                 player2Rating = string_to_rating(star_match[4]);
3748
3749                 if (appData.debugMode)
3750                   fprintf(debugFP,
3751                           "Ratings from 'Creating:' %s %d, %s %d\n",
3752                           player1Name, player1Rating,
3753                           player2Name, player2Rating);
3754
3755                 continue;
3756             }
3757
3758             /* Improved generic start/end-of-game messages */
3759             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3760                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3761                 /* If tkind == 0: */
3762                 /* star_match[0] is the game number */
3763                 /*           [1] is the white player's name */
3764                 /*           [2] is the black player's name */
3765                 /* For end-of-game: */
3766                 /*           [3] is the reason for the game end */
3767                 /*           [4] is a PGN end game-token, preceded by " " */
3768                 /* For start-of-game: */
3769                 /*           [3] begins with "Creating" or "Continuing" */
3770                 /*           [4] is " *" or empty (don't care). */
3771                 int gamenum = atoi(star_match[0]);
3772                 char *whitename, *blackname, *why, *endtoken;
3773                 ChessMove endtype = EndOfFile;
3774
3775                 if (tkind == 0) {
3776                   whitename = star_match[1];
3777                   blackname = star_match[2];
3778                   why = star_match[3];
3779                   endtoken = star_match[4];
3780                 } else {
3781                   whitename = star_match[1];
3782                   blackname = star_match[3];
3783                   why = star_match[5];
3784                   endtoken = star_match[6];
3785                 }
3786
3787                 /* Game start messages */
3788                 if (strncmp(why, "Creating ", 9) == 0 ||
3789                     strncmp(why, "Continuing ", 11) == 0) {
3790                     gs_gamenum = gamenum;
3791                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3792                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3793 #if ZIPPY
3794                     if (appData.zippyPlay) {
3795                         ZippyGameStart(whitename, blackname);
3796                     }
3797 #endif /*ZIPPY*/
3798                     partnerBoardValid = FALSE; // [HGM] bughouse
3799                     continue;
3800                 }
3801
3802                 /* Game end messages */
3803                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3804                     ics_gamenum != gamenum) {
3805                     continue;
3806                 }
3807                 while (endtoken[0] == ' ') endtoken++;
3808                 switch (endtoken[0]) {
3809                   case '*':
3810                   default:
3811                     endtype = GameUnfinished;
3812                     break;
3813                   case '0':
3814                     endtype = BlackWins;
3815                     break;
3816                   case '1':
3817                     if (endtoken[1] == '/')
3818                       endtype = GameIsDrawn;
3819                     else
3820                       endtype = WhiteWins;
3821                     break;
3822                 }
3823                 GameEnds(endtype, why, GE_ICS);
3824 #if ZIPPY
3825                 if (appData.zippyPlay && first.initDone) {
3826                     ZippyGameEnd(endtype, why);
3827                     if (first.pr == NULL) {
3828                       /* Start the next process early so that we'll
3829                          be ready for the next challenge */
3830                       StartChessProgram(&first);
3831                     }
3832                     /* Send "new" early, in case this command takes
3833                        a long time to finish, so that we'll be ready
3834                        for the next challenge. */
3835                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3836                     Reset(TRUE, TRUE);
3837                 }
3838 #endif /*ZIPPY*/
3839                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3840                 continue;
3841             }
3842
3843             if (looking_at(buf, &i, "Removing game * from observation") ||
3844                 looking_at(buf, &i, "no longer observing game *") ||
3845                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3846                 if (gameMode == IcsObserving &&
3847                     atoi(star_match[0]) == ics_gamenum)
3848                   {
3849                       /* icsEngineAnalyze */
3850                       if (appData.icsEngineAnalyze) {
3851                             ExitAnalyzeMode();
3852                             ModeHighlight();
3853                       }
3854                       StopClocks();
3855                       gameMode = IcsIdle;
3856                       ics_gamenum = -1;
3857                       ics_user_moved = FALSE;
3858                   }
3859                 continue;
3860             }
3861
3862             if (looking_at(buf, &i, "no longer examining game *")) {
3863                 if (gameMode == IcsExamining &&
3864                     atoi(star_match[0]) == ics_gamenum)
3865                   {
3866                       gameMode = IcsIdle;
3867                       ics_gamenum = -1;
3868                       ics_user_moved = FALSE;
3869                   }
3870                 continue;
3871             }
3872
3873             /* Advance leftover_start past any newlines we find,
3874                so only partial lines can get reparsed */
3875             if (looking_at(buf, &i, "\n")) {
3876                 prevColor = curColor;
3877                 if (curColor != ColorNormal) {
3878                     if (oldi > next_out) {
3879                         SendToPlayer(&buf[next_out], oldi - next_out);
3880                         next_out = oldi;
3881                     }
3882                     Colorize(ColorNormal, FALSE);
3883                     curColor = ColorNormal;
3884                 }
3885                 if (started == STARTED_BOARD) {
3886                     started = STARTED_NONE;
3887                     parse[parse_pos] = NULLCHAR;
3888                     ParseBoard12(parse);
3889                     ics_user_moved = 0;
3890
3891                     /* Send premove here */
3892                     if (appData.premove) {
3893                       char str[MSG_SIZ];
3894                       if (currentMove == 0 &&
3895                           gameMode == IcsPlayingWhite &&
3896                           appData.premoveWhite) {
3897                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3898                         if (appData.debugMode)
3899                           fprintf(debugFP, "Sending premove:\n");
3900                         SendToICS(str);
3901                       } else if (currentMove == 1 &&
3902                                  gameMode == IcsPlayingBlack &&
3903                                  appData.premoveBlack) {
3904                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3905                         if (appData.debugMode)
3906                           fprintf(debugFP, "Sending premove:\n");
3907                         SendToICS(str);
3908                       } else if (gotPremove) {
3909                         gotPremove = 0;
3910                         ClearPremoveHighlights();
3911                         if (appData.debugMode)
3912                           fprintf(debugFP, "Sending premove:\n");
3913                           UserMoveEvent(premoveFromX, premoveFromY,
3914                                         premoveToX, premoveToY,
3915                                         premovePromoChar);
3916                       }
3917                     }
3918
3919                     /* Usually suppress following prompt */
3920                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3921                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3922                         if (looking_at(buf, &i, "*% ")) {
3923                             savingComment = FALSE;
3924                             suppressKibitz = 0;
3925                         }
3926                     }
3927                     next_out = i;
3928                 } else if (started == STARTED_HOLDINGS) {
3929                     int gamenum;
3930                     char new_piece[MSG_SIZ];
3931                     started = STARTED_NONE;
3932                     parse[parse_pos] = NULLCHAR;
3933                     if (appData.debugMode)
3934                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3935                                                         parse, currentMove);
3936                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3937                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3938                         if (gameInfo.variant == VariantNormal) {
3939                           /* [HGM] We seem to switch variant during a game!
3940                            * Presumably no holdings were displayed, so we have
3941                            * to move the position two files to the right to
3942                            * create room for them!
3943                            */
3944                           VariantClass newVariant;
3945                           switch(gameInfo.boardWidth) { // base guess on board width
3946                                 case 9:  newVariant = VariantShogi; break;
3947                                 case 10: newVariant = VariantGreat; break;
3948                                 default: newVariant = VariantCrazyhouse; break;
3949                           }
3950                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3951                           /* Get a move list just to see the header, which
3952                              will tell us whether this is really bug or zh */
3953                           if (ics_getting_history == H_FALSE) {
3954                             ics_getting_history = H_REQUESTED;
3955                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3956                             SendToICS(str);
3957                           }
3958                         }
3959                         new_piece[0] = NULLCHAR;
3960                         sscanf(parse, "game %d white [%s black [%s <- %s",
3961                                &gamenum, white_holding, black_holding,
3962                                new_piece);
3963                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3964                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3965                         /* [HGM] copy holdings to board holdings area */
3966                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3967                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3968                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3969 #if ZIPPY
3970                         if (appData.zippyPlay && first.initDone) {
3971                             ZippyHoldings(white_holding, black_holding,
3972                                           new_piece);
3973                         }
3974 #endif /*ZIPPY*/
3975                         if (tinyLayout || smallLayout) {
3976                             char wh[16], bh[16];
3977                             PackHolding(wh, white_holding);
3978                             PackHolding(bh, black_holding);
3979                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3980                                     gameInfo.white, gameInfo.black);
3981                         } else {
3982                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3983                                     gameInfo.white, white_holding,
3984                                     gameInfo.black, black_holding);
3985                         }
3986                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3987                         DrawPosition(FALSE, boards[currentMove]);
3988                         DisplayTitle(str);
3989                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3990                         sscanf(parse, "game %d white [%s black [%s <- %s",
3991                                &gamenum, white_holding, black_holding,
3992                                new_piece);
3993                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3994                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3995                         /* [HGM] copy holdings to partner-board holdings area */
3996                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3997                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3998                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3999                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4000                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4001                       }
4002                     }
4003                     /* Suppress following prompt */
4004                     if (looking_at(buf, &i, "*% ")) {
4005                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4006                         savingComment = FALSE;
4007                         suppressKibitz = 0;
4008                     }
4009                     next_out = i;
4010                 }
4011                 continue;
4012             }
4013
4014             i++;                /* skip unparsed character and loop back */
4015         }
4016
4017         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4018 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4019 //          SendToPlayer(&buf[next_out], i - next_out);
4020             started != STARTED_HOLDINGS && leftover_start > next_out) {
4021             SendToPlayer(&buf[next_out], leftover_start - next_out);
4022             next_out = i;
4023         }
4024
4025         leftover_len = buf_len - leftover_start;
4026         /* if buffer ends with something we couldn't parse,
4027            reparse it after appending the next read */
4028
4029     } else if (count == 0) {
4030         RemoveInputSource(isr);
4031         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4032     } else {
4033         DisplayFatalError(_("Error reading from ICS"), error, 1);
4034     }
4035 }
4036
4037
4038 /* Board style 12 looks like this:
4039
4040    <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
4041
4042  * The "<12> " is stripped before it gets to this routine.  The two
4043  * trailing 0's (flip state and clock ticking) are later addition, and
4044  * some chess servers may not have them, or may have only the first.
4045  * Additional trailing fields may be added in the future.
4046  */
4047
4048 #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"
4049
4050 #define RELATION_OBSERVING_PLAYED    0
4051 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4052 #define RELATION_PLAYING_MYMOVE      1
4053 #define RELATION_PLAYING_NOTMYMOVE  -1
4054 #define RELATION_EXAMINING           2
4055 #define RELATION_ISOLATED_BOARD     -3
4056 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4057
4058 void
4059 ParseBoard12(string)
4060      char *string;
4061 {
4062     GameMode newGameMode;
4063     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4064     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4065     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4066     char to_play, board_chars[200];
4067     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4068     char black[32], white[32];
4069     Board board;
4070     int prevMove = currentMove;
4071     int ticking = 2;
4072     ChessMove moveType;
4073     int fromX, fromY, toX, toY;
4074     char promoChar;
4075     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4076     char *bookHit = NULL; // [HGM] book
4077     Boolean weird = FALSE, reqFlag = FALSE;
4078
4079     fromX = fromY = toX = toY = -1;
4080
4081     newGame = FALSE;
4082
4083     if (appData.debugMode)
4084       fprintf(debugFP, _("Parsing board: %s\n"), string);
4085
4086     move_str[0] = NULLCHAR;
4087     elapsed_time[0] = NULLCHAR;
4088     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4089         int  i = 0, j;
4090         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4091             if(string[i] == ' ') { ranks++; files = 0; }
4092             else files++;
4093             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4094             i++;
4095         }
4096         for(j = 0; j <i; j++) board_chars[j] = string[j];
4097         board_chars[i] = '\0';
4098         string += i + 1;
4099     }
4100     n = sscanf(string, PATTERN, &to_play, &double_push,
4101                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4102                &gamenum, white, black, &relation, &basetime, &increment,
4103                &white_stren, &black_stren, &white_time, &black_time,
4104                &moveNum, str, elapsed_time, move_str, &ics_flip,
4105                &ticking);
4106
4107     if (n < 21) {
4108         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4109         DisplayError(str, 0);
4110         return;
4111     }
4112
4113     /* Convert the move number to internal form */
4114     moveNum = (moveNum - 1) * 2;
4115     if (to_play == 'B') moveNum++;
4116     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4117       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4118                         0, 1);
4119       return;
4120     }
4121
4122     switch (relation) {
4123       case RELATION_OBSERVING_PLAYED:
4124       case RELATION_OBSERVING_STATIC:
4125         if (gamenum == -1) {
4126             /* Old ICC buglet */
4127             relation = RELATION_OBSERVING_STATIC;
4128         }
4129         newGameMode = IcsObserving;
4130         break;
4131       case RELATION_PLAYING_MYMOVE:
4132       case RELATION_PLAYING_NOTMYMOVE:
4133         newGameMode =
4134           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4135             IcsPlayingWhite : IcsPlayingBlack;
4136         break;
4137       case RELATION_EXAMINING:
4138         newGameMode = IcsExamining;
4139         break;
4140       case RELATION_ISOLATED_BOARD:
4141       default:
4142         /* Just display this board.  If user was doing something else,
4143            we will forget about it until the next board comes. */
4144         newGameMode = IcsIdle;
4145         break;
4146       case RELATION_STARTING_POSITION:
4147         newGameMode = gameMode;
4148         break;
4149     }
4150
4151     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4152          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4153       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4154       char *toSqr;
4155       for (k = 0; k < ranks; k++) {
4156         for (j = 0; j < files; j++)
4157           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4158         if(gameInfo.holdingsWidth > 1) {
4159              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4160              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4161         }
4162       }
4163       CopyBoard(partnerBoard, board);
4164       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4165         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4166         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4167       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4168       if(toSqr = strchr(str, '-')) {
4169         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4170         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4171       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4172       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4173       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4174       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4175       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4176       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4177                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4178       DisplayMessage(partnerStatus, "");
4179         partnerBoardValid = TRUE;
4180       return;
4181     }
4182
4183     /* Modify behavior for initial board display on move listing
4184        of wild games.
4185        */
4186     switch (ics_getting_history) {
4187       case H_FALSE:
4188       case H_REQUESTED:
4189         break;
4190       case H_GOT_REQ_HEADER:
4191       case H_GOT_UNREQ_HEADER:
4192         /* This is the initial position of the current game */
4193         gamenum = ics_gamenum;
4194         moveNum = 0;            /* old ICS bug workaround */
4195         if (to_play == 'B') {
4196           startedFromSetupPosition = TRUE;
4197           blackPlaysFirst = TRUE;
4198           moveNum = 1;
4199           if (forwardMostMove == 0) forwardMostMove = 1;
4200           if (backwardMostMove == 0) backwardMostMove = 1;
4201           if (currentMove == 0) currentMove = 1;
4202         }
4203         newGameMode = gameMode;
4204         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4205         break;
4206       case H_GOT_UNWANTED_HEADER:
4207         /* This is an initial board that we don't want */
4208         return;
4209       case H_GETTING_MOVES:
4210         /* Should not happen */
4211         DisplayError(_("Error gathering move list: extra board"), 0);
4212         ics_getting_history = H_FALSE;
4213         return;
4214     }
4215
4216    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4217                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4218      /* [HGM] We seem to have switched variant unexpectedly
4219       * Try to guess new variant from board size
4220       */
4221           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4222           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4223           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4224           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4225           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4226           if(!weird) newVariant = VariantNormal;
4227           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4228           /* Get a move list just to see the header, which
4229              will tell us whether this is really bug or zh */
4230           if (ics_getting_history == H_FALSE) {
4231             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4232             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4233             SendToICS(str);
4234           }
4235     }
4236
4237     /* Take action if this is the first board of a new game, or of a
4238        different game than is currently being displayed.  */
4239     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4240         relation == RELATION_ISOLATED_BOARD) {
4241
4242         /* Forget the old game and get the history (if any) of the new one */
4243         if (gameMode != BeginningOfGame) {
4244           Reset(TRUE, TRUE);
4245         }
4246         newGame = TRUE;
4247         if (appData.autoRaiseBoard) BoardToTop();
4248         prevMove = -3;
4249         if (gamenum == -1) {
4250             newGameMode = IcsIdle;
4251         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4252                    appData.getMoveList && !reqFlag) {
4253             /* Need to get game history */
4254             ics_getting_history = H_REQUESTED;
4255             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4256             SendToICS(str);
4257         }
4258
4259         /* Initially flip the board to have black on the bottom if playing
4260            black or if the ICS flip flag is set, but let the user change
4261            it with the Flip View button. */
4262         flipView = appData.autoFlipView ?
4263           (newGameMode == IcsPlayingBlack) || ics_flip :
4264           appData.flipView;
4265
4266         /* Done with values from previous mode; copy in new ones */
4267         gameMode = newGameMode;
4268         ModeHighlight();
4269         ics_gamenum = gamenum;
4270         if (gamenum == gs_gamenum) {
4271             int klen = strlen(gs_kind);
4272             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4273             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4274             gameInfo.event = StrSave(str);
4275         } else {
4276             gameInfo.event = StrSave("ICS game");
4277         }
4278         gameInfo.site = StrSave(appData.icsHost);
4279         gameInfo.date = PGNDate();
4280         gameInfo.round = StrSave("-");
4281         gameInfo.white = StrSave(white);
4282         gameInfo.black = StrSave(black);
4283         timeControl = basetime * 60 * 1000;
4284         timeControl_2 = 0;
4285         timeIncrement = increment * 1000;
4286         movesPerSession = 0;
4287         gameInfo.timeControl = TimeControlTagValue();
4288         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4289   if (appData.debugMode) {
4290     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4291     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4292     setbuf(debugFP, NULL);
4293   }
4294
4295         gameInfo.outOfBook = NULL;
4296
4297         /* Do we have the ratings? */
4298         if (strcmp(player1Name, white) == 0 &&
4299             strcmp(player2Name, black) == 0) {
4300             if (appData.debugMode)
4301               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4302                       player1Rating, player2Rating);
4303             gameInfo.whiteRating = player1Rating;
4304             gameInfo.blackRating = player2Rating;
4305         } else if (strcmp(player2Name, white) == 0 &&
4306                    strcmp(player1Name, black) == 0) {
4307             if (appData.debugMode)
4308               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4309                       player2Rating, player1Rating);
4310             gameInfo.whiteRating = player2Rating;
4311             gameInfo.blackRating = player1Rating;
4312         }
4313         player1Name[0] = player2Name[0] = NULLCHAR;
4314
4315         /* Silence shouts if requested */
4316         if (appData.quietPlay &&
4317             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4318             SendToICS(ics_prefix);
4319             SendToICS("set shout 0\n");
4320         }
4321     }
4322
4323     /* Deal with midgame name changes */
4324     if (!newGame) {
4325         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4326             if (gameInfo.white) free(gameInfo.white);
4327             gameInfo.white = StrSave(white);
4328         }
4329         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4330             if (gameInfo.black) free(gameInfo.black);
4331             gameInfo.black = StrSave(black);
4332         }
4333     }
4334
4335     /* Throw away game result if anything actually changes in examine mode */
4336     if (gameMode == IcsExamining && !newGame) {
4337         gameInfo.result = GameUnfinished;
4338         if (gameInfo.resultDetails != NULL) {
4339             free(gameInfo.resultDetails);
4340             gameInfo.resultDetails = NULL;
4341         }
4342     }
4343
4344     /* In pausing && IcsExamining mode, we ignore boards coming
4345        in if they are in a different variation than we are. */
4346     if (pauseExamInvalid) return;
4347     if (pausing && gameMode == IcsExamining) {
4348         if (moveNum <= pauseExamForwardMostMove) {
4349             pauseExamInvalid = TRUE;
4350             forwardMostMove = pauseExamForwardMostMove;
4351             return;
4352         }
4353     }
4354
4355   if (appData.debugMode) {
4356     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4357   }
4358     /* Parse the board */
4359     for (k = 0; k < ranks; k++) {
4360       for (j = 0; j < files; j++)
4361         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4362       if(gameInfo.holdingsWidth > 1) {
4363            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4364            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4365       }
4366     }
4367     CopyBoard(boards[moveNum], board);
4368     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4369     if (moveNum == 0) {
4370         startedFromSetupPosition =
4371           !CompareBoards(board, initialPosition);
4372         if(startedFromSetupPosition)
4373             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4374     }
4375
4376     /* [HGM] Set castling rights. Take the outermost Rooks,
4377        to make it also work for FRC opening positions. Note that board12
4378        is really defective for later FRC positions, as it has no way to
4379        indicate which Rook can castle if they are on the same side of King.
4380        For the initial position we grant rights to the outermost Rooks,
4381        and remember thos rights, and we then copy them on positions
4382        later in an FRC game. This means WB might not recognize castlings with
4383        Rooks that have moved back to their original position as illegal,
4384        but in ICS mode that is not its job anyway.
4385     */
4386     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4387     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4388
4389         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4390             if(board[0][i] == WhiteRook) j = i;
4391         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4392         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4393             if(board[0][i] == WhiteRook) j = i;
4394         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4395         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4396             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4397         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4398         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4399             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4400         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4401
4402         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4403         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4404             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4405         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4406             if(board[BOARD_HEIGHT-1][k] == bKing)
4407                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4408         if(gameInfo.variant == VariantTwoKings) {
4409             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4410             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4411             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4412         }
4413     } else { int r;
4414         r = boards[moveNum][CASTLING][0] = initialRights[0];
4415         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4416         r = boards[moveNum][CASTLING][1] = initialRights[1];
4417         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4418         r = boards[moveNum][CASTLING][3] = initialRights[3];
4419         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4420         r = boards[moveNum][CASTLING][4] = initialRights[4];
4421         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4422         /* wildcastle kludge: always assume King has rights */
4423         r = boards[moveNum][CASTLING][2] = initialRights[2];
4424         r = boards[moveNum][CASTLING][5] = initialRights[5];
4425     }
4426     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4427     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4428
4429
4430     if (ics_getting_history == H_GOT_REQ_HEADER ||
4431         ics_getting_history == H_GOT_UNREQ_HEADER) {
4432         /* This was an initial position from a move list, not
4433            the current position */
4434         return;
4435     }
4436
4437     /* Update currentMove and known move number limits */
4438     newMove = newGame || moveNum > forwardMostMove;
4439
4440     if (newGame) {
4441         forwardMostMove = backwardMostMove = currentMove = moveNum;
4442         if (gameMode == IcsExamining && moveNum == 0) {
4443           /* Workaround for ICS limitation: we are not told the wild
4444              type when starting to examine a game.  But if we ask for
4445              the move list, the move list header will tell us */
4446             ics_getting_history = H_REQUESTED;
4447             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4448             SendToICS(str);
4449         }
4450     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4451                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4452 #if ZIPPY
4453         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4454         /* [HGM] applied this also to an engine that is silently watching        */
4455         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4456             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4457             gameInfo.variant == currentlyInitializedVariant) {
4458           takeback = forwardMostMove - moveNum;
4459           for (i = 0; i < takeback; i++) {
4460             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4461             SendToProgram("undo\n", &first);
4462           }
4463         }
4464 #endif
4465
4466         forwardMostMove = moveNum;
4467         if (!pausing || currentMove > forwardMostMove)
4468           currentMove = forwardMostMove;
4469     } else {
4470         /* New part of history that is not contiguous with old part */
4471         if (pausing && gameMode == IcsExamining) {
4472             pauseExamInvalid = TRUE;
4473             forwardMostMove = pauseExamForwardMostMove;
4474             return;
4475         }
4476         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4477 #if ZIPPY
4478             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4479                 // [HGM] when we will receive the move list we now request, it will be
4480                 // fed to the engine from the first move on. So if the engine is not
4481                 // in the initial position now, bring it there.
4482                 InitChessProgram(&first, 0);
4483             }
4484 #endif
4485             ics_getting_history = H_REQUESTED;
4486             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4487             SendToICS(str);
4488         }
4489         forwardMostMove = backwardMostMove = currentMove = moveNum;
4490     }
4491
4492     /* Update the clocks */
4493     if (strchr(elapsed_time, '.')) {
4494       /* Time is in ms */
4495       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4496       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4497     } else {
4498       /* Time is in seconds */
4499       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4500       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4501     }
4502
4503
4504 #if ZIPPY
4505     if (appData.zippyPlay && newGame &&
4506         gameMode != IcsObserving && gameMode != IcsIdle &&
4507         gameMode != IcsExamining)
4508       ZippyFirstBoard(moveNum, basetime, increment);
4509 #endif
4510
4511     /* Put the move on the move list, first converting
4512        to canonical algebraic form. */
4513     if (moveNum > 0) {
4514   if (appData.debugMode) {
4515     if (appData.debugMode) { int f = forwardMostMove;
4516         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4517                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4518                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4519     }
4520     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4521     fprintf(debugFP, "moveNum = %d\n", moveNum);
4522     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4523     setbuf(debugFP, NULL);
4524   }
4525         if (moveNum <= backwardMostMove) {
4526             /* We don't know what the board looked like before
4527                this move.  Punt. */
4528           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4529             strcat(parseList[moveNum - 1], " ");
4530             strcat(parseList[moveNum - 1], elapsed_time);
4531             moveList[moveNum - 1][0] = NULLCHAR;
4532         } else if (strcmp(move_str, "none") == 0) {
4533             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4534             /* Again, we don't know what the board looked like;
4535                this is really the start of the game. */
4536             parseList[moveNum - 1][0] = NULLCHAR;
4537             moveList[moveNum - 1][0] = NULLCHAR;
4538             backwardMostMove = moveNum;
4539             startedFromSetupPosition = TRUE;
4540             fromX = fromY = toX = toY = -1;
4541         } else {
4542           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4543           //                 So we parse the long-algebraic move string in stead of the SAN move
4544           int valid; char buf[MSG_SIZ], *prom;
4545
4546           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4547                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4548           // str looks something like "Q/a1-a2"; kill the slash
4549           if(str[1] == '/')
4550             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4551           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4552           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4553                 strcat(buf, prom); // long move lacks promo specification!
4554           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4555                 if(appData.debugMode)
4556                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4557                 safeStrCpy(move_str, buf, MSG_SIZ);
4558           }
4559           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4560                                 &fromX, &fromY, &toX, &toY, &promoChar)
4561                || ParseOneMove(buf, moveNum - 1, &moveType,
4562                                 &fromX, &fromY, &toX, &toY, &promoChar);
4563           // end of long SAN patch
4564           if (valid) {
4565             (void) CoordsToAlgebraic(boards[moveNum - 1],
4566                                      PosFlags(moveNum - 1),
4567                                      fromY, fromX, toY, toX, promoChar,
4568                                      parseList[moveNum-1]);
4569             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4570               case MT_NONE:
4571               case MT_STALEMATE:
4572               default:
4573                 break;
4574               case MT_CHECK:
4575                 if(gameInfo.variant != VariantShogi)
4576                     strcat(parseList[moveNum - 1], "+");
4577                 break;
4578               case MT_CHECKMATE:
4579               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4580                 strcat(parseList[moveNum - 1], "#");
4581                 break;
4582             }
4583             strcat(parseList[moveNum - 1], " ");
4584             strcat(parseList[moveNum - 1], elapsed_time);
4585             /* currentMoveString is set as a side-effect of ParseOneMove */
4586             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4587             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4588             strcat(moveList[moveNum - 1], "\n");
4589
4590             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4591                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4592               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4593                 ChessSquare old, new = boards[moveNum][k][j];
4594                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4595                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4596                   if(old == new) continue;
4597                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4598                   else if(new == WhiteWazir || new == BlackWazir) {
4599                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4600                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4601                       else boards[moveNum][k][j] = old; // preserve type of Gold
4602                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4603                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4604               }
4605           } else {
4606             /* Move from ICS was illegal!?  Punt. */
4607             if (appData.debugMode) {
4608               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4609               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4610             }
4611             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4612             strcat(parseList[moveNum - 1], " ");
4613             strcat(parseList[moveNum - 1], elapsed_time);
4614             moveList[moveNum - 1][0] = NULLCHAR;
4615             fromX = fromY = toX = toY = -1;
4616           }
4617         }
4618   if (appData.debugMode) {
4619     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4620     setbuf(debugFP, NULL);
4621   }
4622
4623 #if ZIPPY
4624         /* Send move to chess program (BEFORE animating it). */
4625         if (appData.zippyPlay && !newGame && newMove &&
4626            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4627
4628             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4629                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4630                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4631                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4632                             move_str);
4633                     DisplayError(str, 0);
4634                 } else {
4635                     if (first.sendTime) {
4636                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4637                     }
4638                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4639                     if (firstMove && !bookHit) {
4640                         firstMove = FALSE;
4641                         if (first.useColors) {
4642                           SendToProgram(gameMode == IcsPlayingWhite ?
4643                                         "white\ngo\n" :
4644                                         "black\ngo\n", &first);
4645                         } else {
4646                           SendToProgram("go\n", &first);
4647                         }
4648                         first.maybeThinking = TRUE;
4649                     }
4650                 }
4651             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4652               if (moveList[moveNum - 1][0] == NULLCHAR) {
4653                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4654                 DisplayError(str, 0);
4655               } else {
4656                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4657                 SendMoveToProgram(moveNum - 1, &first);
4658               }
4659             }
4660         }
4661 #endif
4662     }
4663
4664     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4665         /* If move comes from a remote source, animate it.  If it
4666            isn't remote, it will have already been animated. */
4667         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4668             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4669         }
4670         if (!pausing && appData.highlightLastMove) {
4671             SetHighlights(fromX, fromY, toX, toY);
4672         }
4673     }
4674
4675     /* Start the clocks */
4676     whiteFlag = blackFlag = FALSE;
4677     appData.clockMode = !(basetime == 0 && increment == 0);
4678     if (ticking == 0) {
4679       ics_clock_paused = TRUE;
4680       StopClocks();
4681     } else if (ticking == 1) {
4682       ics_clock_paused = FALSE;
4683     }
4684     if (gameMode == IcsIdle ||
4685         relation == RELATION_OBSERVING_STATIC ||
4686         relation == RELATION_EXAMINING ||
4687         ics_clock_paused)
4688       DisplayBothClocks();
4689     else
4690       StartClocks();
4691
4692     /* Display opponents and material strengths */
4693     if (gameInfo.variant != VariantBughouse &&
4694         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4695         if (tinyLayout || smallLayout) {
4696             if(gameInfo.variant == VariantNormal)
4697               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4698                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4699                     basetime, increment);
4700             else
4701               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4702                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4703                     basetime, increment, (int) gameInfo.variant);
4704         } else {
4705             if(gameInfo.variant == VariantNormal)
4706               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4707                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4708                     basetime, increment);
4709             else
4710               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4711                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4712                     basetime, increment, VariantName(gameInfo.variant));
4713         }
4714         DisplayTitle(str);
4715   if (appData.debugMode) {
4716     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4717   }
4718     }
4719
4720
4721     /* Display the board */
4722     if (!pausing && !appData.noGUI) {
4723
4724       if (appData.premove)
4725           if (!gotPremove ||
4726              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4727              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4728               ClearPremoveHighlights();
4729
4730       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4731         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4732       DrawPosition(j, boards[currentMove]);
4733
4734       DisplayMove(moveNum - 1);
4735       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4736             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4737               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4738         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4739       }
4740     }
4741
4742     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4743 #if ZIPPY
4744     if(bookHit) { // [HGM] book: simulate book reply
4745         static char bookMove[MSG_SIZ]; // a bit generous?
4746
4747         programStats.nodes = programStats.depth = programStats.time =
4748         programStats.score = programStats.got_only_move = 0;
4749         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4750
4751         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4752         strcat(bookMove, bookHit);
4753         HandleMachineMove(bookMove, &first);
4754     }
4755 #endif
4756 }
4757
4758 void
4759 GetMoveListEvent()
4760 {
4761     char buf[MSG_SIZ];
4762     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4763         ics_getting_history = H_REQUESTED;
4764         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4765         SendToICS(buf);
4766     }
4767 }
4768
4769 void
4770 AnalysisPeriodicEvent(force)
4771      int force;
4772 {
4773     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4774          && !force) || !appData.periodicUpdates)
4775       return;
4776
4777     /* Send . command to Crafty to collect stats */
4778     SendToProgram(".\n", &first);
4779
4780     /* Don't send another until we get a response (this makes
4781        us stop sending to old Crafty's which don't understand
4782        the "." command (sending illegal cmds resets node count & time,
4783        which looks bad)) */
4784     programStats.ok_to_send = 0;
4785 }
4786
4787 void ics_update_width(new_width)
4788         int new_width;
4789 {
4790         ics_printf("set width %d\n", new_width);
4791 }
4792
4793 void
4794 SendMoveToProgram(moveNum, cps)
4795      int moveNum;
4796      ChessProgramState *cps;
4797 {
4798     char buf[MSG_SIZ];
4799
4800     if (cps->useUsermove) {
4801       SendToProgram("usermove ", cps);
4802     }
4803     if (cps->useSAN) {
4804       char *space;
4805       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4806         int len = space - parseList[moveNum];
4807         memcpy(buf, parseList[moveNum], len);
4808         buf[len++] = '\n';
4809         buf[len] = NULLCHAR;
4810       } else {
4811         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4812       }
4813       SendToProgram(buf, cps);
4814     } else {
4815       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4816         AlphaRank(moveList[moveNum], 4);
4817         SendToProgram(moveList[moveNum], cps);
4818         AlphaRank(moveList[moveNum], 4); // and back
4819       } else
4820       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4821        * the engine. It would be nice to have a better way to identify castle
4822        * moves here. */
4823       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4824                                                                          && cps->useOOCastle) {
4825         int fromX = moveList[moveNum][0] - AAA;
4826         int fromY = moveList[moveNum][1] - ONE;
4827         int toX = moveList[moveNum][2] - AAA;
4828         int toY = moveList[moveNum][3] - ONE;
4829         if((boards[moveNum][fromY][fromX] == WhiteKing
4830             && boards[moveNum][toY][toX] == WhiteRook)
4831            || (boards[moveNum][fromY][fromX] == BlackKing
4832                && boards[moveNum][toY][toX] == BlackRook)) {
4833           if(toX > fromX) SendToProgram("O-O\n", cps);
4834           else SendToProgram("O-O-O\n", cps);
4835         }
4836         else SendToProgram(moveList[moveNum], cps);
4837       }
4838       else SendToProgram(moveList[moveNum], cps);
4839       /* End of additions by Tord */
4840     }
4841
4842     /* [HGM] setting up the opening has brought engine in force mode! */
4843     /*       Send 'go' if we are in a mode where machine should play. */
4844     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4845         (gameMode == TwoMachinesPlay   ||
4846 #if ZIPPY
4847          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4848 #endif
4849          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4850         SendToProgram("go\n", cps);
4851   if (appData.debugMode) {
4852     fprintf(debugFP, "(extra)\n");
4853   }
4854     }
4855     setboardSpoiledMachineBlack = 0;
4856 }
4857
4858 void
4859 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4860      ChessMove moveType;
4861      int fromX, fromY, toX, toY;
4862      char promoChar;
4863 {
4864     char user_move[MSG_SIZ];
4865
4866     switch (moveType) {
4867       default:
4868         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4869                 (int)moveType, fromX, fromY, toX, toY);
4870         DisplayError(user_move + strlen("say "), 0);
4871         break;
4872       case WhiteKingSideCastle:
4873       case BlackKingSideCastle:
4874       case WhiteQueenSideCastleWild:
4875       case BlackQueenSideCastleWild:
4876       /* PUSH Fabien */
4877       case WhiteHSideCastleFR:
4878       case BlackHSideCastleFR:
4879       /* POP Fabien */
4880         snprintf(user_move, MSG_SIZ, "o-o\n");
4881         break;
4882       case WhiteQueenSideCastle:
4883       case BlackQueenSideCastle:
4884       case WhiteKingSideCastleWild:
4885       case BlackKingSideCastleWild:
4886       /* PUSH Fabien */
4887       case WhiteASideCastleFR:
4888       case BlackASideCastleFR:
4889       /* POP Fabien */
4890         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4891         break;
4892       case WhiteNonPromotion:
4893       case BlackNonPromotion:
4894         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4895         break;
4896       case WhitePromotion:
4897       case BlackPromotion:
4898         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4899           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4900                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4901                 PieceToChar(WhiteFerz));
4902         else if(gameInfo.variant == VariantGreat)
4903           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4904                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4905                 PieceToChar(WhiteMan));
4906         else
4907           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4908                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4909                 promoChar);
4910         break;
4911       case WhiteDrop:
4912       case BlackDrop:
4913       drop:
4914         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4915                  ToUpper(PieceToChar((ChessSquare) fromX)),
4916                  AAA + toX, ONE + toY);
4917         break;
4918       case IllegalMove:  /* could be a variant we don't quite understand */
4919         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4920       case NormalMove:
4921       case WhiteCapturesEnPassant:
4922       case BlackCapturesEnPassant:
4923         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4924                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4925         break;
4926     }
4927     SendToICS(user_move);
4928     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4929         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4930 }
4931
4932 void
4933 UploadGameEvent()
4934 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4935     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4936     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4937     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4938         DisplayError("You cannot do this while you are playing or observing", 0);
4939         return;
4940     }
4941     if(gameMode != IcsExamining) { // is this ever not the case?
4942         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4943
4944         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4945           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4946         } else { // on FICS we must first go to general examine mode
4947           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4948         }
4949         if(gameInfo.variant != VariantNormal) {
4950             // try figure out wild number, as xboard names are not always valid on ICS
4951             for(i=1; i<=36; i++) {
4952               snprintf(buf, MSG_SIZ, "wild/%d", i);
4953                 if(StringToVariant(buf) == gameInfo.variant) break;
4954             }
4955             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4956             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4957             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4958         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4959         SendToICS(ics_prefix);
4960         SendToICS(buf);
4961         if(startedFromSetupPosition || backwardMostMove != 0) {
4962           fen = PositionToFEN(backwardMostMove, NULL);
4963           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4964             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4965             SendToICS(buf);
4966           } else { // FICS: everything has to set by separate bsetup commands
4967             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4968             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4969             SendToICS(buf);
4970             if(!WhiteOnMove(backwardMostMove)) {
4971                 SendToICS("bsetup tomove black\n");
4972             }
4973             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4974             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4975             SendToICS(buf);
4976             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4977             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4978             SendToICS(buf);
4979             i = boards[backwardMostMove][EP_STATUS];
4980             if(i >= 0) { // set e.p.
4981               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4982                 SendToICS(buf);
4983             }
4984             bsetup++;
4985           }
4986         }
4987       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4988             SendToICS("bsetup done\n"); // switch to normal examining.
4989     }
4990     for(i = backwardMostMove; i<last; i++) {
4991         char buf[20];
4992         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4993         SendToICS(buf);
4994     }
4995     SendToICS(ics_prefix);
4996     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4997 }
4998
4999 void
5000 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5001      int rf, ff, rt, ft;
5002      char promoChar;
5003      char move[7];
5004 {
5005     if (rf == DROP_RANK) {
5006       sprintf(move, "%c@%c%c\n",
5007                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5008     } else {
5009         if (promoChar == 'x' || promoChar == NULLCHAR) {
5010           sprintf(move, "%c%c%c%c\n",
5011                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5012         } else {
5013             sprintf(move, "%c%c%c%c%c\n",
5014                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5015         }
5016     }
5017 }
5018
5019 void
5020 ProcessICSInitScript(f)
5021      FILE *f;
5022 {
5023     char buf[MSG_SIZ];
5024
5025     while (fgets(buf, MSG_SIZ, f)) {
5026         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5027     }
5028
5029     fclose(f);
5030 }
5031
5032
5033 static int lastX, lastY, selectFlag, dragging;
5034
5035 void
5036 Sweep(int step)
5037 {
5038     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5039     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5040     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5041     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5042     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5043     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5044     do {
5045         promoSweep -= step;
5046         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5047         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5048         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5049         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5050         if(!step) step = 1;
5051     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5052             appData.testLegality && (promoSweep == king ||
5053             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5054     ChangeDragPiece(promoSweep);
5055 }
5056
5057 int PromoScroll(int x, int y)
5058 {
5059   int step = 0;
5060
5061   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5062   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5063   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5064   if(!step) return FALSE;
5065   lastX = x; lastY = y;
5066   if((promoSweep < BlackPawn) == flipView) step = -step;
5067   if(step > 0) selectFlag = 1;
5068   if(!selectFlag) Sweep(step);
5069   return FALSE;
5070 }
5071
5072 void
5073 NextPiece(int step)
5074 {
5075     ChessSquare piece = boards[currentMove][toY][toX];
5076     do {
5077         pieceSweep -= step;
5078         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5079         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5080         if(!step) step = -1;
5081     } while(PieceToChar(pieceSweep) == '.');
5082     boards[currentMove][toY][toX] = pieceSweep;
5083     DrawPosition(FALSE, boards[currentMove]);
5084     boards[currentMove][toY][toX] = piece;
5085 }
5086 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5087 void
5088 AlphaRank(char *move, int n)
5089 {
5090 //    char *p = move, c; int x, y;
5091
5092     if (appData.debugMode) {
5093         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5094     }
5095
5096     if(move[1]=='*' &&
5097        move[2]>='0' && move[2]<='9' &&
5098        move[3]>='a' && move[3]<='x'    ) {
5099         move[1] = '@';
5100         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5101         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5102     } else
5103     if(move[0]>='0' && move[0]<='9' &&
5104        move[1]>='a' && move[1]<='x' &&
5105        move[2]>='0' && move[2]<='9' &&
5106        move[3]>='a' && move[3]<='x'    ) {
5107         /* input move, Shogi -> normal */
5108         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5109         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5110         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5111         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5112     } else
5113     if(move[1]=='@' &&
5114        move[3]>='0' && move[3]<='9' &&
5115        move[2]>='a' && move[2]<='x'    ) {
5116         move[1] = '*';
5117         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5118         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5119     } else
5120     if(
5121        move[0]>='a' && move[0]<='x' &&
5122        move[3]>='0' && move[3]<='9' &&
5123        move[2]>='a' && move[2]<='x'    ) {
5124          /* output move, normal -> Shogi */
5125         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5126         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5127         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5128         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5129         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5130     }
5131     if (appData.debugMode) {
5132         fprintf(debugFP, "   out = '%s'\n", move);
5133     }
5134 }
5135
5136 char yy_textstr[8000];
5137
5138 /* Parser for moves from gnuchess, ICS, or user typein box */
5139 Boolean
5140 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5141      char *move;
5142      int moveNum;
5143      ChessMove *moveType;
5144      int *fromX, *fromY, *toX, *toY;
5145      char *promoChar;
5146 {
5147     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5148
5149     switch (*moveType) {
5150       case WhitePromotion:
5151       case BlackPromotion:
5152       case WhiteNonPromotion:
5153       case BlackNonPromotion:
5154       case NormalMove:
5155       case WhiteCapturesEnPassant:
5156       case BlackCapturesEnPassant:
5157       case WhiteKingSideCastle:
5158       case WhiteQueenSideCastle:
5159       case BlackKingSideCastle:
5160       case BlackQueenSideCastle:
5161       case WhiteKingSideCastleWild:
5162       case WhiteQueenSideCastleWild:
5163       case BlackKingSideCastleWild:
5164       case BlackQueenSideCastleWild:
5165       /* Code added by Tord: */
5166       case WhiteHSideCastleFR:
5167       case WhiteASideCastleFR:
5168       case BlackHSideCastleFR:
5169       case BlackASideCastleFR:
5170       /* End of code added by Tord */
5171       case IllegalMove:         /* bug or odd chess variant */
5172         *fromX = currentMoveString[0] - AAA;
5173         *fromY = currentMoveString[1] - ONE;
5174         *toX = currentMoveString[2] - AAA;
5175         *toY = currentMoveString[3] - ONE;
5176         *promoChar = currentMoveString[4];
5177         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5178             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5179     if (appData.debugMode) {
5180         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5181     }
5182             *fromX = *fromY = *toX = *toY = 0;
5183             return FALSE;
5184         }
5185         if (appData.testLegality) {
5186           return (*moveType != IllegalMove);
5187         } else {
5188           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5189                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5190         }
5191
5192       case WhiteDrop:
5193       case BlackDrop:
5194         *fromX = *moveType == WhiteDrop ?
5195           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5196           (int) CharToPiece(ToLower(currentMoveString[0]));
5197         *fromY = DROP_RANK;
5198         *toX = currentMoveString[2] - AAA;
5199         *toY = currentMoveString[3] - ONE;
5200         *promoChar = NULLCHAR;
5201         return TRUE;
5202
5203       case AmbiguousMove:
5204       case ImpossibleMove:
5205       case EndOfFile:
5206       case ElapsedTime:
5207       case Comment:
5208       case PGNTag:
5209       case NAG:
5210       case WhiteWins:
5211       case BlackWins:
5212       case GameIsDrawn:
5213       default:
5214     if (appData.debugMode) {
5215         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5216     }
5217         /* bug? */
5218         *fromX = *fromY = *toX = *toY = 0;
5219         *promoChar = NULLCHAR;
5220         return FALSE;
5221     }
5222 }
5223
5224
5225 void
5226 ParsePV(char *pv, Boolean storeComments)
5227 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5228   int fromX, fromY, toX, toY; char promoChar;
5229   ChessMove moveType;
5230   Boolean valid;
5231   int nr = 0;
5232
5233   endPV = forwardMostMove;
5234   do {
5235     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5236     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5237     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5238 if(appData.debugMode){
5239 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);
5240 }
5241     if(!valid && nr == 0 &&
5242        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5243         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5244         // Hande case where played move is different from leading PV move
5245         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5246         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5247         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5248         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5249           endPV += 2; // if position different, keep this
5250           moveList[endPV-1][0] = fromX + AAA;
5251           moveList[endPV-1][1] = fromY + ONE;
5252           moveList[endPV-1][2] = toX + AAA;
5253           moveList[endPV-1][3] = toY + ONE;
5254           parseList[endPV-1][0] = NULLCHAR;
5255           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5256         }
5257       }
5258     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5259     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5260     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5261     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5262         valid++; // allow comments in PV
5263         continue;
5264     }
5265     nr++;
5266     if(endPV+1 > framePtr) break; // no space, truncate
5267     if(!valid) break;
5268     endPV++;
5269     CopyBoard(boards[endPV], boards[endPV-1]);
5270     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5271     moveList[endPV-1][0] = fromX + AAA;
5272     moveList[endPV-1][1] = fromY + ONE;
5273     moveList[endPV-1][2] = toX + AAA;
5274     moveList[endPV-1][3] = toY + ONE;
5275     moveList[endPV-1][4] = promoChar;
5276     moveList[endPV-1][5] = NULLCHAR;
5277     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5278     if(storeComments)
5279         CoordsToAlgebraic(boards[endPV - 1],
5280                              PosFlags(endPV - 1),
5281                              fromY, fromX, toY, toX, promoChar,
5282                              parseList[endPV - 1]);
5283     else
5284         parseList[endPV-1][0] = NULLCHAR;
5285   } while(valid);
5286   currentMove = endPV;
5287   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5288   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5289                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5290   DrawPosition(TRUE, boards[currentMove]);
5291 }
5292
5293 Boolean
5294 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5295 {
5296         int startPV;
5297         char *p;
5298
5299         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5300         lastX = x; lastY = y;
5301         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5302         startPV = index;
5303         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5304         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5305         index = startPV;
5306         do{ while(buf[index] && buf[index] != '\n') index++;
5307         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5308         buf[index] = 0;
5309         ParsePV(buf+startPV, FALSE);
5310         *start = startPV; *end = index-1;
5311         return TRUE;
5312 }
5313
5314 Boolean
5315 LoadPV(int x, int y)
5316 { // called on right mouse click to load PV
5317   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5318   lastX = x; lastY = y;
5319   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5320   return TRUE;
5321 }
5322
5323 void
5324 UnLoadPV()
5325 {
5326   if(endPV < 0) return;
5327   endPV = -1;
5328   currentMove = forwardMostMove;
5329   ClearPremoveHighlights();
5330   DrawPosition(TRUE, boards[currentMove]);
5331 }
5332
5333 void
5334 MovePV(int x, int y, int h)
5335 { // step through PV based on mouse coordinates (called on mouse move)
5336   int margin = h>>3, step = 0;
5337
5338   // we must somehow check if right button is still down (might be released off board!)
5339   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5340   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5341   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5342   if(!step) return;
5343   lastX = x; lastY = y;
5344
5345   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5346   if(endPV < 0) return;
5347   if(y < margin) step = 1; else
5348   if(y > h - margin) step = -1;
5349   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5350   currentMove += step;
5351   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5352   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5353                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5354   DrawPosition(FALSE, boards[currentMove]);
5355 }
5356
5357
5358 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5359 // All positions will have equal probability, but the current method will not provide a unique
5360 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5361 #define DARK 1
5362 #define LITE 2
5363 #define ANY 3
5364
5365 int squaresLeft[4];
5366 int piecesLeft[(int)BlackPawn];
5367 int seed, nrOfShuffles;
5368
5369 void GetPositionNumber()
5370 {       // sets global variable seed
5371         int i;
5372
5373         seed = appData.defaultFrcPosition;
5374         if(seed < 0) { // randomize based on time for negative FRC position numbers
5375                 for(i=0; i<50; i++) seed += random();
5376                 seed = random() ^ random() >> 8 ^ random() << 8;
5377                 if(seed<0) seed = -seed;
5378         }
5379 }
5380
5381 int put(Board board, int pieceType, int rank, int n, int shade)
5382 // put the piece on the (n-1)-th empty squares of the given shade
5383 {
5384         int i;
5385
5386         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5387                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5388                         board[rank][i] = (ChessSquare) pieceType;
5389                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5390                         squaresLeft[ANY]--;
5391                         piecesLeft[pieceType]--;
5392                         return i;
5393                 }
5394         }
5395         return -1;
5396 }
5397
5398
5399 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5400 // calculate where the next piece goes, (any empty square), and put it there
5401 {
5402         int i;
5403
5404         i = seed % squaresLeft[shade];
5405         nrOfShuffles *= squaresLeft[shade];
5406         seed /= squaresLeft[shade];
5407         put(board, pieceType, rank, i, shade);
5408 }
5409
5410 void AddTwoPieces(Board board, int pieceType, int rank)
5411 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5412 {
5413         int i, n=squaresLeft[ANY], j=n-1, k;
5414
5415         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5416         i = seed % k;  // pick one
5417         nrOfShuffles *= k;
5418         seed /= k;
5419         while(i >= j) i -= j--;
5420         j = n - 1 - j; i += j;
5421         put(board, pieceType, rank, j, ANY);
5422         put(board, pieceType, rank, i, ANY);
5423 }
5424
5425 void SetUpShuffle(Board board, int number)
5426 {
5427         int i, p, first=1;
5428
5429         GetPositionNumber(); nrOfShuffles = 1;
5430
5431         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5432         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5433         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5434
5435         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5436
5437         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5438             p = (int) board[0][i];
5439             if(p < (int) BlackPawn) piecesLeft[p] ++;
5440             board[0][i] = EmptySquare;
5441         }
5442
5443         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5444             // shuffles restricted to allow normal castling put KRR first
5445             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5446                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5447             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5448                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5449             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5450                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5451             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5452                 put(board, WhiteRook, 0, 0, ANY);
5453             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5454         }
5455
5456         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5457             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5458             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5459                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5460                 while(piecesLeft[p] >= 2) {
5461                     AddOnePiece(board, p, 0, LITE);
5462                     AddOnePiece(board, p, 0, DARK);
5463                 }
5464                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5465             }
5466
5467         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5468             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5469             // but we leave King and Rooks for last, to possibly obey FRC restriction
5470             if(p == (int)WhiteRook) continue;
5471             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5472             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5473         }
5474
5475         // now everything is placed, except perhaps King (Unicorn) and Rooks
5476
5477         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5478             // Last King gets castling rights
5479             while(piecesLeft[(int)WhiteUnicorn]) {
5480                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5481                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5482             }
5483
5484             while(piecesLeft[(int)WhiteKing]) {
5485                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5486                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5487             }
5488
5489
5490         } else {
5491             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5492             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5493         }
5494
5495         // Only Rooks can be left; simply place them all
5496         while(piecesLeft[(int)WhiteRook]) {
5497                 i = put(board, WhiteRook, 0, 0, ANY);
5498                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5499                         if(first) {
5500                                 first=0;
5501                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5502                         }
5503                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5504                 }
5505         }
5506         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5507             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5508         }
5509
5510         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5511 }
5512
5513 int SetCharTable( char *table, const char * map )
5514 /* [HGM] moved here from winboard.c because of its general usefulness */
5515 /*       Basically a safe strcpy that uses the last character as King */
5516 {
5517     int result = FALSE; int NrPieces;
5518
5519     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5520                     && NrPieces >= 12 && !(NrPieces&1)) {
5521         int i; /* [HGM] Accept even length from 12 to 34 */
5522
5523         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5524         for( i=0; i<NrPieces/2-1; i++ ) {
5525             table[i] = map[i];
5526             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5527         }
5528         table[(int) WhiteKing]  = map[NrPieces/2-1];
5529         table[(int) BlackKing]  = map[NrPieces-1];
5530
5531         result = TRUE;
5532     }
5533
5534     return result;
5535 }
5536
5537 void Prelude(Board board)
5538 {       // [HGM] superchess: random selection of exo-pieces
5539         int i, j, k; ChessSquare p;
5540         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5541
5542         GetPositionNumber(); // use FRC position number
5543
5544         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5545             SetCharTable(pieceToChar, appData.pieceToCharTable);
5546             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5547                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5548         }
5549
5550         j = seed%4;                 seed /= 4;
5551         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5552         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5553         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5554         j = seed%3 + (seed%3 >= j); seed /= 3;
5555         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5556         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5557         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5558         j = seed%3;                 seed /= 3;
5559         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5560         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5561         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5562         j = seed%2 + (seed%2 >= j); seed /= 2;
5563         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5564         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5565         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5566         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5567         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5568         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5569         put(board, exoPieces[0],    0, 0, ANY);
5570         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5571 }
5572
5573 void
5574 InitPosition(redraw)
5575      int redraw;
5576 {
5577     ChessSquare (* pieces)[BOARD_FILES];
5578     int i, j, pawnRow, overrule,
5579     oldx = gameInfo.boardWidth,
5580     oldy = gameInfo.boardHeight,
5581     oldh = gameInfo.holdingsWidth;
5582     static int oldv;
5583
5584     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5585
5586     /* [AS] Initialize pv info list [HGM] and game status */
5587     {
5588         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5589             pvInfoList[i].depth = 0;
5590             boards[i][EP_STATUS] = EP_NONE;
5591             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5592         }
5593
5594         initialRulePlies = 0; /* 50-move counter start */
5595
5596         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5597         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5598     }
5599
5600
5601     /* [HGM] logic here is completely changed. In stead of full positions */
5602     /* the initialized data only consist of the two backranks. The switch */
5603     /* selects which one we will use, which is than copied to the Board   */
5604     /* initialPosition, which for the rest is initialized by Pawns and    */
5605     /* empty squares. This initial position is then copied to boards[0],  */
5606     /* possibly after shuffling, so that it remains available.            */
5607
5608     gameInfo.holdingsWidth = 0; /* default board sizes */
5609     gameInfo.boardWidth    = 8;
5610     gameInfo.boardHeight   = 8;
5611     gameInfo.holdingsSize  = 0;
5612     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5613     for(i=0; i<BOARD_FILES-2; i++)
5614       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5615     initialPosition[EP_STATUS] = EP_NONE;
5616     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5617     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5618          SetCharTable(pieceNickName, appData.pieceNickNames);
5619     else SetCharTable(pieceNickName, "............");
5620     pieces = FIDEArray;
5621
5622     switch (gameInfo.variant) {
5623     case VariantFischeRandom:
5624       shuffleOpenings = TRUE;
5625     default:
5626       break;
5627     case VariantShatranj:
5628       pieces = ShatranjArray;
5629       nrCastlingRights = 0;
5630       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5631       break;
5632     case VariantMakruk:
5633       pieces = makrukArray;
5634       nrCastlingRights = 0;
5635       startedFromSetupPosition = TRUE;
5636       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5637       break;
5638     case VariantTwoKings:
5639       pieces = twoKingsArray;
5640       break;
5641     case VariantCapaRandom:
5642       shuffleOpenings = TRUE;
5643     case VariantCapablanca:
5644       pieces = CapablancaArray;
5645       gameInfo.boardWidth = 10;
5646       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5647       break;
5648     case VariantGothic:
5649       pieces = GothicArray;
5650       gameInfo.boardWidth = 10;
5651       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5652       break;
5653     case VariantSChess:
5654       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5655       gameInfo.holdingsSize = 7;
5656       break;
5657     case VariantJanus:
5658       pieces = JanusArray;
5659       gameInfo.boardWidth = 10;
5660       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5661       nrCastlingRights = 6;
5662         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5663         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5664         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5665         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5666         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5667         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5668       break;
5669     case VariantFalcon:
5670       pieces = FalconArray;
5671       gameInfo.boardWidth = 10;
5672       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5673       break;
5674     case VariantXiangqi:
5675       pieces = XiangqiArray;
5676       gameInfo.boardWidth  = 9;
5677       gameInfo.boardHeight = 10;
5678       nrCastlingRights = 0;
5679       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5680       break;
5681     case VariantShogi:
5682       pieces = ShogiArray;
5683       gameInfo.boardWidth  = 9;
5684       gameInfo.boardHeight = 9;
5685       gameInfo.holdingsSize = 7;
5686       nrCastlingRights = 0;
5687       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5688       break;
5689     case VariantCourier:
5690       pieces = CourierArray;
5691       gameInfo.boardWidth  = 12;
5692       nrCastlingRights = 0;
5693       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5694       break;
5695     case VariantKnightmate:
5696       pieces = KnightmateArray;
5697       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5698       break;
5699     case VariantSpartan:
5700       pieces = SpartanArray;
5701       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5702       break;
5703     case VariantFairy:
5704       pieces = fairyArray;
5705       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5706       break;
5707     case VariantGreat:
5708       pieces = GreatArray;
5709       gameInfo.boardWidth = 10;
5710       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5711       gameInfo.holdingsSize = 8;
5712       break;
5713     case VariantSuper:
5714       pieces = FIDEArray;
5715       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5716       gameInfo.holdingsSize = 8;
5717       startedFromSetupPosition = TRUE;
5718       break;
5719     case VariantCrazyhouse:
5720     case VariantBughouse:
5721       pieces = FIDEArray;
5722       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5723       gameInfo.holdingsSize = 5;
5724       break;
5725     case VariantWildCastle:
5726       pieces = FIDEArray;
5727       /* !!?shuffle with kings guaranteed to be on d or e file */
5728       shuffleOpenings = 1;
5729       break;
5730     case VariantNoCastle:
5731       pieces = FIDEArray;
5732       nrCastlingRights = 0;
5733       /* !!?unconstrained back-rank shuffle */
5734       shuffleOpenings = 1;
5735       break;
5736     }
5737
5738     overrule = 0;
5739     if(appData.NrFiles >= 0) {
5740         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5741         gameInfo.boardWidth = appData.NrFiles;
5742     }
5743     if(appData.NrRanks >= 0) {
5744         gameInfo.boardHeight = appData.NrRanks;
5745     }
5746     if(appData.holdingsSize >= 0) {
5747         i = appData.holdingsSize;
5748         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5749         gameInfo.holdingsSize = i;
5750     }
5751     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5752     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5753         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5754
5755     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5756     if(pawnRow < 1) pawnRow = 1;
5757     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5758
5759     /* User pieceToChar list overrules defaults */
5760     if(appData.pieceToCharTable != NULL)
5761         SetCharTable(pieceToChar, appData.pieceToCharTable);
5762
5763     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5764
5765         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5766             s = (ChessSquare) 0; /* account holding counts in guard band */
5767         for( i=0; i<BOARD_HEIGHT; i++ )
5768             initialPosition[i][j] = s;
5769
5770         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5771         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5772         initialPosition[pawnRow][j] = WhitePawn;
5773         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5774         if(gameInfo.variant == VariantXiangqi) {
5775             if(j&1) {
5776                 initialPosition[pawnRow][j] =
5777                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5778                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5779                    initialPosition[2][j] = WhiteCannon;
5780                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5781                 }
5782             }
5783         }
5784         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5785     }
5786     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5787
5788             j=BOARD_LEFT+1;
5789             initialPosition[1][j] = WhiteBishop;
5790             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5791             j=BOARD_RGHT-2;
5792             initialPosition[1][j] = WhiteRook;
5793             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5794     }
5795
5796     if( nrCastlingRights == -1) {
5797         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5798         /*       This sets default castling rights from none to normal corners   */
5799         /* Variants with other castling rights must set them themselves above    */
5800         nrCastlingRights = 6;
5801
5802         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5803         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5804         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5805         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5806         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5807         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5808      }
5809
5810      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5811      if(gameInfo.variant == VariantGreat) { // promotion commoners
5812         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5813         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5814         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5815         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5816      }
5817      if( gameInfo.variant == VariantSChess ) {
5818       initialPosition[1][0] = BlackMarshall;
5819       initialPosition[2][0] = BlackAngel;
5820       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5821       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5822       initialPosition[1][1] = initialPosition[2][1] = 
5823       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5824      }
5825   if (appData.debugMode) {
5826     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5827   }
5828     if(shuffleOpenings) {
5829         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5830         startedFromSetupPosition = TRUE;
5831     }
5832     if(startedFromPositionFile) {
5833       /* [HGM] loadPos: use PositionFile for every new game */
5834       CopyBoard(initialPosition, filePosition);
5835       for(i=0; i<nrCastlingRights; i++)
5836           initialRights[i] = filePosition[CASTLING][i];
5837       startedFromSetupPosition = TRUE;
5838     }
5839
5840     CopyBoard(boards[0], initialPosition);
5841
5842     if(oldx != gameInfo.boardWidth ||
5843        oldy != gameInfo.boardHeight ||
5844        oldv != gameInfo.variant ||
5845        oldh != gameInfo.holdingsWidth
5846                                          )
5847             InitDrawingSizes(-2 ,0);
5848
5849     oldv = gameInfo.variant;
5850     if (redraw)
5851       DrawPosition(TRUE, boards[currentMove]);
5852 }
5853
5854 void
5855 SendBoard(cps, moveNum)
5856      ChessProgramState *cps;
5857      int moveNum;
5858 {
5859     char message[MSG_SIZ];
5860
5861     if (cps->useSetboard) {
5862       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5863       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5864       SendToProgram(message, cps);
5865       free(fen);
5866
5867     } else {
5868       ChessSquare *bp;
5869       int i, j;
5870       /* Kludge to set black to move, avoiding the troublesome and now
5871        * deprecated "black" command.
5872        */
5873       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5874         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5875
5876       SendToProgram("edit\n", cps);
5877       SendToProgram("#\n", cps);
5878       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5879         bp = &boards[moveNum][i][BOARD_LEFT];
5880         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5881           if ((int) *bp < (int) BlackPawn) {
5882             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5883                     AAA + j, ONE + i);
5884             if(message[0] == '+' || message[0] == '~') {
5885               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5886                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5887                         AAA + j, ONE + i);
5888             }
5889             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5890                 message[1] = BOARD_RGHT   - 1 - j + '1';
5891                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5892             }
5893             SendToProgram(message, cps);
5894           }
5895         }
5896       }
5897
5898       SendToProgram("c\n", cps);
5899       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5900         bp = &boards[moveNum][i][BOARD_LEFT];
5901         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5902           if (((int) *bp != (int) EmptySquare)
5903               && ((int) *bp >= (int) BlackPawn)) {
5904             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5905                     AAA + j, ONE + i);
5906             if(message[0] == '+' || message[0] == '~') {
5907               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5908                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5909                         AAA + j, ONE + i);
5910             }
5911             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5912                 message[1] = BOARD_RGHT   - 1 - j + '1';
5913                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5914             }
5915             SendToProgram(message, cps);
5916           }
5917         }
5918       }
5919
5920       SendToProgram(".\n", cps);
5921     }
5922     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5923 }
5924
5925 ChessSquare
5926 DefaultPromoChoice(int white)
5927 {
5928     ChessSquare result;
5929     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5930         result = WhiteFerz; // no choice
5931     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5932         result= WhiteKing; // in Suicide Q is the last thing we want
5933     else if(gameInfo.variant == VariantSpartan)
5934         result = white ? WhiteQueen : WhiteAngel;
5935     else result = WhiteQueen;
5936     if(!white) result = WHITE_TO_BLACK result;
5937     return result;
5938 }
5939
5940 static int autoQueen; // [HGM] oneclick
5941
5942 int
5943 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5944 {
5945     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5946     /* [HGM] add Shogi promotions */
5947     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5948     ChessSquare piece;
5949     ChessMove moveType;
5950     Boolean premove;
5951
5952     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5953     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5954
5955     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5956       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5957         return FALSE;
5958
5959     piece = boards[currentMove][fromY][fromX];
5960     if(gameInfo.variant == VariantShogi) {
5961         promotionZoneSize = BOARD_HEIGHT/3;
5962         highestPromotingPiece = (int)WhiteFerz;
5963     } else if(gameInfo.variant == VariantMakruk) {
5964         promotionZoneSize = 3;
5965     }
5966
5967     // Treat Lance as Pawn when it is not representing Amazon
5968     if(gameInfo.variant != VariantSuper) {
5969         if(piece == WhiteLance) piece = WhitePawn; else
5970         if(piece == BlackLance) piece = BlackPawn;
5971     }
5972
5973     // next weed out all moves that do not touch the promotion zone at all
5974     if((int)piece >= BlackPawn) {
5975         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5976              return FALSE;
5977         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5978     } else {
5979         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5980            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5981     }
5982
5983     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5984
5985     // weed out mandatory Shogi promotions
5986     if(gameInfo.variant == VariantShogi) {
5987         if(piece >= BlackPawn) {
5988             if(toY == 0 && piece == BlackPawn ||
5989                toY == 0 && piece == BlackQueen ||
5990                toY <= 1 && piece == BlackKnight) {
5991                 *promoChoice = '+';
5992                 return FALSE;
5993             }
5994         } else {
5995             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5996                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5997                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5998                 *promoChoice = '+';
5999                 return FALSE;
6000             }
6001         }
6002     }
6003
6004     // weed out obviously illegal Pawn moves
6005     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6006         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6007         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6008         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6009         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6010         // note we are not allowed to test for valid (non-)capture, due to premove
6011     }
6012
6013     // we either have a choice what to promote to, or (in Shogi) whether to promote
6014     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6015         *promoChoice = PieceToChar(BlackFerz);  // no choice
6016         return FALSE;
6017     }
6018     // no sense asking what we must promote to if it is going to explode...
6019     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6020         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6021         return FALSE;
6022     }
6023     // give caller the default choice even if we will not make it
6024     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6025     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6026     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6027                            && gameInfo.variant != VariantShogi
6028                            && gameInfo.variant != VariantSuper) return FALSE;
6029     if(autoQueen) return FALSE; // predetermined
6030
6031     // suppress promotion popup on illegal moves that are not premoves
6032     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6033               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6034     if(appData.testLegality && !premove) {
6035         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6036                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6037         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6038             return FALSE;
6039     }
6040
6041     return TRUE;
6042 }
6043
6044 int
6045 InPalace(row, column)
6046      int row, column;
6047 {   /* [HGM] for Xiangqi */
6048     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6049          column < (BOARD_WIDTH + 4)/2 &&
6050          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6051     return FALSE;
6052 }
6053
6054 int
6055 PieceForSquare (x, y)
6056      int x;
6057      int y;
6058 {
6059   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6060      return -1;
6061   else
6062      return boards[currentMove][y][x];
6063 }
6064
6065 int
6066 OKToStartUserMove(x, y)
6067      int x, y;
6068 {
6069     ChessSquare from_piece;
6070     int white_piece;
6071
6072     if (matchMode) return FALSE;
6073     if (gameMode == EditPosition) return TRUE;
6074
6075     if (x >= 0 && y >= 0)
6076       from_piece = boards[currentMove][y][x];
6077     else
6078       from_piece = EmptySquare;
6079
6080     if (from_piece == EmptySquare) return FALSE;
6081
6082     white_piece = (int)from_piece >= (int)WhitePawn &&
6083       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6084
6085     switch (gameMode) {
6086       case PlayFromGameFile:
6087       case AnalyzeFile:
6088       case TwoMachinesPlay:
6089       case EndOfGame:
6090         return FALSE;
6091
6092       case IcsObserving:
6093       case IcsIdle:
6094         return FALSE;
6095
6096       case MachinePlaysWhite:
6097       case IcsPlayingBlack:
6098         if (appData.zippyPlay) return FALSE;
6099         if (white_piece) {
6100             DisplayMoveError(_("You are playing Black"));
6101             return FALSE;
6102         }
6103         break;
6104
6105       case MachinePlaysBlack:
6106       case IcsPlayingWhite:
6107         if (appData.zippyPlay) return FALSE;
6108         if (!white_piece) {
6109             DisplayMoveError(_("You are playing White"));
6110             return FALSE;
6111         }
6112         break;
6113
6114       case EditGame:
6115         if (!white_piece && WhiteOnMove(currentMove)) {
6116             DisplayMoveError(_("It is White's turn"));
6117             return FALSE;
6118         }
6119         if (white_piece && !WhiteOnMove(currentMove)) {
6120             DisplayMoveError(_("It is Black's turn"));
6121             return FALSE;
6122         }
6123         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6124             /* Editing correspondence game history */
6125             /* Could disallow this or prompt for confirmation */
6126             cmailOldMove = -1;
6127         }
6128         break;
6129
6130       case BeginningOfGame:
6131         if (appData.icsActive) return FALSE;
6132         if (!appData.noChessProgram) {
6133             if (!white_piece) {
6134                 DisplayMoveError(_("You are playing White"));
6135                 return FALSE;
6136             }
6137         }
6138         break;
6139
6140       case Training:
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         break;
6150
6151       default:
6152       case IcsExamining:
6153         break;
6154     }
6155     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6156         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6157         && gameMode != AnalyzeFile && gameMode != Training) {
6158         DisplayMoveError(_("Displayed position is not current"));
6159         return FALSE;
6160     }
6161     return TRUE;
6162 }
6163
6164 Boolean
6165 OnlyMove(int *x, int *y, Boolean captures) {
6166     DisambiguateClosure cl;
6167     if (appData.zippyPlay) return FALSE;
6168     switch(gameMode) {
6169       case MachinePlaysBlack:
6170       case IcsPlayingWhite:
6171       case BeginningOfGame:
6172         if(!WhiteOnMove(currentMove)) return FALSE;
6173         break;
6174       case MachinePlaysWhite:
6175       case IcsPlayingBlack:
6176         if(WhiteOnMove(currentMove)) return FALSE;
6177         break;
6178       case EditGame:
6179         break;
6180       default:
6181         return FALSE;
6182     }
6183     cl.pieceIn = EmptySquare;
6184     cl.rfIn = *y;
6185     cl.ffIn = *x;
6186     cl.rtIn = -1;
6187     cl.ftIn = -1;
6188     cl.promoCharIn = NULLCHAR;
6189     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6190     if( cl.kind == NormalMove ||
6191         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6192         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6193         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6194       fromX = cl.ff;
6195       fromY = cl.rf;
6196       *x = cl.ft;
6197       *y = cl.rt;
6198       return TRUE;
6199     }
6200     if(cl.kind != ImpossibleMove) return FALSE;
6201     cl.pieceIn = EmptySquare;
6202     cl.rfIn = -1;
6203     cl.ffIn = -1;
6204     cl.rtIn = *y;
6205     cl.ftIn = *x;
6206     cl.promoCharIn = NULLCHAR;
6207     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6208     if( cl.kind == NormalMove ||
6209         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6210         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6211         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6212       fromX = cl.ff;
6213       fromY = cl.rf;
6214       *x = cl.ft;
6215       *y = cl.rt;
6216       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6217       return TRUE;
6218     }
6219     return FALSE;
6220 }
6221
6222 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6223 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6224 int lastLoadGameUseList = FALSE;
6225 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6226 ChessMove lastLoadGameStart = EndOfFile;
6227
6228 void
6229 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6230      int fromX, fromY, toX, toY;
6231      int promoChar;
6232 {
6233     ChessMove moveType;
6234     ChessSquare pdown, pup;
6235
6236     /* Check if the user is playing in turn.  This is complicated because we
6237        let the user "pick up" a piece before it is his turn.  So the piece he
6238        tried to pick up may have been captured by the time he puts it down!
6239        Therefore we use the color the user is supposed to be playing in this
6240        test, not the color of the piece that is currently on the starting
6241        square---except in EditGame mode, where the user is playing both
6242        sides; fortunately there the capture race can't happen.  (It can
6243        now happen in IcsExamining mode, but that's just too bad.  The user
6244        will get a somewhat confusing message in that case.)
6245        */
6246
6247     switch (gameMode) {
6248       case PlayFromGameFile:
6249       case AnalyzeFile:
6250       case TwoMachinesPlay:
6251       case EndOfGame:
6252       case IcsObserving:
6253       case IcsIdle:
6254         /* We switched into a game mode where moves are not accepted,
6255            perhaps while the mouse button was down. */
6256         return;
6257
6258       case MachinePlaysWhite:
6259         /* User is moving for Black */
6260         if (WhiteOnMove(currentMove)) {
6261             DisplayMoveError(_("It is White's turn"));
6262             return;
6263         }
6264         break;
6265
6266       case MachinePlaysBlack:
6267         /* User is moving for White */
6268         if (!WhiteOnMove(currentMove)) {
6269             DisplayMoveError(_("It is Black's turn"));
6270             return;
6271         }
6272         break;
6273
6274       case EditGame:
6275       case IcsExamining:
6276       case BeginningOfGame:
6277       case AnalyzeMode:
6278       case Training:
6279         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6280         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6281             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6282             /* User is moving for Black */
6283             if (WhiteOnMove(currentMove)) {
6284                 DisplayMoveError(_("It is White's turn"));
6285                 return;
6286             }
6287         } else {
6288             /* User is moving for White */
6289             if (!WhiteOnMove(currentMove)) {
6290                 DisplayMoveError(_("It is Black's turn"));
6291                 return;
6292             }
6293         }
6294         break;
6295
6296       case IcsPlayingBlack:
6297         /* User is moving for Black */
6298         if (WhiteOnMove(currentMove)) {
6299             if (!appData.premove) {
6300                 DisplayMoveError(_("It is White's turn"));
6301             } else if (toX >= 0 && toY >= 0) {
6302                 premoveToX = toX;
6303                 premoveToY = toY;
6304                 premoveFromX = fromX;
6305                 premoveFromY = fromY;
6306                 premovePromoChar = promoChar;
6307                 gotPremove = 1;
6308                 if (appData.debugMode)
6309                     fprintf(debugFP, "Got premove: fromX %d,"
6310                             "fromY %d, toX %d, toY %d\n",
6311                             fromX, fromY, toX, toY);
6312             }
6313             return;
6314         }
6315         break;
6316
6317       case IcsPlayingWhite:
6318         /* User is moving for White */
6319         if (!WhiteOnMove(currentMove)) {
6320             if (!appData.premove) {
6321                 DisplayMoveError(_("It is Black's turn"));
6322             } else if (toX >= 0 && toY >= 0) {
6323                 premoveToX = toX;
6324                 premoveToY = toY;
6325                 premoveFromX = fromX;
6326                 premoveFromY = fromY;
6327                 premovePromoChar = promoChar;
6328                 gotPremove = 1;
6329                 if (appData.debugMode)
6330                     fprintf(debugFP, "Got premove: fromX %d,"
6331                             "fromY %d, toX %d, toY %d\n",
6332                             fromX, fromY, toX, toY);
6333             }
6334             return;
6335         }
6336         break;
6337
6338       default:
6339         break;
6340
6341       case EditPosition:
6342         /* EditPosition, empty square, or different color piece;
6343            click-click move is possible */
6344         if (toX == -2 || toY == -2) {
6345             boards[0][fromY][fromX] = EmptySquare;
6346             DrawPosition(FALSE, boards[currentMove]);
6347             return;
6348         } else if (toX >= 0 && toY >= 0) {
6349             boards[0][toY][toX] = boards[0][fromY][fromX];
6350             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6351                 if(boards[0][fromY][0] != EmptySquare) {
6352                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6353                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6354                 }
6355             } else
6356             if(fromX == BOARD_RGHT+1) {
6357                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6358                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6359                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6360                 }
6361             } else
6362             boards[0][fromY][fromX] = EmptySquare;
6363             DrawPosition(FALSE, boards[currentMove]);
6364             return;
6365         }
6366         return;
6367     }
6368
6369     if(toX < 0 || toY < 0) return;
6370     pdown = boards[currentMove][fromY][fromX];
6371     pup = boards[currentMove][toY][toX];
6372
6373     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6374     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6375          if( pup != EmptySquare ) return;
6376          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6377            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6378                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6379            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6380            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6381            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6382            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6383          fromY = DROP_RANK;
6384     }
6385
6386     /* [HGM] always test for legality, to get promotion info */
6387     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6388                                          fromY, fromX, toY, toX, promoChar);
6389     /* [HGM] but possibly ignore an IllegalMove result */
6390     if (appData.testLegality) {
6391         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6392             DisplayMoveError(_("Illegal move"));
6393             return;
6394         }
6395     }
6396
6397     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6398 }
6399
6400 /* Common tail of UserMoveEvent and DropMenuEvent */
6401 int
6402 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6403      ChessMove moveType;
6404      int fromX, fromY, toX, toY;
6405      /*char*/int promoChar;
6406 {
6407     char *bookHit = 0;
6408
6409     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6410         // [HGM] superchess: suppress promotions to non-available piece
6411         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6412         if(WhiteOnMove(currentMove)) {
6413             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6414         } else {
6415             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6416         }
6417     }
6418
6419     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6420        move type in caller when we know the move is a legal promotion */
6421     if(moveType == NormalMove && promoChar)
6422         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6423
6424     /* [HGM] <popupFix> The following if has been moved here from
6425        UserMoveEvent(). Because it seemed to belong here (why not allow
6426        piece drops in training games?), and because it can only be
6427        performed after it is known to what we promote. */
6428     if (gameMode == Training) {
6429       /* compare the move played on the board to the next move in the
6430        * game. If they match, display the move and the opponent's response.
6431        * If they don't match, display an error message.
6432        */
6433       int saveAnimate;
6434       Board testBoard;
6435       CopyBoard(testBoard, boards[currentMove]);
6436       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6437
6438       if (CompareBoards(testBoard, boards[currentMove+1])) {
6439         ForwardInner(currentMove+1);
6440
6441         /* Autoplay the opponent's response.
6442          * if appData.animate was TRUE when Training mode was entered,
6443          * the response will be animated.
6444          */
6445         saveAnimate = appData.animate;
6446         appData.animate = animateTraining;
6447         ForwardInner(currentMove+1);
6448         appData.animate = saveAnimate;
6449
6450         /* check for the end of the game */
6451         if (currentMove >= forwardMostMove) {
6452           gameMode = PlayFromGameFile;
6453           ModeHighlight();
6454           SetTrainingModeOff();
6455           DisplayInformation(_("End of game"));
6456         }
6457       } else {
6458         DisplayError(_("Incorrect move"), 0);
6459       }
6460       return 1;
6461     }
6462
6463   /* Ok, now we know that the move is good, so we can kill
6464      the previous line in Analysis Mode */
6465   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6466                                 && currentMove < forwardMostMove) {
6467     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6468     else forwardMostMove = currentMove;
6469   }
6470
6471   /* If we need the chess program but it's dead, restart it */
6472   ResurrectChessProgram();
6473
6474   /* A user move restarts a paused game*/
6475   if (pausing)
6476     PauseEvent();
6477
6478   thinkOutput[0] = NULLCHAR;
6479
6480   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6481
6482   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6483     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6484     return 1;
6485   }
6486
6487   if (gameMode == BeginningOfGame) {
6488     if (appData.noChessProgram) {
6489       gameMode = EditGame;
6490       SetGameInfo();
6491     } else {
6492       char buf[MSG_SIZ];
6493       gameMode = MachinePlaysBlack;
6494       StartClocks();
6495       SetGameInfo();
6496       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6497       DisplayTitle(buf);
6498       if (first.sendName) {
6499         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6500         SendToProgram(buf, &first);
6501       }
6502       StartClocks();
6503     }
6504     ModeHighlight();
6505   }
6506
6507   /* Relay move to ICS or chess engine */
6508   if (appData.icsActive) {
6509     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6510         gameMode == IcsExamining) {
6511       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6512         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6513         SendToICS("draw ");
6514         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6515       }
6516       // also send plain move, in case ICS does not understand atomic claims
6517       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6518       ics_user_moved = 1;
6519     }
6520   } else {
6521     if (first.sendTime && (gameMode == BeginningOfGame ||
6522                            gameMode == MachinePlaysWhite ||
6523                            gameMode == MachinePlaysBlack)) {
6524       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6525     }
6526     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6527          // [HGM] book: if program might be playing, let it use book
6528         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6529         first.maybeThinking = TRUE;
6530     } else SendMoveToProgram(forwardMostMove-1, &first);
6531     if (currentMove == cmailOldMove + 1) {
6532       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6533     }
6534   }
6535
6536   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6537
6538   switch (gameMode) {
6539   case EditGame:
6540     if(appData.testLegality)
6541     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6542     case MT_NONE:
6543     case MT_CHECK:
6544       break;
6545     case MT_CHECKMATE:
6546     case MT_STAINMATE:
6547       if (WhiteOnMove(currentMove)) {
6548         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6549       } else {
6550         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6551       }
6552       break;
6553     case MT_STALEMATE:
6554       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6555       break;
6556     }
6557     break;
6558
6559   case MachinePlaysBlack:
6560   case MachinePlaysWhite:
6561     /* disable certain menu options while machine is thinking */
6562     SetMachineThinkingEnables();
6563     break;
6564
6565   default:
6566     break;
6567   }
6568
6569   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6570   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6571
6572   if(bookHit) { // [HGM] book: simulate book reply
6573         static char bookMove[MSG_SIZ]; // a bit generous?
6574
6575         programStats.nodes = programStats.depth = programStats.time =
6576         programStats.score = programStats.got_only_move = 0;
6577         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6578
6579         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6580         strcat(bookMove, bookHit);
6581         HandleMachineMove(bookMove, &first);
6582   }
6583   return 1;
6584 }
6585
6586 void
6587 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6588      Board board;
6589      int flags;
6590      ChessMove kind;
6591      int rf, ff, rt, ft;
6592      VOIDSTAR closure;
6593 {
6594     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6595     Markers *m = (Markers *) closure;
6596     if(rf == fromY && ff == fromX)
6597         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6598                          || kind == WhiteCapturesEnPassant
6599                          || kind == BlackCapturesEnPassant);
6600     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6601 }
6602
6603 void
6604 MarkTargetSquares(int clear)
6605 {
6606   int x, y;
6607   if(!appData.markers || !appData.highlightDragging ||
6608      !appData.testLegality || gameMode == EditPosition) return;
6609   if(clear) {
6610     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6611   } else {
6612     int capt = 0;
6613     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6614     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6615       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6616       if(capt)
6617       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6618     }
6619   }
6620   DrawPosition(TRUE, NULL);
6621 }
6622
6623 int
6624 Explode(Board board, int fromX, int fromY, int toX, int toY)
6625 {
6626     if(gameInfo.variant == VariantAtomic &&
6627        (board[toY][toX] != EmptySquare ||                     // capture?
6628         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6629                          board[fromY][fromX] == BlackPawn   )
6630       )) {
6631         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6632         return TRUE;
6633     }
6634     return FALSE;
6635 }
6636
6637 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6638
6639 int CanPromote(ChessSquare piece, int y)
6640 {
6641         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6642         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6643         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6644            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6645            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6646                                                   gameInfo.variant == VariantMakruk) return FALSE;
6647         return (piece == BlackPawn && y == 1 ||
6648                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6649                 piece == BlackLance && y == 1 ||
6650                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6651 }
6652
6653 void LeftClick(ClickType clickType, int xPix, int yPix)
6654 {
6655     int x, y;
6656     Boolean saveAnimate;
6657     static int second = 0, promotionChoice = 0, clearFlag = 0;
6658     char promoChoice = NULLCHAR;
6659     ChessSquare piece;
6660
6661     if(appData.seekGraph && appData.icsActive && loggedOn &&
6662         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6663         SeekGraphClick(clickType, xPix, yPix, 0);
6664         return;
6665     }
6666
6667     if (clickType == Press) ErrorPopDown();
6668     MarkTargetSquares(1);
6669
6670     x = EventToSquare(xPix, BOARD_WIDTH);
6671     y = EventToSquare(yPix, BOARD_HEIGHT);
6672     if (!flipView && y >= 0) {
6673         y = BOARD_HEIGHT - 1 - y;
6674     }
6675     if (flipView && x >= 0) {
6676         x = BOARD_WIDTH - 1 - x;
6677     }
6678
6679     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6680         defaultPromoChoice = promoSweep;
6681         promoSweep = EmptySquare;   // terminate sweep
6682         promoDefaultAltered = TRUE;
6683         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6684     }
6685
6686     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6687         if(clickType == Release) return; // ignore upclick of click-click destination
6688         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6689         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6690         if(gameInfo.holdingsWidth &&
6691                 (WhiteOnMove(currentMove)
6692                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6693                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6694             // click in right holdings, for determining promotion piece
6695             ChessSquare p = boards[currentMove][y][x];
6696             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6697             if(p != EmptySquare) {
6698                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6699                 fromX = fromY = -1;
6700                 return;
6701             }
6702         }
6703         DrawPosition(FALSE, boards[currentMove]);
6704         return;
6705     }
6706
6707     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6708     if(clickType == Press
6709             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6710               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6711               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6712         return;
6713
6714     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6715         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6716
6717     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6718         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6719                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6720         defaultPromoChoice = DefaultPromoChoice(side);
6721     }
6722
6723     autoQueen = appData.alwaysPromoteToQueen;
6724
6725     if (fromX == -1) {
6726       int originalY = y;
6727       gatingPiece = EmptySquare;
6728       if (clickType != Press) {
6729         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6730             DragPieceEnd(xPix, yPix); dragging = 0;
6731             DrawPosition(FALSE, NULL);
6732         }
6733         return;
6734       }
6735       fromX = x; fromY = y;
6736       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6737          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6738          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6739             /* First square */
6740             if (OKToStartUserMove(fromX, fromY)) {
6741                 second = 0;
6742                 MarkTargetSquares(0);
6743                 DragPieceBegin(xPix, yPix); dragging = 1;
6744                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6745                     promoSweep = defaultPromoChoice;
6746                     selectFlag = 0; lastX = xPix; lastY = yPix;
6747                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6748                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6749                 }
6750                 if (appData.highlightDragging) {
6751                     SetHighlights(fromX, fromY, -1, -1);
6752                 }
6753             } else fromX = fromY = -1;
6754             return;
6755         }
6756     }
6757
6758     /* fromX != -1 */
6759     if (clickType == Press && gameMode != EditPosition) {
6760         ChessSquare fromP;
6761         ChessSquare toP;
6762         int frc;
6763
6764         // ignore off-board to clicks
6765         if(y < 0 || x < 0) return;
6766
6767         /* Check if clicking again on the same color piece */
6768         fromP = boards[currentMove][fromY][fromX];
6769         toP = boards[currentMove][y][x];
6770         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6771         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6772              WhitePawn <= toP && toP <= WhiteKing &&
6773              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6774              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6775             (BlackPawn <= fromP && fromP <= BlackKing &&
6776              BlackPawn <= toP && toP <= BlackKing &&
6777              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6778              !(fromP == BlackKing && toP == BlackRook && frc))) {
6779             /* Clicked again on same color piece -- changed his mind */
6780             second = (x == fromX && y == fromY);
6781             promoDefaultAltered = FALSE;
6782            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6783             if (appData.highlightDragging) {
6784                 SetHighlights(x, y, -1, -1);
6785             } else {
6786                 ClearHighlights();
6787             }
6788             if (OKToStartUserMove(x, y)) {
6789                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6790                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6791                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6792                  gatingPiece = boards[currentMove][fromY][fromX];
6793                 else gatingPiece = EmptySquare;
6794                 fromX = x;
6795                 fromY = y; dragging = 1;
6796                 MarkTargetSquares(0);
6797                 DragPieceBegin(xPix, yPix);
6798                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6799                     promoSweep = defaultPromoChoice;
6800                     selectFlag = 0; lastX = xPix; lastY = yPix;
6801                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6802                 }
6803             }
6804            }
6805            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6806            second = FALSE; 
6807         }
6808         // ignore clicks on holdings
6809         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6810     }
6811
6812     if (clickType == Release && x == fromX && y == fromY) {
6813         DragPieceEnd(xPix, yPix); dragging = 0;
6814         if(clearFlag) {
6815             // a deferred attempt to click-click move an empty square on top of a piece
6816             boards[currentMove][y][x] = EmptySquare;
6817             ClearHighlights();
6818             DrawPosition(FALSE, boards[currentMove]);
6819             fromX = fromY = -1; clearFlag = 0;
6820             return;
6821         }
6822         if (appData.animateDragging) {
6823             /* Undo animation damage if any */
6824             DrawPosition(FALSE, NULL);
6825         }
6826         if (second) {
6827             /* Second up/down in same square; just abort move */
6828             second = 0;
6829             fromX = fromY = -1;
6830             gatingPiece = EmptySquare;
6831             ClearHighlights();
6832             gotPremove = 0;
6833             ClearPremoveHighlights();
6834         } else {
6835             /* First upclick in same square; start click-click mode */
6836             SetHighlights(x, y, -1, -1);
6837         }
6838         return;
6839     }
6840
6841     clearFlag = 0;
6842
6843     /* we now have a different from- and (possibly off-board) to-square */
6844     /* Completed move */
6845     toX = x;
6846     toY = y;
6847     saveAnimate = appData.animate;
6848     if (clickType == Press) {
6849         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6850             // must be Edit Position mode with empty-square selected
6851             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6852             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6853             return;
6854         }
6855         /* Finish clickclick move */
6856         if (appData.animate || appData.highlightLastMove) {
6857             SetHighlights(fromX, fromY, toX, toY);
6858         } else {
6859             ClearHighlights();
6860         }
6861     } else {
6862         /* Finish drag move */
6863         if (appData.highlightLastMove) {
6864             SetHighlights(fromX, fromY, toX, toY);
6865         } else {
6866             ClearHighlights();
6867         }
6868         DragPieceEnd(xPix, yPix); dragging = 0;
6869         /* Don't animate move and drag both */
6870         appData.animate = FALSE;
6871     }
6872
6873     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6874     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6875         ChessSquare piece = boards[currentMove][fromY][fromX];
6876         if(gameMode == EditPosition && piece != EmptySquare &&
6877            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6878             int n;
6879
6880             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6881                 n = PieceToNumber(piece - (int)BlackPawn);
6882                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6883                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6884                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6885             } else
6886             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6887                 n = PieceToNumber(piece);
6888                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6889                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6890                 boards[currentMove][n][BOARD_WIDTH-2]++;
6891             }
6892             boards[currentMove][fromY][fromX] = EmptySquare;
6893         }
6894         ClearHighlights();
6895         fromX = fromY = -1;
6896         DrawPosition(TRUE, boards[currentMove]);
6897         return;
6898     }
6899
6900     // off-board moves should not be highlighted
6901     if(x < 0 || y < 0) ClearHighlights();
6902
6903     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6904
6905     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6906         SetHighlights(fromX, fromY, toX, toY);
6907         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6908             // [HGM] super: promotion to captured piece selected from holdings
6909             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6910             promotionChoice = TRUE;
6911             // kludge follows to temporarily execute move on display, without promoting yet
6912             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6913             boards[currentMove][toY][toX] = p;
6914             DrawPosition(FALSE, boards[currentMove]);
6915             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6916             boards[currentMove][toY][toX] = q;
6917             DisplayMessage("Click in holdings to choose piece", "");
6918             return;
6919         }
6920         PromotionPopUp();
6921     } else {
6922         int oldMove = currentMove;
6923         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6924         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6925         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6926         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6927            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6928             DrawPosition(TRUE, boards[currentMove]);
6929         fromX = fromY = -1;
6930     }
6931     appData.animate = saveAnimate;
6932     if (appData.animate || appData.animateDragging) {
6933         /* Undo animation damage if needed */
6934         DrawPosition(FALSE, NULL);
6935     }
6936 }
6937
6938 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6939 {   // front-end-free part taken out of PieceMenuPopup
6940     int whichMenu; int xSqr, ySqr;
6941
6942     if(seekGraphUp) { // [HGM] seekgraph
6943         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6944         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6945         return -2;
6946     }
6947
6948     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6949          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6950         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6951         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6952         if(action == Press)   {
6953             originalFlip = flipView;
6954             flipView = !flipView; // temporarily flip board to see game from partners perspective
6955             DrawPosition(TRUE, partnerBoard);
6956             DisplayMessage(partnerStatus, "");
6957             partnerUp = TRUE;
6958         } else if(action == Release) {
6959             flipView = originalFlip;
6960             DrawPosition(TRUE, boards[currentMove]);
6961             partnerUp = FALSE;
6962         }
6963         return -2;
6964     }
6965
6966     xSqr = EventToSquare(x, BOARD_WIDTH);
6967     ySqr = EventToSquare(y, BOARD_HEIGHT);
6968     if (action == Release) {
6969         if(pieceSweep != EmptySquare) {
6970             EditPositionMenuEvent(pieceSweep, toX, toY);
6971             pieceSweep = EmptySquare;
6972         } else UnLoadPV(); // [HGM] pv
6973     }
6974     if (action != Press) return -2; // return code to be ignored
6975     switch (gameMode) {
6976       case IcsExamining:
6977         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6978       case EditPosition:
6979         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6980         if (xSqr < 0 || ySqr < 0) return -1;
6981         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6982         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6983         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6984         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6985         NextPiece(0);
6986         return -2;\r
6987       case IcsObserving:
6988         if(!appData.icsEngineAnalyze) return -1;
6989       case IcsPlayingWhite:
6990       case IcsPlayingBlack:
6991         if(!appData.zippyPlay) goto noZip;
6992       case AnalyzeMode:
6993       case AnalyzeFile:
6994       case MachinePlaysWhite:
6995       case MachinePlaysBlack:
6996       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6997         if (!appData.dropMenu) {
6998           LoadPV(x, y);
6999           return 2; // flag front-end to grab mouse events
7000         }
7001         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7002            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7003       case EditGame:
7004       noZip:
7005         if (xSqr < 0 || ySqr < 0) return -1;
7006         if (!appData.dropMenu || appData.testLegality &&
7007             gameInfo.variant != VariantBughouse &&
7008             gameInfo.variant != VariantCrazyhouse) return -1;
7009         whichMenu = 1; // drop menu
7010         break;
7011       default:
7012         return -1;
7013     }
7014
7015     if (((*fromX = xSqr) < 0) ||
7016         ((*fromY = ySqr) < 0)) {
7017         *fromX = *fromY = -1;
7018         return -1;
7019     }
7020     if (flipView)
7021       *fromX = BOARD_WIDTH - 1 - *fromX;
7022     else
7023       *fromY = BOARD_HEIGHT - 1 - *fromY;
7024
7025     return whichMenu;
7026 }
7027
7028 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7029 {
7030 //    char * hint = lastHint;
7031     FrontEndProgramStats stats;
7032
7033     stats.which = cps == &first ? 0 : 1;
7034     stats.depth = cpstats->depth;
7035     stats.nodes = cpstats->nodes;
7036     stats.score = cpstats->score;
7037     stats.time = cpstats->time;
7038     stats.pv = cpstats->movelist;
7039     stats.hint = lastHint;
7040     stats.an_move_index = 0;
7041     stats.an_move_count = 0;
7042
7043     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7044         stats.hint = cpstats->move_name;
7045         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7046         stats.an_move_count = cpstats->nr_moves;
7047     }
7048
7049     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
7050
7051     SetProgramStats( &stats );
7052 }
7053
7054 #define MAXPLAYERS 500
7055
7056 char *
7057 TourneyStandings(int display)
7058 {
7059     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7060     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7061     char result, *p, *names[MAXPLAYERS];
7062
7063     names[0] = p = strdup(appData.participants);
7064     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7065
7066     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7067
7068     while(result = appData.results[nr]) {
7069         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7070         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7071         wScore = bScore = 0;
7072         switch(result) {
7073           case '+': wScore = 2; break;
7074           case '-': bScore = 2; break;
7075           case '=': wScore = bScore = 1; break;
7076           case ' ':
7077           case '*': return NULL; // tourney not finished
7078         }
7079         score[w] += wScore;
7080         score[b] += bScore;
7081         games[w]++;
7082         games[b]++;
7083         nr++;
7084     }
7085     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7086     for(w=0; w<nPlayers; w++) {
7087         bScore = -1;
7088         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7089         ranking[w] = b; points[w] = bScore; score[b] = -2;
7090     }
7091     p = malloc(nPlayers*34+1);
7092     for(w=0; w<nPlayers && w<display; w++)
7093         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7094     free(names[0]);
7095     return p;
7096 }
7097
7098 void
7099 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7100 {       // count all piece types
7101         int p, f, r;
7102         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7103         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7104         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7105                 p = board[r][f];
7106                 pCnt[p]++;
7107                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7108                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7109                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7110                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7111                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7112                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7113         }
7114 }
7115
7116 int
7117 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7118 {
7119         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7120         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7121
7122         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7123         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7124         if(myPawns == 2 && nMine == 3) // KPP
7125             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7126         if(myPawns == 1 && nMine == 2) // KP
7127             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7128         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7129             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7130         if(myPawns) return FALSE;
7131         if(pCnt[WhiteRook+side])
7132             return pCnt[BlackRook-side] ||
7133                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7134                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7135                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7136         if(pCnt[WhiteCannon+side]) {
7137             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7138             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7139         }
7140         if(pCnt[WhiteKnight+side])
7141             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7142         return FALSE;
7143 }
7144
7145 int
7146 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7147 {
7148         VariantClass v = gameInfo.variant;
7149
7150         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7151         if(v == VariantShatranj) return TRUE; // always winnable through baring
7152         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7153         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7154
7155         if(v == VariantXiangqi) {
7156                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7157
7158                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7159                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7160                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7161                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7162                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7163                 if(stale) // we have at least one last-rank P plus perhaps C
7164                     return majors // KPKX
7165                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7166                 else // KCA*E*
7167                     return pCnt[WhiteFerz+side] // KCAK
7168                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7169                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7170                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7171
7172         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7173                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7174
7175                 if(nMine == 1) return FALSE; // bare King
7176                 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
7177                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7178                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7179                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7180                 if(pCnt[WhiteKnight+side])
7181                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7182                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7183                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7184                 if(nBishops)
7185                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7186                 if(pCnt[WhiteAlfil+side])
7187                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7188                 if(pCnt[WhiteWazir+side])
7189                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7190         }
7191
7192         return TRUE;
7193 }
7194
7195 int
7196 Adjudicate(ChessProgramState *cps)
7197 {       // [HGM] some adjudications useful with buggy engines
7198         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7199         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7200         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7201         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7202         int k, count = 0; static int bare = 1;
7203         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7204         Boolean canAdjudicate = !appData.icsActive;
7205
7206         // most tests only when we understand the game, i.e. legality-checking on
7207             if( appData.testLegality )
7208             {   /* [HGM] Some more adjudications for obstinate engines */
7209                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7210                 static int moveCount = 6;
7211                 ChessMove result;
7212                 char *reason = NULL;
7213
7214                 /* Count what is on board. */
7215                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7216
7217                 /* Some material-based adjudications that have to be made before stalemate test */
7218                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7219                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7220                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7221                      if(canAdjudicate && appData.checkMates) {
7222                          if(engineOpponent)
7223                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7224                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7225                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7226                          return 1;
7227                      }
7228                 }
7229
7230                 /* Bare King in Shatranj (loses) or Losers (wins) */
7231                 if( nrW == 1 || nrB == 1) {
7232                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7233                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7234                      if(canAdjudicate && appData.checkMates) {
7235                          if(engineOpponent)
7236                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7237                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7238                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7239                          return 1;
7240                      }
7241                   } else
7242                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7243                   {    /* bare King */
7244                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7245                         if(canAdjudicate && appData.checkMates) {
7246                             /* but only adjudicate if adjudication enabled */
7247                             if(engineOpponent)
7248                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7249                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7250                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7251                             return 1;
7252                         }
7253                   }
7254                 } else bare = 1;
7255
7256
7257             // don't wait for engine to announce game end if we can judge ourselves
7258             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7259               case MT_CHECK:
7260                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7261                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7262                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7263                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7264                             checkCnt++;
7265                         if(checkCnt >= 2) {
7266                             reason = "Xboard adjudication: 3rd check";
7267                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7268                             break;
7269                         }
7270                     }
7271                 }
7272               case MT_NONE:
7273               default:
7274                 break;
7275               case MT_STALEMATE:
7276               case MT_STAINMATE:
7277                 reason = "Xboard adjudication: Stalemate";
7278                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7279                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7280                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7281                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7282                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7283                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7284                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7285                                                                         EP_CHECKMATE : EP_WINS);
7286                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7287                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7288                 }
7289                 break;
7290               case MT_CHECKMATE:
7291                 reason = "Xboard adjudication: Checkmate";
7292                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7293                 break;
7294             }
7295
7296                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7297                     case EP_STALEMATE:
7298                         result = GameIsDrawn; break;
7299                     case EP_CHECKMATE:
7300                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7301                     case EP_WINS:
7302                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7303                     default:
7304                         result = EndOfFile;
7305                 }
7306                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7307                     if(engineOpponent)
7308                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7309                     GameEnds( result, reason, GE_XBOARD );
7310                     return 1;
7311                 }
7312
7313                 /* Next absolutely insufficient mating material. */
7314                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7315                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7316                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7317
7318                      /* always flag draws, for judging claims */
7319                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7320
7321                      if(canAdjudicate && appData.materialDraws) {
7322                          /* but only adjudicate them if adjudication enabled */
7323                          if(engineOpponent) {
7324                            SendToProgram("force\n", engineOpponent); // suppress reply
7325                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7326                          }
7327                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7328                          return 1;
7329                      }
7330                 }
7331
7332                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7333                 if(gameInfo.variant == VariantXiangqi ?
7334                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7335                  : nrW + nrB == 4 &&
7336                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7337                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7338                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7339                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7340                    ) ) {
7341                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7342                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7343                           if(engineOpponent) {
7344                             SendToProgram("force\n", engineOpponent); // suppress reply
7345                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7346                           }
7347                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7348                           return 1;
7349                      }
7350                 } else moveCount = 6;
7351             }
7352         if (appData.debugMode) { int i;
7353             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7354                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7355                     appData.drawRepeats);
7356             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7357               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7358
7359         }
7360
7361         // Repetition draws and 50-move rule can be applied independently of legality testing
7362
7363                 /* Check for rep-draws */
7364                 count = 0;
7365                 for(k = forwardMostMove-2;
7366                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7367                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7368                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7369                     k-=2)
7370                 {   int rights=0;
7371                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7372                         /* compare castling rights */
7373                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7374                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7375                                 rights++; /* King lost rights, while rook still had them */
7376                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7377                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7378                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7379                                    rights++; /* but at least one rook lost them */
7380                         }
7381                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7382                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7383                                 rights++;
7384                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7385                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7386                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7387                                    rights++;
7388                         }
7389                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7390                             && appData.drawRepeats > 1) {
7391                              /* adjudicate after user-specified nr of repeats */
7392                              int result = GameIsDrawn;
7393                              char *details = "XBoard adjudication: repetition draw";
7394                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7395                                 // [HGM] xiangqi: check for forbidden perpetuals
7396                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7397                                 for(m=forwardMostMove; m>k; m-=2) {
7398                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7399                                         ourPerpetual = 0; // the current mover did not always check
7400                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7401                                         hisPerpetual = 0; // the opponent did not always check
7402                                 }
7403                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7404                                                                         ourPerpetual, hisPerpetual);
7405                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7406                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7407                                     details = "Xboard adjudication: perpetual checking";
7408                                 } else
7409                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7410                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7411                                 } else
7412                                 // Now check for perpetual chases
7413                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7414                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7415                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7416                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7417                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7418                                         details = "Xboard adjudication: perpetual chasing";
7419                                     } else
7420                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7421                                         break; // Abort repetition-checking loop.
7422                                 }
7423                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7424                              }
7425                              if(engineOpponent) {
7426                                SendToProgram("force\n", engineOpponent); // suppress reply
7427                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7428                              }
7429                              GameEnds( result, details, GE_XBOARD );
7430                              return 1;
7431                         }
7432                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7433                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7434                     }
7435                 }
7436
7437                 /* Now we test for 50-move draws. Determine ply count */
7438                 count = forwardMostMove;
7439                 /* look for last irreversble move */
7440                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7441                     count--;
7442                 /* if we hit starting position, add initial plies */
7443                 if( count == backwardMostMove )
7444                     count -= initialRulePlies;
7445                 count = forwardMostMove - count;
7446                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7447                         // adjust reversible move counter for checks in Xiangqi
7448                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7449                         if(i < backwardMostMove) i = backwardMostMove;
7450                         while(i <= forwardMostMove) {
7451                                 lastCheck = inCheck; // check evasion does not count
7452                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7453                                 if(inCheck || lastCheck) count--; // check does not count
7454                                 i++;
7455                         }
7456                 }
7457                 if( count >= 100)
7458                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7459                          /* this is used to judge if draw claims are legal */
7460                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7461                          if(engineOpponent) {
7462                            SendToProgram("force\n", engineOpponent); // suppress reply
7463                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7464                          }
7465                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7466                          return 1;
7467                 }
7468
7469                 /* if draw offer is pending, treat it as a draw claim
7470                  * when draw condition present, to allow engines a way to
7471                  * claim draws before making their move to avoid a race
7472                  * condition occurring after their move
7473                  */
7474                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7475                          char *p = NULL;
7476                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7477                              p = "Draw claim: 50-move rule";
7478                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7479                              p = "Draw claim: 3-fold repetition";
7480                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7481                              p = "Draw claim: insufficient mating material";
7482                          if( p != NULL && canAdjudicate) {
7483                              if(engineOpponent) {
7484                                SendToProgram("force\n", engineOpponent); // suppress reply
7485                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7486                              }
7487                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7488                              return 1;
7489                          }
7490                 }
7491
7492                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7493                     if(engineOpponent) {
7494                       SendToProgram("force\n", engineOpponent); // suppress reply
7495                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7496                     }
7497                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7498                     return 1;
7499                 }
7500         return 0;
7501 }
7502
7503 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7504 {   // [HGM] book: this routine intercepts moves to simulate book replies
7505     char *bookHit = NULL;
7506
7507     //first determine if the incoming move brings opponent into his book
7508     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7509         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7510     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7511     if(bookHit != NULL && !cps->bookSuspend) {
7512         // make sure opponent is not going to reply after receiving move to book position
7513         SendToProgram("force\n", cps);
7514         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7515     }
7516     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7517     // now arrange restart after book miss
7518     if(bookHit) {
7519         // after a book hit we never send 'go', and the code after the call to this routine
7520         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7521         char buf[MSG_SIZ];
7522         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7523         SendToProgram(buf, cps);
7524         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7525     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7526         SendToProgram("go\n", cps);
7527         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7528     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7529         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7530             SendToProgram("go\n", cps);
7531         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7532     }
7533     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7534 }
7535
7536 char *savedMessage;
7537 ChessProgramState *savedState;
7538 void DeferredBookMove(void)
7539 {
7540         if(savedState->lastPing != savedState->lastPong)
7541                     ScheduleDelayedEvent(DeferredBookMove, 10);
7542         else
7543         HandleMachineMove(savedMessage, savedState);
7544 }
7545
7546 void
7547 HandleMachineMove(message, cps)
7548      char *message;
7549      ChessProgramState *cps;
7550 {
7551     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7552     char realname[MSG_SIZ];
7553     int fromX, fromY, toX, toY;
7554     ChessMove moveType;
7555     char promoChar;
7556     char *p;
7557     int machineWhite;
7558     char *bookHit;
7559
7560     cps->userError = 0;
7561
7562 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7563     /*
7564      * Kludge to ignore BEL characters
7565      */
7566     while (*message == '\007') message++;
7567
7568     /*
7569      * [HGM] engine debug message: ignore lines starting with '#' character
7570      */
7571     if(cps->debug && *message == '#') return;
7572
7573     /*
7574      * Look for book output
7575      */
7576     if (cps == &first && bookRequested) {
7577         if (message[0] == '\t' || message[0] == ' ') {
7578             /* Part of the book output is here; append it */
7579             strcat(bookOutput, message);
7580             strcat(bookOutput, "  \n");
7581             return;
7582         } else if (bookOutput[0] != NULLCHAR) {
7583             /* All of book output has arrived; display it */
7584             char *p = bookOutput;
7585             while (*p != NULLCHAR) {
7586                 if (*p == '\t') *p = ' ';
7587                 p++;
7588             }
7589             DisplayInformation(bookOutput);
7590             bookRequested = FALSE;
7591             /* Fall through to parse the current output */
7592         }
7593     }
7594
7595     /*
7596      * Look for machine move.
7597      */
7598     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7599         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7600     {
7601         /* This method is only useful on engines that support ping */
7602         if (cps->lastPing != cps->lastPong) {
7603           if (gameMode == BeginningOfGame) {
7604             /* Extra move from before last new; ignore */
7605             if (appData.debugMode) {
7606                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7607             }
7608           } else {
7609             if (appData.debugMode) {
7610                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7611                         cps->which, gameMode);
7612             }
7613
7614             SendToProgram("undo\n", cps);
7615           }
7616           return;
7617         }
7618
7619         switch (gameMode) {
7620           case BeginningOfGame:
7621             /* Extra move from before last reset; ignore */
7622             if (appData.debugMode) {
7623                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7624             }
7625             return;
7626
7627           case EndOfGame:
7628           case IcsIdle:
7629           default:
7630             /* Extra move after we tried to stop.  The mode test is
7631                not a reliable way of detecting this problem, but it's
7632                the best we can do on engines that don't support ping.
7633             */
7634             if (appData.debugMode) {
7635                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7636                         cps->which, gameMode);
7637             }
7638             SendToProgram("undo\n", cps);
7639             return;
7640
7641           case MachinePlaysWhite:
7642           case IcsPlayingWhite:
7643             machineWhite = TRUE;
7644             break;
7645
7646           case MachinePlaysBlack:
7647           case IcsPlayingBlack:
7648             machineWhite = FALSE;
7649             break;
7650
7651           case TwoMachinesPlay:
7652             machineWhite = (cps->twoMachinesColor[0] == 'w');
7653             break;
7654         }
7655         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7656             if (appData.debugMode) {
7657                 fprintf(debugFP,
7658                         "Ignoring move out of turn by %s, gameMode %d"
7659                         ", forwardMost %d\n",
7660                         cps->which, gameMode, forwardMostMove);
7661             }
7662             return;
7663         }
7664
7665     if (appData.debugMode) { int f = forwardMostMove;
7666         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7667                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7668                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7669     }
7670         if(cps->alphaRank) AlphaRank(machineMove, 4);
7671         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7672                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7673             /* Machine move could not be parsed; ignore it. */
7674           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7675                     machineMove, _(cps->which));
7676             DisplayError(buf1, 0);
7677             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7678                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7679             if (gameMode == TwoMachinesPlay) {
7680               GameEnds(machineWhite ? BlackWins : WhiteWins,
7681                        buf1, GE_XBOARD);
7682             }
7683             return;
7684         }
7685
7686         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7687         /* So we have to redo legality test with true e.p. status here,  */
7688         /* to make sure an illegal e.p. capture does not slip through,   */
7689         /* to cause a forfeit on a justified illegal-move complaint      */
7690         /* of the opponent.                                              */
7691         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7692            ChessMove moveType;
7693            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7694                              fromY, fromX, toY, toX, promoChar);
7695             if (appData.debugMode) {
7696                 int i;
7697                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7698                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7699                 fprintf(debugFP, "castling rights\n");
7700             }
7701             if(moveType == IllegalMove) {
7702               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7703                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7704                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7705                            buf1, GE_XBOARD);
7706                 return;
7707            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7708            /* [HGM] Kludge to handle engines that send FRC-style castling
7709               when they shouldn't (like TSCP-Gothic) */
7710            switch(moveType) {
7711              case WhiteASideCastleFR:
7712              case BlackASideCastleFR:
7713                toX+=2;
7714                currentMoveString[2]++;
7715                break;
7716              case WhiteHSideCastleFR:
7717              case BlackHSideCastleFR:
7718                toX--;
7719                currentMoveString[2]--;
7720                break;
7721              default: ; // nothing to do, but suppresses warning of pedantic compilers
7722            }
7723         }
7724         hintRequested = FALSE;
7725         lastHint[0] = NULLCHAR;
7726         bookRequested = FALSE;
7727         /* Program may be pondering now */
7728         cps->maybeThinking = TRUE;
7729         if (cps->sendTime == 2) cps->sendTime = 1;
7730         if (cps->offeredDraw) cps->offeredDraw--;
7731
7732         /* [AS] Save move info*/
7733         pvInfoList[ forwardMostMove ].score = programStats.score;
7734         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7735         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7736
7737         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7738
7739         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7740         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7741             int count = 0;
7742
7743             while( count < adjudicateLossPlies ) {
7744                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7745
7746                 if( count & 1 ) {
7747                     score = -score; /* Flip score for winning side */
7748                 }
7749
7750                 if( score > adjudicateLossThreshold ) {
7751                     break;
7752                 }
7753
7754                 count++;
7755             }
7756
7757             if( count >= adjudicateLossPlies ) {
7758                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7759
7760                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7761                     "Xboard adjudication",
7762                     GE_XBOARD );
7763
7764                 return;
7765             }
7766         }
7767
7768         if(Adjudicate(cps)) {
7769             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7770             return; // [HGM] adjudicate: for all automatic game ends
7771         }
7772
7773 #if ZIPPY
7774         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7775             first.initDone) {
7776           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7777                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7778                 SendToICS("draw ");
7779                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7780           }
7781           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7782           ics_user_moved = 1;
7783           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7784                 char buf[3*MSG_SIZ];
7785
7786                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7787                         programStats.score / 100.,
7788                         programStats.depth,
7789                         programStats.time / 100.,
7790                         (unsigned int)programStats.nodes,
7791                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7792                         programStats.movelist);
7793                 SendToICS(buf);
7794 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7795           }
7796         }
7797 #endif
7798
7799         /* [AS] Clear stats for next move */
7800         ClearProgramStats();
7801         thinkOutput[0] = NULLCHAR;
7802         hiddenThinkOutputState = 0;
7803
7804         bookHit = NULL;
7805         if (gameMode == TwoMachinesPlay) {
7806             /* [HGM] relaying draw offers moved to after reception of move */
7807             /* and interpreting offer as claim if it brings draw condition */
7808             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7809                 SendToProgram("draw\n", cps->other);
7810             }
7811             if (cps->other->sendTime) {
7812                 SendTimeRemaining(cps->other,
7813                                   cps->other->twoMachinesColor[0] == 'w');
7814             }
7815             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7816             if (firstMove && !bookHit) {
7817                 firstMove = FALSE;
7818                 if (cps->other->useColors) {
7819                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7820                 }
7821                 SendToProgram("go\n", cps->other);
7822             }
7823             cps->other->maybeThinking = TRUE;
7824         }
7825
7826         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7827
7828         if (!pausing && appData.ringBellAfterMoves) {
7829             RingBell();
7830         }
7831
7832         /*
7833          * Reenable menu items that were disabled while
7834          * machine was thinking
7835          */
7836         if (gameMode != TwoMachinesPlay)
7837             SetUserThinkingEnables();
7838
7839         // [HGM] book: after book hit opponent has received move and is now in force mode
7840         // force the book reply into it, and then fake that it outputted this move by jumping
7841         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7842         if(bookHit) {
7843                 static char bookMove[MSG_SIZ]; // a bit generous?
7844
7845                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7846                 strcat(bookMove, bookHit);
7847                 message = bookMove;
7848                 cps = cps->other;
7849                 programStats.nodes = programStats.depth = programStats.time =
7850                 programStats.score = programStats.got_only_move = 0;
7851                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7852
7853                 if(cps->lastPing != cps->lastPong) {
7854                     savedMessage = message; // args for deferred call
7855                     savedState = cps;
7856                     ScheduleDelayedEvent(DeferredBookMove, 10);
7857                     return;
7858                 }
7859                 goto FakeBookMove;
7860         }
7861
7862         return;
7863     }
7864
7865     /* Set special modes for chess engines.  Later something general
7866      *  could be added here; for now there is just one kludge feature,
7867      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7868      *  when "xboard" is given as an interactive command.
7869      */
7870     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7871         cps->useSigint = FALSE;
7872         cps->useSigterm = FALSE;
7873     }
7874     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7875       ParseFeatures(message+8, cps);
7876       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7877     }
7878
7879     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7880       int dummy, s=6; char buf[MSG_SIZ];
7881       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7882       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7883       ParseFEN(boards[0], &dummy, message+s);
7884       DrawPosition(TRUE, boards[0]);
7885       startedFromSetupPosition = TRUE;
7886       return;
7887     }
7888     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7889      * want this, I was asked to put it in, and obliged.
7890      */
7891     if (!strncmp(message, "setboard ", 9)) {
7892         Board initial_position;
7893
7894         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7895
7896         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7897             DisplayError(_("Bad FEN received from engine"), 0);
7898             return ;
7899         } else {
7900            Reset(TRUE, FALSE);
7901            CopyBoard(boards[0], initial_position);
7902            initialRulePlies = FENrulePlies;
7903            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7904            else gameMode = MachinePlaysBlack;
7905            DrawPosition(FALSE, boards[currentMove]);
7906         }
7907         return;
7908     }
7909
7910     /*
7911      * Look for communication commands
7912      */
7913     if (!strncmp(message, "telluser ", 9)) {
7914         if(message[9] == '\\' && message[10] == '\\')
7915             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7916         DisplayNote(message + 9);
7917         return;
7918     }
7919     if (!strncmp(message, "tellusererror ", 14)) {
7920         cps->userError = 1;
7921         if(message[14] == '\\' && message[15] == '\\')
7922             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7923         DisplayError(message + 14, 0);
7924         return;
7925     }
7926     if (!strncmp(message, "tellopponent ", 13)) {
7927       if (appData.icsActive) {
7928         if (loggedOn) {
7929           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7930           SendToICS(buf1);
7931         }
7932       } else {
7933         DisplayNote(message + 13);
7934       }
7935       return;
7936     }
7937     if (!strncmp(message, "tellothers ", 11)) {
7938       if (appData.icsActive) {
7939         if (loggedOn) {
7940           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7941           SendToICS(buf1);
7942         }
7943       }
7944       return;
7945     }
7946     if (!strncmp(message, "tellall ", 8)) {
7947       if (appData.icsActive) {
7948         if (loggedOn) {
7949           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7950           SendToICS(buf1);
7951         }
7952       } else {
7953         DisplayNote(message + 8);
7954       }
7955       return;
7956     }
7957     if (strncmp(message, "warning", 7) == 0) {
7958         /* Undocumented feature, use tellusererror in new code */
7959         DisplayError(message, 0);
7960         return;
7961     }
7962     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7963         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7964         strcat(realname, " query");
7965         AskQuestion(realname, buf2, buf1, cps->pr);
7966         return;
7967     }
7968     /* Commands from the engine directly to ICS.  We don't allow these to be
7969      *  sent until we are logged on. Crafty kibitzes have been known to
7970      *  interfere with the login process.
7971      */
7972     if (loggedOn) {
7973         if (!strncmp(message, "tellics ", 8)) {
7974             SendToICS(message + 8);
7975             SendToICS("\n");
7976             return;
7977         }
7978         if (!strncmp(message, "tellicsnoalias ", 15)) {
7979             SendToICS(ics_prefix);
7980             SendToICS(message + 15);
7981             SendToICS("\n");
7982             return;
7983         }
7984         /* The following are for backward compatibility only */
7985         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7986             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7987             SendToICS(ics_prefix);
7988             SendToICS(message);
7989             SendToICS("\n");
7990             return;
7991         }
7992     }
7993     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7994         return;
7995     }
7996     /*
7997      * If the move is illegal, cancel it and redraw the board.
7998      * Also deal with other error cases.  Matching is rather loose
7999      * here to accommodate engines written before the spec.
8000      */
8001     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8002         strncmp(message, "Error", 5) == 0) {
8003         if (StrStr(message, "name") ||
8004             StrStr(message, "rating") || StrStr(message, "?") ||
8005             StrStr(message, "result") || StrStr(message, "board") ||
8006             StrStr(message, "bk") || StrStr(message, "computer") ||
8007             StrStr(message, "variant") || StrStr(message, "hint") ||
8008             StrStr(message, "random") || StrStr(message, "depth") ||
8009             StrStr(message, "accepted")) {
8010             return;
8011         }
8012         if (StrStr(message, "protover")) {
8013           /* Program is responding to input, so it's apparently done
8014              initializing, and this error message indicates it is
8015              protocol version 1.  So we don't need to wait any longer
8016              for it to initialize and send feature commands. */
8017           FeatureDone(cps, 1);
8018           cps->protocolVersion = 1;
8019           return;
8020         }
8021         cps->maybeThinking = FALSE;
8022
8023         if (StrStr(message, "draw")) {
8024             /* Program doesn't have "draw" command */
8025             cps->sendDrawOffers = 0;
8026             return;
8027         }
8028         if (cps->sendTime != 1 &&
8029             (StrStr(message, "time") || StrStr(message, "otim"))) {
8030           /* Program apparently doesn't have "time" or "otim" command */
8031           cps->sendTime = 0;
8032           return;
8033         }
8034         if (StrStr(message, "analyze")) {
8035             cps->analysisSupport = FALSE;
8036             cps->analyzing = FALSE;
8037             Reset(FALSE, TRUE);
8038             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8039             DisplayError(buf2, 0);
8040             return;
8041         }
8042         if (StrStr(message, "(no matching move)st")) {
8043           /* Special kludge for GNU Chess 4 only */
8044           cps->stKludge = TRUE;
8045           SendTimeControl(cps, movesPerSession, timeControl,
8046                           timeIncrement, appData.searchDepth,
8047                           searchTime);
8048           return;
8049         }
8050         if (StrStr(message, "(no matching move)sd")) {
8051           /* Special kludge for GNU Chess 4 only */
8052           cps->sdKludge = TRUE;
8053           SendTimeControl(cps, movesPerSession, timeControl,
8054                           timeIncrement, appData.searchDepth,
8055                           searchTime);
8056           return;
8057         }
8058         if (!StrStr(message, "llegal")) {
8059             return;
8060         }
8061         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8062             gameMode == IcsIdle) return;
8063         if (forwardMostMove <= backwardMostMove) return;
8064         if (pausing) PauseEvent();
8065       if(appData.forceIllegal) {
8066             // [HGM] illegal: machine refused move; force position after move into it
8067           SendToProgram("force\n", cps);
8068           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8069                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8070                 // when black is to move, while there might be nothing on a2 or black
8071                 // might already have the move. So send the board as if white has the move.
8072                 // But first we must change the stm of the engine, as it refused the last move
8073                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8074                 if(WhiteOnMove(forwardMostMove)) {
8075                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8076                     SendBoard(cps, forwardMostMove); // kludgeless board
8077                 } else {
8078                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8079                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8080                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8081                 }
8082           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8083             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8084                  gameMode == TwoMachinesPlay)
8085               SendToProgram("go\n", cps);
8086             return;
8087       } else
8088         if (gameMode == PlayFromGameFile) {
8089             /* Stop reading this game file */
8090             gameMode = EditGame;
8091             ModeHighlight();
8092         }
8093         /* [HGM] illegal-move claim should forfeit game when Xboard */
8094         /* only passes fully legal moves                            */
8095         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8096             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8097                                 "False illegal-move claim", GE_XBOARD );
8098             return; // do not take back move we tested as valid
8099         }
8100         currentMove = forwardMostMove-1;
8101         DisplayMove(currentMove-1); /* before DisplayMoveError */
8102         SwitchClocks(forwardMostMove-1); // [HGM] race
8103         DisplayBothClocks();
8104         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8105                 parseList[currentMove], _(cps->which));
8106         DisplayMoveError(buf1);
8107         DrawPosition(FALSE, boards[currentMove]);
8108         return;
8109     }
8110     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8111         /* Program has a broken "time" command that
8112            outputs a string not ending in newline.
8113            Don't use it. */
8114         cps->sendTime = 0;
8115     }
8116
8117     /*
8118      * If chess program startup fails, exit with an error message.
8119      * Attempts to recover here are futile.
8120      */
8121     if ((StrStr(message, "unknown host") != NULL)
8122         || (StrStr(message, "No remote directory") != NULL)
8123         || (StrStr(message, "not found") != NULL)
8124         || (StrStr(message, "No such file") != NULL)
8125         || (StrStr(message, "can't alloc") != NULL)
8126         || (StrStr(message, "Permission denied") != NULL)) {
8127
8128         cps->maybeThinking = FALSE;
8129         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8130                 _(cps->which), cps->program, cps->host, message);
8131         RemoveInputSource(cps->isr);
8132         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8133             if(cps == &first) appData.noChessProgram = TRUE;
8134             DisplayError(buf1, 0);
8135         }
8136         return;
8137     }
8138
8139     /*
8140      * Look for hint output
8141      */
8142     if (sscanf(message, "Hint: %s", buf1) == 1) {
8143         if (cps == &first && hintRequested) {
8144             hintRequested = FALSE;
8145             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8146                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8147                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8148                                     PosFlags(forwardMostMove),
8149                                     fromY, fromX, toY, toX, promoChar, buf1);
8150                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8151                 DisplayInformation(buf2);
8152             } else {
8153                 /* Hint move could not be parsed!? */
8154               snprintf(buf2, sizeof(buf2),
8155                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8156                         buf1, _(cps->which));
8157                 DisplayError(buf2, 0);
8158             }
8159         } else {
8160           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8161         }
8162         return;
8163     }
8164
8165     /*
8166      * Ignore other messages if game is not in progress
8167      */
8168     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8169         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8170
8171     /*
8172      * look for win, lose, draw, or draw offer
8173      */
8174     if (strncmp(message, "1-0", 3) == 0) {
8175         char *p, *q, *r = "";
8176         p = strchr(message, '{');
8177         if (p) {
8178             q = strchr(p, '}');
8179             if (q) {
8180                 *q = NULLCHAR;
8181                 r = p + 1;
8182             }
8183         }
8184         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8185         return;
8186     } else if (strncmp(message, "0-1", 3) == 0) {
8187         char *p, *q, *r = "";
8188         p = strchr(message, '{');
8189         if (p) {
8190             q = strchr(p, '}');
8191             if (q) {
8192                 *q = NULLCHAR;
8193                 r = p + 1;
8194             }
8195         }
8196         /* Kludge for Arasan 4.1 bug */
8197         if (strcmp(r, "Black resigns") == 0) {
8198             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8199             return;
8200         }
8201         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8202         return;
8203     } else if (strncmp(message, "1/2", 3) == 0) {
8204         char *p, *q, *r = "";
8205         p = strchr(message, '{');
8206         if (p) {
8207             q = strchr(p, '}');
8208             if (q) {
8209                 *q = NULLCHAR;
8210                 r = p + 1;
8211             }
8212         }
8213
8214         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8215         return;
8216
8217     } else if (strncmp(message, "White resign", 12) == 0) {
8218         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8219         return;
8220     } else if (strncmp(message, "Black resign", 12) == 0) {
8221         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8222         return;
8223     } else if (strncmp(message, "White matches", 13) == 0 ||
8224                strncmp(message, "Black matches", 13) == 0   ) {
8225         /* [HGM] ignore GNUShogi noises */
8226         return;
8227     } else if (strncmp(message, "White", 5) == 0 &&
8228                message[5] != '(' &&
8229                StrStr(message, "Black") == NULL) {
8230         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8231         return;
8232     } else if (strncmp(message, "Black", 5) == 0 &&
8233                message[5] != '(') {
8234         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8235         return;
8236     } else if (strcmp(message, "resign") == 0 ||
8237                strcmp(message, "computer resigns") == 0) {
8238         switch (gameMode) {
8239           case MachinePlaysBlack:
8240           case IcsPlayingBlack:
8241             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8242             break;
8243           case MachinePlaysWhite:
8244           case IcsPlayingWhite:
8245             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8246             break;
8247           case TwoMachinesPlay:
8248             if (cps->twoMachinesColor[0] == 'w')
8249               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8250             else
8251               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8252             break;
8253           default:
8254             /* can't happen */
8255             break;
8256         }
8257         return;
8258     } else if (strncmp(message, "opponent mates", 14) == 0) {
8259         switch (gameMode) {
8260           case MachinePlaysBlack:
8261           case IcsPlayingBlack:
8262             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8263             break;
8264           case MachinePlaysWhite:
8265           case IcsPlayingWhite:
8266             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8267             break;
8268           case TwoMachinesPlay:
8269             if (cps->twoMachinesColor[0] == 'w')
8270               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8271             else
8272               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8273             break;
8274           default:
8275             /* can't happen */
8276             break;
8277         }
8278         return;
8279     } else if (strncmp(message, "computer mates", 14) == 0) {
8280         switch (gameMode) {
8281           case MachinePlaysBlack:
8282           case IcsPlayingBlack:
8283             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8284             break;
8285           case MachinePlaysWhite:
8286           case IcsPlayingWhite:
8287             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8288             break;
8289           case TwoMachinesPlay:
8290             if (cps->twoMachinesColor[0] == 'w')
8291               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8292             else
8293               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8294             break;
8295           default:
8296             /* can't happen */
8297             break;
8298         }
8299         return;
8300     } else if (strncmp(message, "checkmate", 9) == 0) {
8301         if (WhiteOnMove(forwardMostMove)) {
8302             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8303         } else {
8304             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8305         }
8306         return;
8307     } else if (strstr(message, "Draw") != NULL ||
8308                strstr(message, "game is a draw") != NULL) {
8309         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8310         return;
8311     } else if (strstr(message, "offer") != NULL &&
8312                strstr(message, "draw") != NULL) {
8313 #if ZIPPY
8314         if (appData.zippyPlay && first.initDone) {
8315             /* Relay offer to ICS */
8316             SendToICS(ics_prefix);
8317             SendToICS("draw\n");
8318         }
8319 #endif
8320         cps->offeredDraw = 2; /* valid until this engine moves twice */
8321         if (gameMode == TwoMachinesPlay) {
8322             if (cps->other->offeredDraw) {
8323                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8324             /* [HGM] in two-machine mode we delay relaying draw offer      */
8325             /* until after we also have move, to see if it is really claim */
8326             }
8327         } else if (gameMode == MachinePlaysWhite ||
8328                    gameMode == MachinePlaysBlack) {
8329           if (userOfferedDraw) {
8330             DisplayInformation(_("Machine accepts your draw offer"));
8331             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8332           } else {
8333             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8334           }
8335         }
8336     }
8337
8338
8339     /*
8340      * Look for thinking output
8341      */
8342     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8343           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8344                                 ) {
8345         int plylev, mvleft, mvtot, curscore, time;
8346         char mvname[MOVE_LEN];
8347         u64 nodes; // [DM]
8348         char plyext;
8349         int ignore = FALSE;
8350         int prefixHint = FALSE;
8351         mvname[0] = NULLCHAR;
8352
8353         switch (gameMode) {
8354           case MachinePlaysBlack:
8355           case IcsPlayingBlack:
8356             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8357             break;
8358           case MachinePlaysWhite:
8359           case IcsPlayingWhite:
8360             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8361             break;
8362           case AnalyzeMode:
8363           case AnalyzeFile:
8364             break;
8365           case IcsObserving: /* [DM] icsEngineAnalyze */
8366             if (!appData.icsEngineAnalyze) ignore = TRUE;
8367             break;
8368           case TwoMachinesPlay:
8369             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8370                 ignore = TRUE;
8371             }
8372             break;
8373           default:
8374             ignore = TRUE;
8375             break;
8376         }
8377
8378         if (!ignore) {
8379             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8380             buf1[0] = NULLCHAR;
8381             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8382                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8383
8384                 if (plyext != ' ' && plyext != '\t') {
8385                     time *= 100;
8386                 }
8387
8388                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8389                 if( cps->scoreIsAbsolute &&
8390                     ( gameMode == MachinePlaysBlack ||
8391                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8392                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8393                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8394                      !WhiteOnMove(currentMove)
8395                     ) )
8396                 {
8397                     curscore = -curscore;
8398                 }
8399
8400
8401                 tempStats.depth = plylev;
8402                 tempStats.nodes = nodes;
8403                 tempStats.time = time;
8404                 tempStats.score = curscore;
8405                 tempStats.got_only_move = 0;
8406
8407                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8408                         int ticklen;
8409
8410                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8411                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8412                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8413                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8414                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8415                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8416                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8417                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8418                 }
8419
8420                 /* Buffer overflow protection */
8421                 if (buf1[0] != NULLCHAR) {
8422                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8423                         && appData.debugMode) {
8424                         fprintf(debugFP,
8425                                 "PV is too long; using the first %u bytes.\n",
8426                                 (unsigned) sizeof(tempStats.movelist) - 1);
8427                     }
8428
8429                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8430                 } else {
8431                     sprintf(tempStats.movelist, " no PV\n");
8432                 }
8433
8434                 if (tempStats.seen_stat) {
8435                     tempStats.ok_to_send = 1;
8436                 }
8437
8438                 if (strchr(tempStats.movelist, '(') != NULL) {
8439                     tempStats.line_is_book = 1;
8440                     tempStats.nr_moves = 0;
8441                     tempStats.moves_left = 0;
8442                 } else {
8443                     tempStats.line_is_book = 0;
8444                 }
8445
8446                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8447                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8448
8449                 SendProgramStatsToFrontend( cps, &tempStats );
8450
8451                 /*
8452                     [AS] Protect the thinkOutput buffer from overflow... this
8453                     is only useful if buf1 hasn't overflowed first!
8454                 */
8455                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8456                          plylev,
8457                          (gameMode == TwoMachinesPlay ?
8458                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8459                          ((double) curscore) / 100.0,
8460                          prefixHint ? lastHint : "",
8461                          prefixHint ? " " : "" );
8462
8463                 if( buf1[0] != NULLCHAR ) {
8464                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8465
8466                     if( strlen(buf1) > max_len ) {
8467                         if( appData.debugMode) {
8468                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8469                         }
8470                         buf1[max_len+1] = '\0';
8471                     }
8472
8473                     strcat( thinkOutput, buf1 );
8474                 }
8475
8476                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8477                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8478                     DisplayMove(currentMove - 1);
8479                 }
8480                 return;
8481
8482             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8483                 /* crafty (9.25+) says "(only move) <move>"
8484                  * if there is only 1 legal move
8485                  */
8486                 sscanf(p, "(only move) %s", buf1);
8487                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8488                 sprintf(programStats.movelist, "%s (only move)", buf1);
8489                 programStats.depth = 1;
8490                 programStats.nr_moves = 1;
8491                 programStats.moves_left = 1;
8492                 programStats.nodes = 1;
8493                 programStats.time = 1;
8494                 programStats.got_only_move = 1;
8495
8496                 /* Not really, but we also use this member to
8497                    mean "line isn't going to change" (Crafty
8498                    isn't searching, so stats won't change) */
8499                 programStats.line_is_book = 1;
8500
8501                 SendProgramStatsToFrontend( cps, &programStats );
8502
8503                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8504                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8505                     DisplayMove(currentMove - 1);
8506                 }
8507                 return;
8508             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8509                               &time, &nodes, &plylev, &mvleft,
8510                               &mvtot, mvname) >= 5) {
8511                 /* The stat01: line is from Crafty (9.29+) in response
8512                    to the "." command */
8513                 programStats.seen_stat = 1;
8514                 cps->maybeThinking = TRUE;
8515
8516                 if (programStats.got_only_move || !appData.periodicUpdates)
8517                   return;
8518
8519                 programStats.depth = plylev;
8520                 programStats.time = time;
8521                 programStats.nodes = nodes;
8522                 programStats.moves_left = mvleft;
8523                 programStats.nr_moves = mvtot;
8524                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8525                 programStats.ok_to_send = 1;
8526                 programStats.movelist[0] = '\0';
8527
8528                 SendProgramStatsToFrontend( cps, &programStats );
8529
8530                 return;
8531
8532             } else if (strncmp(message,"++",2) == 0) {
8533                 /* Crafty 9.29+ outputs this */
8534                 programStats.got_fail = 2;
8535                 return;
8536
8537             } else if (strncmp(message,"--",2) == 0) {
8538                 /* Crafty 9.29+ outputs this */
8539                 programStats.got_fail = 1;
8540                 return;
8541
8542             } else if (thinkOutput[0] != NULLCHAR &&
8543                        strncmp(message, "    ", 4) == 0) {
8544                 unsigned message_len;
8545
8546                 p = message;
8547                 while (*p && *p == ' ') p++;
8548
8549                 message_len = strlen( p );
8550
8551                 /* [AS] Avoid buffer overflow */
8552                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8553                     strcat(thinkOutput, " ");
8554                     strcat(thinkOutput, p);
8555                 }
8556
8557                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8558                     strcat(programStats.movelist, " ");
8559                     strcat(programStats.movelist, p);
8560                 }
8561
8562                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8563                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8564                     DisplayMove(currentMove - 1);
8565                 }
8566                 return;
8567             }
8568         }
8569         else {
8570             buf1[0] = NULLCHAR;
8571
8572             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8573                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8574             {
8575                 ChessProgramStats cpstats;
8576
8577                 if (plyext != ' ' && plyext != '\t') {
8578                     time *= 100;
8579                 }
8580
8581                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8582                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8583                     curscore = -curscore;
8584                 }
8585
8586                 cpstats.depth = plylev;
8587                 cpstats.nodes = nodes;
8588                 cpstats.time = time;
8589                 cpstats.score = curscore;
8590                 cpstats.got_only_move = 0;
8591                 cpstats.movelist[0] = '\0';
8592
8593                 if (buf1[0] != NULLCHAR) {
8594                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8595                 }
8596
8597                 cpstats.ok_to_send = 0;
8598                 cpstats.line_is_book = 0;
8599                 cpstats.nr_moves = 0;
8600                 cpstats.moves_left = 0;
8601
8602                 SendProgramStatsToFrontend( cps, &cpstats );
8603             }
8604         }
8605     }
8606 }
8607
8608
8609 /* Parse a game score from the character string "game", and
8610    record it as the history of the current game.  The game
8611    score is NOT assumed to start from the standard position.
8612    The display is not updated in any way.
8613    */
8614 void
8615 ParseGameHistory(game)
8616      char *game;
8617 {
8618     ChessMove moveType;
8619     int fromX, fromY, toX, toY, boardIndex;
8620     char promoChar;
8621     char *p, *q;
8622     char buf[MSG_SIZ];
8623
8624     if (appData.debugMode)
8625       fprintf(debugFP, "Parsing game history: %s\n", game);
8626
8627     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8628     gameInfo.site = StrSave(appData.icsHost);
8629     gameInfo.date = PGNDate();
8630     gameInfo.round = StrSave("-");
8631
8632     /* Parse out names of players */
8633     while (*game == ' ') game++;
8634     p = buf;
8635     while (*game != ' ') *p++ = *game++;
8636     *p = NULLCHAR;
8637     gameInfo.white = StrSave(buf);
8638     while (*game == ' ') game++;
8639     p = buf;
8640     while (*game != ' ' && *game != '\n') *p++ = *game++;
8641     *p = NULLCHAR;
8642     gameInfo.black = StrSave(buf);
8643
8644     /* Parse moves */
8645     boardIndex = blackPlaysFirst ? 1 : 0;
8646     yynewstr(game);
8647     for (;;) {
8648         yyboardindex = boardIndex;
8649         moveType = (ChessMove) Myylex();
8650         switch (moveType) {
8651           case IllegalMove:             /* maybe suicide chess, etc. */
8652   if (appData.debugMode) {
8653     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8654     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8655     setbuf(debugFP, NULL);
8656   }
8657           case WhitePromotion:
8658           case BlackPromotion:
8659           case WhiteNonPromotion:
8660           case BlackNonPromotion:
8661           case NormalMove:
8662           case WhiteCapturesEnPassant:
8663           case BlackCapturesEnPassant:
8664           case WhiteKingSideCastle:
8665           case WhiteQueenSideCastle:
8666           case BlackKingSideCastle:
8667           case BlackQueenSideCastle:
8668           case WhiteKingSideCastleWild:
8669           case WhiteQueenSideCastleWild:
8670           case BlackKingSideCastleWild:
8671           case BlackQueenSideCastleWild:
8672           /* PUSH Fabien */
8673           case WhiteHSideCastleFR:
8674           case WhiteASideCastleFR:
8675           case BlackHSideCastleFR:
8676           case BlackASideCastleFR:
8677           /* POP Fabien */
8678             fromX = currentMoveString[0] - AAA;
8679             fromY = currentMoveString[1] - ONE;
8680             toX = currentMoveString[2] - AAA;
8681             toY = currentMoveString[3] - ONE;
8682             promoChar = currentMoveString[4];
8683             break;
8684           case WhiteDrop:
8685           case BlackDrop:
8686             fromX = moveType == WhiteDrop ?
8687               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8688             (int) CharToPiece(ToLower(currentMoveString[0]));
8689             fromY = DROP_RANK;
8690             toX = currentMoveString[2] - AAA;
8691             toY = currentMoveString[3] - ONE;
8692             promoChar = NULLCHAR;
8693             break;
8694           case AmbiguousMove:
8695             /* bug? */
8696             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8697   if (appData.debugMode) {
8698     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8699     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8700     setbuf(debugFP, NULL);
8701   }
8702             DisplayError(buf, 0);
8703             return;
8704           case ImpossibleMove:
8705             /* bug? */
8706             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8707   if (appData.debugMode) {
8708     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8709     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8710     setbuf(debugFP, NULL);
8711   }
8712             DisplayError(buf, 0);
8713             return;
8714           case EndOfFile:
8715             if (boardIndex < backwardMostMove) {
8716                 /* Oops, gap.  How did that happen? */
8717                 DisplayError(_("Gap in move list"), 0);
8718                 return;
8719             }
8720             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8721             if (boardIndex > forwardMostMove) {
8722                 forwardMostMove = boardIndex;
8723             }
8724             return;
8725           case ElapsedTime:
8726             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8727                 strcat(parseList[boardIndex-1], " ");
8728                 strcat(parseList[boardIndex-1], yy_text);
8729             }
8730             continue;
8731           case Comment:
8732           case PGNTag:
8733           case NAG:
8734           default:
8735             /* ignore */
8736             continue;
8737           case WhiteWins:
8738           case BlackWins:
8739           case GameIsDrawn:
8740           case GameUnfinished:
8741             if (gameMode == IcsExamining) {
8742                 if (boardIndex < backwardMostMove) {
8743                     /* Oops, gap.  How did that happen? */
8744                     return;
8745                 }
8746                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8747                 return;
8748             }
8749             gameInfo.result = moveType;
8750             p = strchr(yy_text, '{');
8751             if (p == NULL) p = strchr(yy_text, '(');
8752             if (p == NULL) {
8753                 p = yy_text;
8754                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8755             } else {
8756                 q = strchr(p, *p == '{' ? '}' : ')');
8757                 if (q != NULL) *q = NULLCHAR;
8758                 p++;
8759             }
8760             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8761             gameInfo.resultDetails = StrSave(p);
8762             continue;
8763         }
8764         if (boardIndex >= forwardMostMove &&
8765             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8766             backwardMostMove = blackPlaysFirst ? 1 : 0;
8767             return;
8768         }
8769         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8770                                  fromY, fromX, toY, toX, promoChar,
8771                                  parseList[boardIndex]);
8772         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8773         /* currentMoveString is set as a side-effect of yylex */
8774         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8775         strcat(moveList[boardIndex], "\n");
8776         boardIndex++;
8777         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8778         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8779           case MT_NONE:
8780           case MT_STALEMATE:
8781           default:
8782             break;
8783           case MT_CHECK:
8784             if(gameInfo.variant != VariantShogi)
8785                 strcat(parseList[boardIndex - 1], "+");
8786             break;
8787           case MT_CHECKMATE:
8788           case MT_STAINMATE:
8789             strcat(parseList[boardIndex - 1], "#");
8790             break;
8791         }
8792     }
8793 }
8794
8795
8796 /* Apply a move to the given board  */
8797 void
8798 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8799      int fromX, fromY, toX, toY;
8800      int promoChar;
8801      Board board;
8802 {
8803   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8804   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8805
8806     /* [HGM] compute & store e.p. status and castling rights for new position */
8807     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8808
8809       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8810       oldEP = (signed char)board[EP_STATUS];
8811       board[EP_STATUS] = EP_NONE;
8812
8813       if( board[toY][toX] != EmptySquare )
8814            board[EP_STATUS] = EP_CAPTURE;
8815
8816   if (fromY == DROP_RANK) {
8817         /* must be first */
8818         piece = board[toY][toX] = (ChessSquare) fromX;
8819   } else {
8820       int i;
8821
8822       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8823            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8824                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8825       } else
8826       if( board[fromY][fromX] == WhitePawn ) {
8827            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8828                board[EP_STATUS] = EP_PAWN_MOVE;
8829            if( toY-fromY==2) {
8830                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8831                         gameInfo.variant != VariantBerolina || toX < fromX)
8832                       board[EP_STATUS] = toX | berolina;
8833                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8834                         gameInfo.variant != VariantBerolina || toX > fromX)
8835                       board[EP_STATUS] = toX;
8836            }
8837       } else
8838       if( board[fromY][fromX] == BlackPawn ) {
8839            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8840                board[EP_STATUS] = EP_PAWN_MOVE;
8841            if( toY-fromY== -2) {
8842                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8843                         gameInfo.variant != VariantBerolina || toX < fromX)
8844                       board[EP_STATUS] = toX | berolina;
8845                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8846                         gameInfo.variant != VariantBerolina || toX > fromX)
8847                       board[EP_STATUS] = toX;
8848            }
8849        }
8850
8851        for(i=0; i<nrCastlingRights; i++) {
8852            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8853               board[CASTLING][i] == toX   && castlingRank[i] == toY
8854              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8855        }
8856
8857      if (fromX == toX && fromY == toY) return;
8858
8859      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8860      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8861      if(gameInfo.variant == VariantKnightmate)
8862          king += (int) WhiteUnicorn - (int) WhiteKing;
8863
8864     /* Code added by Tord: */
8865     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8866     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8867         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8868       board[fromY][fromX] = EmptySquare;
8869       board[toY][toX] = EmptySquare;
8870       if((toX > fromX) != (piece == WhiteRook)) {
8871         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8872       } else {
8873         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8874       }
8875     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8876                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8877       board[fromY][fromX] = EmptySquare;
8878       board[toY][toX] = EmptySquare;
8879       if((toX > fromX) != (piece == BlackRook)) {
8880         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8881       } else {
8882         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8883       }
8884     /* End of code added by Tord */
8885
8886     } else if (board[fromY][fromX] == king
8887         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8888         && toY == fromY && toX > fromX+1) {
8889         board[fromY][fromX] = EmptySquare;
8890         board[toY][toX] = king;
8891         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8892         board[fromY][BOARD_RGHT-1] = EmptySquare;
8893     } else if (board[fromY][fromX] == king
8894         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8895                && toY == fromY && toX < fromX-1) {
8896         board[fromY][fromX] = EmptySquare;
8897         board[toY][toX] = king;
8898         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8899         board[fromY][BOARD_LEFT] = EmptySquare;
8900     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8901                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8902                && toY >= BOARD_HEIGHT-promoRank
8903                ) {
8904         /* white pawn promotion */
8905         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8906         if (board[toY][toX] == EmptySquare) {
8907             board[toY][toX] = WhiteQueen;
8908         }
8909         if(gameInfo.variant==VariantBughouse ||
8910            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8911             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8912         board[fromY][fromX] = EmptySquare;
8913     } else if ((fromY == BOARD_HEIGHT-4)
8914                && (toX != fromX)
8915                && gameInfo.variant != VariantXiangqi
8916                && gameInfo.variant != VariantBerolina
8917                && (board[fromY][fromX] == WhitePawn)
8918                && (board[toY][toX] == EmptySquare)) {
8919         board[fromY][fromX] = EmptySquare;
8920         board[toY][toX] = WhitePawn;
8921         captured = board[toY - 1][toX];
8922         board[toY - 1][toX] = EmptySquare;
8923     } else if ((fromY == BOARD_HEIGHT-4)
8924                && (toX == fromX)
8925                && gameInfo.variant == VariantBerolina
8926                && (board[fromY][fromX] == WhitePawn)
8927                && (board[toY][toX] == EmptySquare)) {
8928         board[fromY][fromX] = EmptySquare;
8929         board[toY][toX] = WhitePawn;
8930         if(oldEP & EP_BEROLIN_A) {
8931                 captured = board[fromY][fromX-1];
8932                 board[fromY][fromX-1] = EmptySquare;
8933         }else{  captured = board[fromY][fromX+1];
8934                 board[fromY][fromX+1] = EmptySquare;
8935         }
8936     } else if (board[fromY][fromX] == king
8937         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8938                && toY == fromY && toX > fromX+1) {
8939         board[fromY][fromX] = EmptySquare;
8940         board[toY][toX] = king;
8941         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8942         board[fromY][BOARD_RGHT-1] = EmptySquare;
8943     } else if (board[fromY][fromX] == king
8944         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8945                && toY == fromY && toX < fromX-1) {
8946         board[fromY][fromX] = EmptySquare;
8947         board[toY][toX] = king;
8948         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8949         board[fromY][BOARD_LEFT] = EmptySquare;
8950     } else if (fromY == 7 && fromX == 3
8951                && board[fromY][fromX] == BlackKing
8952                && toY == 7 && toX == 5) {
8953         board[fromY][fromX] = EmptySquare;
8954         board[toY][toX] = BlackKing;
8955         board[fromY][7] = EmptySquare;
8956         board[toY][4] = BlackRook;
8957     } else if (fromY == 7 && fromX == 3
8958                && board[fromY][fromX] == BlackKing
8959                && toY == 7 && toX == 1) {
8960         board[fromY][fromX] = EmptySquare;
8961         board[toY][toX] = BlackKing;
8962         board[fromY][0] = EmptySquare;
8963         board[toY][2] = BlackRook;
8964     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8965                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8966                && toY < promoRank
8967                ) {
8968         /* black pawn promotion */
8969         board[toY][toX] = CharToPiece(ToLower(promoChar));
8970         if (board[toY][toX] == EmptySquare) {
8971             board[toY][toX] = BlackQueen;
8972         }
8973         if(gameInfo.variant==VariantBughouse ||
8974            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8975             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8976         board[fromY][fromX] = EmptySquare;
8977     } else if ((fromY == 3)
8978                && (toX != fromX)
8979                && gameInfo.variant != VariantXiangqi
8980                && gameInfo.variant != VariantBerolina
8981                && (board[fromY][fromX] == BlackPawn)
8982                && (board[toY][toX] == EmptySquare)) {
8983         board[fromY][fromX] = EmptySquare;
8984         board[toY][toX] = BlackPawn;
8985         captured = board[toY + 1][toX];
8986         board[toY + 1][toX] = EmptySquare;
8987     } else if ((fromY == 3)
8988                && (toX == fromX)
8989                && gameInfo.variant == VariantBerolina
8990                && (board[fromY][fromX] == BlackPawn)
8991                && (board[toY][toX] == EmptySquare)) {
8992         board[fromY][fromX] = EmptySquare;
8993         board[toY][toX] = BlackPawn;
8994         if(oldEP & EP_BEROLIN_A) {
8995                 captured = board[fromY][fromX-1];
8996                 board[fromY][fromX-1] = EmptySquare;
8997         }else{  captured = board[fromY][fromX+1];
8998                 board[fromY][fromX+1] = EmptySquare;
8999         }
9000     } else {
9001         board[toY][toX] = board[fromY][fromX];
9002         board[fromY][fromX] = EmptySquare;
9003     }
9004   }
9005
9006     if (gameInfo.holdingsWidth != 0) {
9007
9008       /* !!A lot more code needs to be written to support holdings  */
9009       /* [HGM] OK, so I have written it. Holdings are stored in the */
9010       /* penultimate board files, so they are automaticlly stored   */
9011       /* in the game history.                                       */
9012       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9013                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9014         /* Delete from holdings, by decreasing count */
9015         /* and erasing image if necessary            */
9016         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9017         if(p < (int) BlackPawn) { /* white drop */
9018              p -= (int)WhitePawn;
9019                  p = PieceToNumber((ChessSquare)p);
9020              if(p >= gameInfo.holdingsSize) p = 0;
9021              if(--board[p][BOARD_WIDTH-2] <= 0)
9022                   board[p][BOARD_WIDTH-1] = EmptySquare;
9023              if((int)board[p][BOARD_WIDTH-2] < 0)
9024                         board[p][BOARD_WIDTH-2] = 0;
9025         } else {                  /* black drop */
9026              p -= (int)BlackPawn;
9027                  p = PieceToNumber((ChessSquare)p);
9028              if(p >= gameInfo.holdingsSize) p = 0;
9029              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9030                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9031              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9032                         board[BOARD_HEIGHT-1-p][1] = 0;
9033         }
9034       }
9035       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9036           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9037         /* [HGM] holdings: Add to holdings, if holdings exist */
9038         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9039                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9040                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9041         }
9042         p = (int) captured;
9043         if (p >= (int) BlackPawn) {
9044           p -= (int)BlackPawn;
9045           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9046                   /* in Shogi restore piece to its original  first */
9047                   captured = (ChessSquare) (DEMOTED captured);
9048                   p = DEMOTED p;
9049           }
9050           p = PieceToNumber((ChessSquare)p);
9051           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9052           board[p][BOARD_WIDTH-2]++;
9053           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9054         } else {
9055           p -= (int)WhitePawn;
9056           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9057                   captured = (ChessSquare) (DEMOTED captured);
9058                   p = DEMOTED p;
9059           }
9060           p = PieceToNumber((ChessSquare)p);
9061           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9062           board[BOARD_HEIGHT-1-p][1]++;
9063           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9064         }
9065       }
9066     } else if (gameInfo.variant == VariantAtomic) {
9067       if (captured != EmptySquare) {
9068         int y, x;
9069         for (y = toY-1; y <= toY+1; y++) {
9070           for (x = toX-1; x <= toX+1; x++) {
9071             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9072                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9073               board[y][x] = EmptySquare;
9074             }
9075           }
9076         }
9077         board[toY][toX] = EmptySquare;
9078       }
9079     }
9080     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9081         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9082     } else
9083     if(promoChar == '+') {
9084         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9085         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9086     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9087         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9088     }
9089     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9090                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9091         // [HGM] superchess: take promotion piece out of holdings
9092         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9093         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9094             if(!--board[k][BOARD_WIDTH-2])
9095                 board[k][BOARD_WIDTH-1] = EmptySquare;
9096         } else {
9097             if(!--board[BOARD_HEIGHT-1-k][1])
9098                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9099         }
9100     }
9101
9102 }
9103
9104 /* Updates forwardMostMove */
9105 void
9106 MakeMove(fromX, fromY, toX, toY, promoChar)
9107      int fromX, fromY, toX, toY;
9108      int promoChar;
9109 {
9110 //    forwardMostMove++; // [HGM] bare: moved downstream
9111
9112     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9113         int timeLeft; static int lastLoadFlag=0; int king, piece;
9114         piece = boards[forwardMostMove][fromY][fromX];
9115         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9116         if(gameInfo.variant == VariantKnightmate)
9117             king += (int) WhiteUnicorn - (int) WhiteKing;
9118         if(forwardMostMove == 0) {
9119             if(blackPlaysFirst)
9120                 fprintf(serverMoves, "%s;", second.tidy);
9121             fprintf(serverMoves, "%s;", first.tidy);
9122             if(!blackPlaysFirst)
9123                 fprintf(serverMoves, "%s;", second.tidy);
9124         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9125         lastLoadFlag = loadFlag;
9126         // print base move
9127         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9128         // print castling suffix
9129         if( toY == fromY && piece == king ) {
9130             if(toX-fromX > 1)
9131                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9132             if(fromX-toX >1)
9133                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9134         }
9135         // e.p. suffix
9136         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9137              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9138              boards[forwardMostMove][toY][toX] == EmptySquare
9139              && fromX != toX && fromY != toY)
9140                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9141         // promotion suffix
9142         if(promoChar != NULLCHAR)
9143                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9144         if(!loadFlag) {
9145             fprintf(serverMoves, "/%d/%d",
9146                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9147             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9148             else                      timeLeft = blackTimeRemaining/1000;
9149             fprintf(serverMoves, "/%d", timeLeft);
9150         }
9151         fflush(serverMoves);
9152     }
9153
9154     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9155       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9156                         0, 1);
9157       return;
9158     }
9159     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9160     if (commentList[forwardMostMove+1] != NULL) {
9161         free(commentList[forwardMostMove+1]);
9162         commentList[forwardMostMove+1] = NULL;
9163     }
9164     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9165     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9166     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9167     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9168     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9169     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9170     gameInfo.result = GameUnfinished;
9171     if (gameInfo.resultDetails != NULL) {
9172         free(gameInfo.resultDetails);
9173         gameInfo.resultDetails = NULL;
9174     }
9175     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9176                               moveList[forwardMostMove - 1]);
9177     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9178                              PosFlags(forwardMostMove - 1),
9179                              fromY, fromX, toY, toX, promoChar,
9180                              parseList[forwardMostMove - 1]);
9181     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9182       case MT_NONE:
9183       case MT_STALEMATE:
9184       default:
9185         break;
9186       case MT_CHECK:
9187         if(gameInfo.variant != VariantShogi)
9188             strcat(parseList[forwardMostMove - 1], "+");
9189         break;
9190       case MT_CHECKMATE:
9191       case MT_STAINMATE:
9192         strcat(parseList[forwardMostMove - 1], "#");
9193         break;
9194     }
9195     if (appData.debugMode) {
9196         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9197     }
9198
9199 }
9200
9201 /* Updates currentMove if not pausing */
9202 void
9203 ShowMove(fromX, fromY, toX, toY)
9204 {
9205     int instant = (gameMode == PlayFromGameFile) ?
9206         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9207     if(appData.noGUI) return;
9208     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9209         if (!instant) {
9210             if (forwardMostMove == currentMove + 1) {
9211                 AnimateMove(boards[forwardMostMove - 1],
9212                             fromX, fromY, toX, toY);
9213             }
9214             if (appData.highlightLastMove) {
9215                 SetHighlights(fromX, fromY, toX, toY);
9216             }
9217         }
9218         currentMove = forwardMostMove;
9219     }
9220
9221     if (instant) return;
9222
9223     DisplayMove(currentMove - 1);
9224     DrawPosition(FALSE, boards[currentMove]);
9225     DisplayBothClocks();
9226     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9227 }
9228
9229 void SendEgtPath(ChessProgramState *cps)
9230 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9231         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9232
9233         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9234
9235         while(*p) {
9236             char c, *q = name+1, *r, *s;
9237
9238             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9239             while(*p && *p != ',') *q++ = *p++;
9240             *q++ = ':'; *q = 0;
9241             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9242                 strcmp(name, ",nalimov:") == 0 ) {
9243                 // take nalimov path from the menu-changeable option first, if it is defined
9244               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9245                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9246             } else
9247             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9248                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9249                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9250                 s = r = StrStr(s, ":") + 1; // beginning of path info
9251                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9252                 c = *r; *r = 0;             // temporarily null-terminate path info
9253                     *--q = 0;               // strip of trailig ':' from name
9254                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9255                 *r = c;
9256                 SendToProgram(buf,cps);     // send egtbpath command for this format
9257             }
9258             if(*p == ',') p++; // read away comma to position for next format name
9259         }
9260 }
9261
9262 void
9263 InitChessProgram(cps, setup)
9264      ChessProgramState *cps;
9265      int setup; /* [HGM] needed to setup FRC opening position */
9266 {
9267     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9268     if (appData.noChessProgram) return;
9269     hintRequested = FALSE;
9270     bookRequested = FALSE;
9271
9272     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9273     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9274     if(cps->memSize) { /* [HGM] memory */
9275       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9276         SendToProgram(buf, cps);
9277     }
9278     SendEgtPath(cps); /* [HGM] EGT */
9279     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9280       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9281         SendToProgram(buf, cps);
9282     }
9283
9284     SendToProgram(cps->initString, cps);
9285     if (gameInfo.variant != VariantNormal &&
9286         gameInfo.variant != VariantLoadable
9287         /* [HGM] also send variant if board size non-standard */
9288         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9289                                             ) {
9290       char *v = VariantName(gameInfo.variant);
9291       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9292         /* [HGM] in protocol 1 we have to assume all variants valid */
9293         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9294         DisplayFatalError(buf, 0, 1);
9295         return;
9296       }
9297
9298       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9299       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9300       if( gameInfo.variant == VariantXiangqi )
9301            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9302       if( gameInfo.variant == VariantShogi )
9303            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9304       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9305            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9306       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9307           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9308            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9309       if( gameInfo.variant == VariantCourier )
9310            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9311       if( gameInfo.variant == VariantSuper )
9312            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9313       if( gameInfo.variant == VariantGreat )
9314            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9315       if( gameInfo.variant == VariantSChess )
9316            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9317
9318       if(overruled) {
9319         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9320                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9321            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9322            if(StrStr(cps->variants, b) == NULL) {
9323                // specific sized variant not known, check if general sizing allowed
9324                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9325                    if(StrStr(cps->variants, "boardsize") == NULL) {
9326                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9327                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9328                        DisplayFatalError(buf, 0, 1);
9329                        return;
9330                    }
9331                    /* [HGM] here we really should compare with the maximum supported board size */
9332                }
9333            }
9334       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9335       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9336       SendToProgram(buf, cps);
9337     }
9338     currentlyInitializedVariant = gameInfo.variant;
9339
9340     /* [HGM] send opening position in FRC to first engine */
9341     if(setup) {
9342           SendToProgram("force\n", cps);
9343           SendBoard(cps, 0);
9344           /* engine is now in force mode! Set flag to wake it up after first move. */
9345           setboardSpoiledMachineBlack = 1;
9346     }
9347
9348     if (cps->sendICS) {
9349       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9350       SendToProgram(buf, cps);
9351     }
9352     cps->maybeThinking = FALSE;
9353     cps->offeredDraw = 0;
9354     if (!appData.icsActive) {
9355         SendTimeControl(cps, movesPerSession, timeControl,
9356                         timeIncrement, appData.searchDepth,
9357                         searchTime);
9358     }
9359     if (appData.showThinking
9360         // [HGM] thinking: four options require thinking output to be sent
9361         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9362                                 ) {
9363         SendToProgram("post\n", cps);
9364     }
9365     SendToProgram("hard\n", cps);
9366     if (!appData.ponderNextMove) {
9367         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9368            it without being sure what state we are in first.  "hard"
9369            is not a toggle, so that one is OK.
9370          */
9371         SendToProgram("easy\n", cps);
9372     }
9373     if (cps->usePing) {
9374       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9375       SendToProgram(buf, cps);
9376     }
9377     cps->initDone = TRUE;
9378 }
9379
9380
9381 void
9382 StartChessProgram(cps)
9383      ChessProgramState *cps;
9384 {
9385     char buf[MSG_SIZ];
9386     int err;
9387
9388     if (appData.noChessProgram) return;
9389     cps->initDone = FALSE;
9390
9391     if (strcmp(cps->host, "localhost") == 0) {
9392         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9393     } else if (*appData.remoteShell == NULLCHAR) {
9394         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9395     } else {
9396         if (*appData.remoteUser == NULLCHAR) {
9397           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9398                     cps->program);
9399         } else {
9400           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9401                     cps->host, appData.remoteUser, cps->program);
9402         }
9403         err = StartChildProcess(buf, "", &cps->pr);
9404     }
9405
9406     if (err != 0) {
9407       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9408         DisplayFatalError(buf, err, 1);
9409         cps->pr = NoProc;
9410         cps->isr = NULL;
9411         return;
9412     }
9413
9414     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9415     if (cps->protocolVersion > 1) {
9416       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9417       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9418       cps->comboCnt = 0;  //                and values of combo boxes
9419       SendToProgram(buf, cps);
9420     } else {
9421       SendToProgram("xboard\n", cps);
9422     }
9423 }
9424
9425 void
9426 TwoMachinesEventIfReady P((void))
9427 {
9428   static int curMess = 0;
9429   if (first.lastPing != first.lastPong) {
9430     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9431     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9432     return;
9433   }
9434   if (second.lastPing != second.lastPong) {
9435     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9436     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9437     return;
9438   }
9439   DisplayMessage("", ""); curMess = 0;
9440   ThawUI();
9441   TwoMachinesEvent();
9442 }
9443
9444 int
9445 CreateTourney(char *name)
9446 {
9447         FILE *f;
9448         if(name[0] == NULLCHAR) return 0;
9449         f = fopen(appData.tourneyFile, "r");
9450         if(f) { // file exists
9451             ParseArgsFromFile(f); // parse it
9452         } else {
9453             f = fopen(appData.tourneyFile, "w");
9454             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9455                 // create a file with tournament description
9456                 fprintf(f, "-participants {%s}\n", appData.participants);
9457                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9458                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9459                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9460                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9461                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9462                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9463                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9464                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9465                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9466                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9467                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9468                 fprintf(f, "-results \"\"\n");
9469             }
9470         }
9471         fclose(f);
9472         appData.noChessProgram = FALSE;
9473         appData.clockMode = TRUE;
9474         SetGNUMode();
9475         return 1;
9476 }
9477
9478 #define MAXENGINES 1000
9479 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9480
9481 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9482 {
9483     char buf[MSG_SIZ], *p, *q;
9484     int i=1;
9485     while(*names) {
9486         p = names; q = buf;
9487         while(*p && *p != '\n') *q++ = *p++;
9488         *q = 0;
9489         if(engineList[i]) free(engineList[i]);
9490         engineList[i] = strdup(buf);
9491         if(*p == '\n') p++;
9492         TidyProgramName(engineList[i], "localhost", buf);
9493         if(engineMnemonic[i]) free(engineMnemonic[i]);
9494         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9495             strcat(buf, " (");
9496             sscanf(q + 8, "%s", buf + strlen(buf));
9497             strcat(buf, ")");
9498         }
9499         engineMnemonic[i] = strdup(buf);
9500         names = p; i++;
9501       if(i > MAXENGINES - 2) break;
9502     }
9503     engineList[i] = NULL;
9504 }
9505
9506 // following implemented as macro to avoid type limitations
9507 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9508
9509 void SwapEngines(int n)
9510 {   // swap settings for first engine and other engine (so far only some selected options)
9511     int h;
9512     char *p;
9513     if(n == 0) return;
9514     SWAP(directory, p)
9515     SWAP(chessProgram, p)
9516     SWAP(isUCI, h)
9517     SWAP(hasOwnBookUCI, h)
9518     SWAP(protocolVersion, h)
9519     SWAP(reuse, h)
9520     SWAP(scoreIsAbsolute, h)
9521     SWAP(timeOdds, h)
9522     SWAP(logo, p)
9523 }
9524
9525 void
9526 SetPlayer(int player)
9527 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9528     int i;
9529     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9530     static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9531                                  "-firstNeedsNoncompliantFEN false -firstNPS -1";
9532     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9533     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9534     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9535     if(mnemonic[i]) {
9536         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9537         ParseArgsFromString(resetOptions);
9538         ParseArgsFromString(buf);
9539     }
9540     free(engineName);
9541 }
9542
9543 int
9544 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9545 {   // determine players from game number
9546     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9547
9548     if(appData.tourneyType == 0) {
9549         roundsPerCycle = (nPlayers - 1) | 1;
9550         pairingsPerRound = nPlayers / 2;
9551     } else if(appData.tourneyType > 0) {
9552         roundsPerCycle = nPlayers - appData.tourneyType;
9553         pairingsPerRound = appData.tourneyType;
9554     }
9555     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9556     gamesPerCycle = gamesPerRound * roundsPerCycle;
9557     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9558     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9559     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9560     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9561     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9562     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9563
9564     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9565     if(appData.roundSync) *syncInterval = gamesPerRound;
9566
9567     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9568
9569     if(appData.tourneyType == 0) {
9570         if(curPairing == (nPlayers-1)/2 ) {
9571             *whitePlayer = curRound;
9572             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9573         } else {
9574             *whitePlayer = curRound - pairingsPerRound + curPairing;
9575             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9576             *blackPlayer = curRound + pairingsPerRound - curPairing;
9577             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9578         }
9579     } else if(appData.tourneyType > 0) {
9580         *whitePlayer = curPairing;
9581         *blackPlayer = curRound + appData.tourneyType;
9582     }
9583
9584     // take care of white/black alternation per round. 
9585     // For cycles and games this is already taken care of by default, derived from matchGame!
9586     return curRound & 1;
9587 }
9588
9589 int
9590 NextTourneyGame(int nr, int *swapColors)
9591 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9592     char *p, *q;
9593     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9594     FILE *tf;
9595     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9596     tf = fopen(appData.tourneyFile, "r");
9597     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9598     ParseArgsFromFile(tf); fclose(tf);
9599
9600     p = appData.participants;
9601     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9602     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9603
9604     if(syncInterval) {
9605         p = q = appData.results;
9606         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9607         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9608             DisplayMessage(_("Waiting for other game(s)"),"");
9609             waitingForGame = TRUE;
9610             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9611             return 0;
9612         }
9613         waitingForGame = FALSE;
9614     }
9615
9616     if(first.pr != NoProc) return 1; // engines already loaded
9617
9618     // redefine engines, engine dir, etc.
9619     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9620     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9621     SwapEngines(1);
9622     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9623     SwapEngines(1);         // and make that valid for second engine by swapping
9624     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9625     InitEngine(&second, 1);
9626     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9627     return 1;
9628 }
9629
9630 void
9631 NextMatchGame()
9632 {   // performs game initialization that does not invoke engines, and then tries to start the game
9633     int firstWhite, swapColors = 0;
9634     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9635     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9636     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9637     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9638     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9639     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9640     Reset(FALSE, first.pr != NoProc);
9641     appData.noChessProgram = FALSE;
9642     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9643     TwoMachinesEvent();
9644 }
9645
9646 void UserAdjudicationEvent( int result )
9647 {
9648     ChessMove gameResult = GameIsDrawn;
9649
9650     if( result > 0 ) {
9651         gameResult = WhiteWins;
9652     }
9653     else if( result < 0 ) {
9654         gameResult = BlackWins;
9655     }
9656
9657     if( gameMode == TwoMachinesPlay ) {
9658         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9659     }
9660 }
9661
9662
9663 // [HGM] save: calculate checksum of game to make games easily identifiable
9664 int StringCheckSum(char *s)
9665 {
9666         int i = 0;
9667         if(s==NULL) return 0;
9668         while(*s) i = i*259 + *s++;
9669         return i;
9670 }
9671
9672 int GameCheckSum()
9673 {
9674         int i, sum=0;
9675         for(i=backwardMostMove; i<forwardMostMove; i++) {
9676                 sum += pvInfoList[i].depth;
9677                 sum += StringCheckSum(parseList[i]);
9678                 sum += StringCheckSum(commentList[i]);
9679                 sum *= 261;
9680         }
9681         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9682         return sum + StringCheckSum(commentList[i]);
9683 } // end of save patch
9684
9685 void
9686 GameEnds(result, resultDetails, whosays)
9687      ChessMove result;
9688      char *resultDetails;
9689      int whosays;
9690 {
9691     GameMode nextGameMode;
9692     int isIcsGame;
9693     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9694
9695     if(endingGame) return; /* [HGM] crash: forbid recursion */
9696     endingGame = 1;
9697     if(twoBoards) { // [HGM] dual: switch back to one board
9698         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9699         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9700     }
9701     if (appData.debugMode) {
9702       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9703               result, resultDetails ? resultDetails : "(null)", whosays);
9704     }
9705
9706     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9707
9708     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9709         /* If we are playing on ICS, the server decides when the
9710            game is over, but the engine can offer to draw, claim
9711            a draw, or resign.
9712          */
9713 #if ZIPPY
9714         if (appData.zippyPlay && first.initDone) {
9715             if (result == GameIsDrawn) {
9716                 /* In case draw still needs to be claimed */
9717                 SendToICS(ics_prefix);
9718                 SendToICS("draw\n");
9719             } else if (StrCaseStr(resultDetails, "resign")) {
9720                 SendToICS(ics_prefix);
9721                 SendToICS("resign\n");
9722             }
9723         }
9724 #endif
9725         endingGame = 0; /* [HGM] crash */
9726         return;
9727     }
9728
9729     /* If we're loading the game from a file, stop */
9730     if (whosays == GE_FILE) {
9731       (void) StopLoadGameTimer();
9732       gameFileFP = NULL;
9733     }
9734
9735     /* Cancel draw offers */
9736     first.offeredDraw = second.offeredDraw = 0;
9737
9738     /* If this is an ICS game, only ICS can really say it's done;
9739        if not, anyone can. */
9740     isIcsGame = (gameMode == IcsPlayingWhite ||
9741                  gameMode == IcsPlayingBlack ||
9742                  gameMode == IcsObserving    ||
9743                  gameMode == IcsExamining);
9744
9745     if (!isIcsGame || whosays == GE_ICS) {
9746         /* OK -- not an ICS game, or ICS said it was done */
9747         StopClocks();
9748         if (!isIcsGame && !appData.noChessProgram)
9749           SetUserThinkingEnables();
9750
9751         /* [HGM] if a machine claims the game end we verify this claim */
9752         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9753             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9754                 char claimer;
9755                 ChessMove trueResult = (ChessMove) -1;
9756
9757                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9758                                             first.twoMachinesColor[0] :
9759                                             second.twoMachinesColor[0] ;
9760
9761                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9762                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9763                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9764                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9765                 } else
9766                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9767                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9768                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9769                 } else
9770                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9771                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9772                 }
9773
9774                 // now verify win claims, but not in drop games, as we don't understand those yet
9775                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9776                                                  || gameInfo.variant == VariantGreat) &&
9777                     (result == WhiteWins && claimer == 'w' ||
9778                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9779                       if (appData.debugMode) {
9780                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9781                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9782                       }
9783                       if(result != trueResult) {
9784                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9785                               result = claimer == 'w' ? BlackWins : WhiteWins;
9786                               resultDetails = buf;
9787                       }
9788                 } else
9789                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9790                     && (forwardMostMove <= backwardMostMove ||
9791                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9792                         (claimer=='b')==(forwardMostMove&1))
9793                                                                                   ) {
9794                       /* [HGM] verify: draws that were not flagged are false claims */
9795                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9796                       result = claimer == 'w' ? BlackWins : WhiteWins;
9797                       resultDetails = buf;
9798                 }
9799                 /* (Claiming a loss is accepted no questions asked!) */
9800             }
9801             /* [HGM] bare: don't allow bare King to win */
9802             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9803                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9804                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9805                && result != GameIsDrawn)
9806             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9807                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9808                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9809                         if(p >= 0 && p <= (int)WhiteKing) k++;
9810                 }
9811                 if (appData.debugMode) {
9812                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9813                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9814                 }
9815                 if(k <= 1) {
9816                         result = GameIsDrawn;
9817                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9818                         resultDetails = buf;
9819                 }
9820             }
9821         }
9822
9823
9824         if(serverMoves != NULL && !loadFlag) { char c = '=';
9825             if(result==WhiteWins) c = '+';
9826             if(result==BlackWins) c = '-';
9827             if(resultDetails != NULL)
9828                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9829         }
9830         if (resultDetails != NULL) {
9831             gameInfo.result = result;
9832             gameInfo.resultDetails = StrSave(resultDetails);
9833
9834             /* display last move only if game was not loaded from file */
9835             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9836                 DisplayMove(currentMove - 1);
9837
9838             if (forwardMostMove != 0) {
9839                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9840                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9841                                                                 ) {
9842                     if (*appData.saveGameFile != NULLCHAR) {
9843                         SaveGameToFile(appData.saveGameFile, TRUE);
9844                     } else if (appData.autoSaveGames) {
9845                         AutoSaveGame();
9846                     }
9847                     if (*appData.savePositionFile != NULLCHAR) {
9848                         SavePositionToFile(appData.savePositionFile);
9849                     }
9850                 }
9851             }
9852
9853             /* Tell program how game ended in case it is learning */
9854             /* [HGM] Moved this to after saving the PGN, just in case */
9855             /* engine died and we got here through time loss. In that */
9856             /* case we will get a fatal error writing the pipe, which */
9857             /* would otherwise lose us the PGN.                       */
9858             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9859             /* output during GameEnds should never be fatal anymore   */
9860             if (gameMode == MachinePlaysWhite ||
9861                 gameMode == MachinePlaysBlack ||
9862                 gameMode == TwoMachinesPlay ||
9863                 gameMode == IcsPlayingWhite ||
9864                 gameMode == IcsPlayingBlack ||
9865                 gameMode == BeginningOfGame) {
9866                 char buf[MSG_SIZ];
9867                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9868                         resultDetails);
9869                 if (first.pr != NoProc) {
9870                     SendToProgram(buf, &first);
9871                 }
9872                 if (second.pr != NoProc &&
9873                     gameMode == TwoMachinesPlay) {
9874                     SendToProgram(buf, &second);
9875                 }
9876             }
9877         }
9878
9879         if (appData.icsActive) {
9880             if (appData.quietPlay &&
9881                 (gameMode == IcsPlayingWhite ||
9882                  gameMode == IcsPlayingBlack)) {
9883                 SendToICS(ics_prefix);
9884                 SendToICS("set shout 1\n");
9885             }
9886             nextGameMode = IcsIdle;
9887             ics_user_moved = FALSE;
9888             /* clean up premove.  It's ugly when the game has ended and the
9889              * premove highlights are still on the board.
9890              */
9891             if (gotPremove) {
9892               gotPremove = FALSE;
9893               ClearPremoveHighlights();
9894               DrawPosition(FALSE, boards[currentMove]);
9895             }
9896             if (whosays == GE_ICS) {
9897                 switch (result) {
9898                 case WhiteWins:
9899                     if (gameMode == IcsPlayingWhite)
9900                         PlayIcsWinSound();
9901                     else if(gameMode == IcsPlayingBlack)
9902                         PlayIcsLossSound();
9903                     break;
9904                 case BlackWins:
9905                     if (gameMode == IcsPlayingBlack)
9906                         PlayIcsWinSound();
9907                     else if(gameMode == IcsPlayingWhite)
9908                         PlayIcsLossSound();
9909                     break;
9910                 case GameIsDrawn:
9911                     PlayIcsDrawSound();
9912                     break;
9913                 default:
9914                     PlayIcsUnfinishedSound();
9915                 }
9916             }
9917         } else if (gameMode == EditGame ||
9918                    gameMode == PlayFromGameFile ||
9919                    gameMode == AnalyzeMode ||
9920                    gameMode == AnalyzeFile) {
9921             nextGameMode = gameMode;
9922         } else {
9923             nextGameMode = EndOfGame;
9924         }
9925         pausing = FALSE;
9926         ModeHighlight();
9927     } else {
9928         nextGameMode = gameMode;
9929     }
9930
9931     if (appData.noChessProgram) {
9932         gameMode = nextGameMode;
9933         ModeHighlight();
9934         endingGame = 0; /* [HGM] crash */
9935         return;
9936     }
9937
9938     if (first.reuse) {
9939         /* Put first chess program into idle state */
9940         if (first.pr != NoProc &&
9941             (gameMode == MachinePlaysWhite ||
9942              gameMode == MachinePlaysBlack ||
9943              gameMode == TwoMachinesPlay ||
9944              gameMode == IcsPlayingWhite ||
9945              gameMode == IcsPlayingBlack ||
9946              gameMode == BeginningOfGame)) {
9947             SendToProgram("force\n", &first);
9948             if (first.usePing) {
9949               char buf[MSG_SIZ];
9950               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9951               SendToProgram(buf, &first);
9952             }
9953         }
9954     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9955         /* Kill off first chess program */
9956         if (first.isr != NULL)
9957           RemoveInputSource(first.isr);
9958         first.isr = NULL;
9959
9960         if (first.pr != NoProc) {
9961             ExitAnalyzeMode();
9962             DoSleep( appData.delayBeforeQuit );
9963             SendToProgram("quit\n", &first);
9964             DoSleep( appData.delayAfterQuit );
9965             DestroyChildProcess(first.pr, first.useSigterm);
9966         }
9967         first.pr = NoProc;
9968     }
9969     if (second.reuse) {
9970         /* Put second chess program into idle state */
9971         if (second.pr != NoProc &&
9972             gameMode == TwoMachinesPlay) {
9973             SendToProgram("force\n", &second);
9974             if (second.usePing) {
9975               char buf[MSG_SIZ];
9976               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9977               SendToProgram(buf, &second);
9978             }
9979         }
9980     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9981         /* Kill off second chess program */
9982         if (second.isr != NULL)
9983           RemoveInputSource(second.isr);
9984         second.isr = NULL;
9985
9986         if (second.pr != NoProc) {
9987             DoSleep( appData.delayBeforeQuit );
9988             SendToProgram("quit\n", &second);
9989             DoSleep( appData.delayAfterQuit );
9990             DestroyChildProcess(second.pr, second.useSigterm);
9991         }
9992         second.pr = NoProc;
9993     }
9994
9995     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
9996         char resChar = '=';
9997         switch (result) {
9998         case WhiteWins:
9999           resChar = '+';
10000           if (first.twoMachinesColor[0] == 'w') {
10001             first.matchWins++;
10002           } else {
10003             second.matchWins++;
10004           }
10005           break;
10006         case BlackWins:
10007           resChar = '-';
10008           if (first.twoMachinesColor[0] == 'b') {
10009             first.matchWins++;
10010           } else {
10011             second.matchWins++;
10012           }
10013           break;
10014         case GameUnfinished:
10015           resChar = ' ';
10016         default:
10017           break;
10018         }
10019
10020         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10021         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10022             ReserveGame(nextGame, resChar); // sets nextGame
10023             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10024         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10025
10026         if (nextGame <= appData.matchGames) {
10027             gameMode = nextGameMode;
10028             matchGame = nextGame; // this will be overruled in tourney mode!
10029             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10030             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10031             endingGame = 0; /* [HGM] crash */
10032             return;
10033         } else {
10034             gameMode = nextGameMode;
10035             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10036                      first.tidy, second.tidy,
10037                      first.matchWins, second.matchWins,
10038                      appData.matchGames - (first.matchWins + second.matchWins));
10039             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10040             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10041                 first.twoMachinesColor = "black\n";
10042                 second.twoMachinesColor = "white\n";
10043             } else {
10044                 first.twoMachinesColor = "white\n";
10045                 second.twoMachinesColor = "black\n";
10046             }
10047         }
10048     }
10049     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10050         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10051       ExitAnalyzeMode();
10052     gameMode = nextGameMode;
10053     ModeHighlight();
10054     endingGame = 0;  /* [HGM] crash */
10055     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10056       if(matchMode == TRUE) DisplayFatalError(ranking ? ranking : buf, 0, 0); else {
10057         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10058         DisplayNote(ranking ? ranking : buf);
10059       }
10060       if(ranking) free(ranking);
10061     }
10062 }
10063
10064 /* Assumes program was just initialized (initString sent).
10065    Leaves program in force mode. */
10066 void
10067 FeedMovesToProgram(cps, upto)
10068      ChessProgramState *cps;
10069      int upto;
10070 {
10071     int i;
10072
10073     if (appData.debugMode)
10074       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10075               startedFromSetupPosition ? "position and " : "",
10076               backwardMostMove, upto, cps->which);
10077     if(currentlyInitializedVariant != gameInfo.variant) {
10078       char buf[MSG_SIZ];
10079         // [HGM] variantswitch: make engine aware of new variant
10080         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10081                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10082         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10083         SendToProgram(buf, cps);
10084         currentlyInitializedVariant = gameInfo.variant;
10085     }
10086     SendToProgram("force\n", cps);
10087     if (startedFromSetupPosition) {
10088         SendBoard(cps, backwardMostMove);
10089     if (appData.debugMode) {
10090         fprintf(debugFP, "feedMoves\n");
10091     }
10092     }
10093     for (i = backwardMostMove; i < upto; i++) {
10094         SendMoveToProgram(i, cps);
10095     }
10096 }
10097
10098
10099 int
10100 ResurrectChessProgram()
10101 {
10102      /* The chess program may have exited.
10103         If so, restart it and feed it all the moves made so far. */
10104     static int doInit = 0;
10105
10106     if (appData.noChessProgram) return 1;
10107
10108     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10109         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10110         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10111         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10112     } else {
10113         if (first.pr != NoProc) return 1;
10114         StartChessProgram(&first);
10115     }
10116     InitChessProgram(&first, FALSE);
10117     FeedMovesToProgram(&first, currentMove);
10118
10119     if (!first.sendTime) {
10120         /* can't tell gnuchess what its clock should read,
10121            so we bow to its notion. */
10122         ResetClocks();
10123         timeRemaining[0][currentMove] = whiteTimeRemaining;
10124         timeRemaining[1][currentMove] = blackTimeRemaining;
10125     }
10126
10127     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10128                 appData.icsEngineAnalyze) && first.analysisSupport) {
10129       SendToProgram("analyze\n", &first);
10130       first.analyzing = TRUE;
10131     }
10132     return 1;
10133 }
10134
10135 /*
10136  * Button procedures
10137  */
10138 void
10139 Reset(redraw, init)
10140      int redraw, init;
10141 {
10142     int i;
10143
10144     if (appData.debugMode) {
10145         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10146                 redraw, init, gameMode);
10147     }
10148     CleanupTail(); // [HGM] vari: delete any stored variations
10149     pausing = pauseExamInvalid = FALSE;
10150     startedFromSetupPosition = blackPlaysFirst = FALSE;
10151     firstMove = TRUE;
10152     whiteFlag = blackFlag = FALSE;
10153     userOfferedDraw = FALSE;
10154     hintRequested = bookRequested = FALSE;
10155     first.maybeThinking = FALSE;
10156     second.maybeThinking = FALSE;
10157     first.bookSuspend = FALSE; // [HGM] book
10158     second.bookSuspend = FALSE;
10159     thinkOutput[0] = NULLCHAR;
10160     lastHint[0] = NULLCHAR;
10161     ClearGameInfo(&gameInfo);
10162     gameInfo.variant = StringToVariant(appData.variant);
10163     ics_user_moved = ics_clock_paused = FALSE;
10164     ics_getting_history = H_FALSE;
10165     ics_gamenum = -1;
10166     white_holding[0] = black_holding[0] = NULLCHAR;
10167     ClearProgramStats();
10168     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10169
10170     ResetFrontEnd();
10171     ClearHighlights();
10172     flipView = appData.flipView;
10173     ClearPremoveHighlights();
10174     gotPremove = FALSE;
10175     alarmSounded = FALSE;
10176
10177     GameEnds(EndOfFile, NULL, GE_PLAYER);
10178     if(appData.serverMovesName != NULL) {
10179         /* [HGM] prepare to make moves file for broadcasting */
10180         clock_t t = clock();
10181         if(serverMoves != NULL) fclose(serverMoves);
10182         serverMoves = fopen(appData.serverMovesName, "r");
10183         if(serverMoves != NULL) {
10184             fclose(serverMoves);
10185             /* delay 15 sec before overwriting, so all clients can see end */
10186             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10187         }
10188         serverMoves = fopen(appData.serverMovesName, "w");
10189     }
10190
10191     ExitAnalyzeMode();
10192     gameMode = BeginningOfGame;
10193     ModeHighlight();
10194     if(appData.icsActive) gameInfo.variant = VariantNormal;
10195     currentMove = forwardMostMove = backwardMostMove = 0;
10196     InitPosition(redraw);
10197     for (i = 0; i < MAX_MOVES; i++) {
10198         if (commentList[i] != NULL) {
10199             free(commentList[i]);
10200             commentList[i] = NULL;
10201         }
10202     }
10203     ResetClocks();
10204     timeRemaining[0][0] = whiteTimeRemaining;
10205     timeRemaining[1][0] = blackTimeRemaining;
10206
10207     if (first.pr == NULL) {
10208         StartChessProgram(&first);
10209     }
10210     if (init) {
10211             InitChessProgram(&first, startedFromSetupPosition);
10212     }
10213     DisplayTitle("");
10214     DisplayMessage("", "");
10215     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10216     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10217 }
10218
10219 void
10220 AutoPlayGameLoop()
10221 {
10222     for (;;) {
10223         if (!AutoPlayOneMove())
10224           return;
10225         if (matchMode || appData.timeDelay == 0)
10226           continue;
10227         if (appData.timeDelay < 0)
10228           return;
10229         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10230         break;
10231     }
10232 }
10233
10234
10235 int
10236 AutoPlayOneMove()
10237 {
10238     int fromX, fromY, toX, toY;
10239
10240     if (appData.debugMode) {
10241       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10242     }
10243
10244     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10245       return FALSE;
10246
10247     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10248       pvInfoList[currentMove].depth = programStats.depth;
10249       pvInfoList[currentMove].score = programStats.score;
10250       pvInfoList[currentMove].time  = 0;
10251       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10252     }
10253
10254     if (currentMove >= forwardMostMove) {
10255       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10256       gameMode = EditGame;
10257       ModeHighlight();
10258
10259       /* [AS] Clear current move marker at the end of a game */
10260       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10261
10262       return FALSE;
10263     }
10264
10265     toX = moveList[currentMove][2] - AAA;
10266     toY = moveList[currentMove][3] - ONE;
10267
10268     if (moveList[currentMove][1] == '@') {
10269         if (appData.highlightLastMove) {
10270             SetHighlights(-1, -1, toX, toY);
10271         }
10272     } else {
10273         fromX = moveList[currentMove][0] - AAA;
10274         fromY = moveList[currentMove][1] - ONE;
10275
10276         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10277
10278         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10279
10280         if (appData.highlightLastMove) {
10281             SetHighlights(fromX, fromY, toX, toY);
10282         }
10283     }
10284     DisplayMove(currentMove);
10285     SendMoveToProgram(currentMove++, &first);
10286     DisplayBothClocks();
10287     DrawPosition(FALSE, boards[currentMove]);
10288     // [HGM] PV info: always display, routine tests if empty
10289     DisplayComment(currentMove - 1, commentList[currentMove]);
10290     return TRUE;
10291 }
10292
10293
10294 int
10295 LoadGameOneMove(readAhead)
10296      ChessMove readAhead;
10297 {
10298     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10299     char promoChar = NULLCHAR;
10300     ChessMove moveType;
10301     char move[MSG_SIZ];
10302     char *p, *q;
10303
10304     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10305         gameMode != AnalyzeMode && gameMode != Training) {
10306         gameFileFP = NULL;
10307         return FALSE;
10308     }
10309
10310     yyboardindex = forwardMostMove;
10311     if (readAhead != EndOfFile) {
10312       moveType = readAhead;
10313     } else {
10314       if (gameFileFP == NULL)
10315           return FALSE;
10316       moveType = (ChessMove) Myylex();
10317     }
10318
10319     done = FALSE;
10320     switch (moveType) {
10321       case Comment:
10322         if (appData.debugMode)
10323           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10324         p = yy_text;
10325
10326         /* append the comment but don't display it */
10327         AppendComment(currentMove, p, FALSE);
10328         return TRUE;
10329
10330       case WhiteCapturesEnPassant:
10331       case BlackCapturesEnPassant:
10332       case WhitePromotion:
10333       case BlackPromotion:
10334       case WhiteNonPromotion:
10335       case BlackNonPromotion:
10336       case NormalMove:
10337       case WhiteKingSideCastle:
10338       case WhiteQueenSideCastle:
10339       case BlackKingSideCastle:
10340       case BlackQueenSideCastle:
10341       case WhiteKingSideCastleWild:
10342       case WhiteQueenSideCastleWild:
10343       case BlackKingSideCastleWild:
10344       case BlackQueenSideCastleWild:
10345       /* PUSH Fabien */
10346       case WhiteHSideCastleFR:
10347       case WhiteASideCastleFR:
10348       case BlackHSideCastleFR:
10349       case BlackASideCastleFR:
10350       /* POP Fabien */
10351         if (appData.debugMode)
10352           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10353         fromX = currentMoveString[0] - AAA;
10354         fromY = currentMoveString[1] - ONE;
10355         toX = currentMoveString[2] - AAA;
10356         toY = currentMoveString[3] - ONE;
10357         promoChar = currentMoveString[4];
10358         break;
10359
10360       case WhiteDrop:
10361       case BlackDrop:
10362         if (appData.debugMode)
10363           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10364         fromX = moveType == WhiteDrop ?
10365           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10366         (int) CharToPiece(ToLower(currentMoveString[0]));
10367         fromY = DROP_RANK;
10368         toX = currentMoveString[2] - AAA;
10369         toY = currentMoveString[3] - ONE;
10370         break;
10371
10372       case WhiteWins:
10373       case BlackWins:
10374       case GameIsDrawn:
10375       case GameUnfinished:
10376         if (appData.debugMode)
10377           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10378         p = strchr(yy_text, '{');
10379         if (p == NULL) p = strchr(yy_text, '(');
10380         if (p == NULL) {
10381             p = yy_text;
10382             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10383         } else {
10384             q = strchr(p, *p == '{' ? '}' : ')');
10385             if (q != NULL) *q = NULLCHAR;
10386             p++;
10387         }
10388         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10389         GameEnds(moveType, p, GE_FILE);
10390         done = TRUE;
10391         if (cmailMsgLoaded) {
10392             ClearHighlights();
10393             flipView = WhiteOnMove(currentMove);
10394             if (moveType == GameUnfinished) flipView = !flipView;
10395             if (appData.debugMode)
10396               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10397         }
10398         break;
10399
10400       case EndOfFile:
10401         if (appData.debugMode)
10402           fprintf(debugFP, "Parser hit end of file\n");
10403         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10404           case MT_NONE:
10405           case MT_CHECK:
10406             break;
10407           case MT_CHECKMATE:
10408           case MT_STAINMATE:
10409             if (WhiteOnMove(currentMove)) {
10410                 GameEnds(BlackWins, "Black mates", GE_FILE);
10411             } else {
10412                 GameEnds(WhiteWins, "White mates", GE_FILE);
10413             }
10414             break;
10415           case MT_STALEMATE:
10416             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10417             break;
10418         }
10419         done = TRUE;
10420         break;
10421
10422       case MoveNumberOne:
10423         if (lastLoadGameStart == GNUChessGame) {
10424             /* GNUChessGames have numbers, but they aren't move numbers */
10425             if (appData.debugMode)
10426               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10427                       yy_text, (int) moveType);
10428             return LoadGameOneMove(EndOfFile); /* tail recursion */
10429         }
10430         /* else fall thru */
10431
10432       case XBoardGame:
10433       case GNUChessGame:
10434       case PGNTag:
10435         /* Reached start of next game in file */
10436         if (appData.debugMode)
10437           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10438         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10439           case MT_NONE:
10440           case MT_CHECK:
10441             break;
10442           case MT_CHECKMATE:
10443           case MT_STAINMATE:
10444             if (WhiteOnMove(currentMove)) {
10445                 GameEnds(BlackWins, "Black mates", GE_FILE);
10446             } else {
10447                 GameEnds(WhiteWins, "White mates", GE_FILE);
10448             }
10449             break;
10450           case MT_STALEMATE:
10451             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10452             break;
10453         }
10454         done = TRUE;
10455         break;
10456
10457       case PositionDiagram:     /* should not happen; ignore */
10458       case ElapsedTime:         /* ignore */
10459       case NAG:                 /* ignore */
10460         if (appData.debugMode)
10461           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10462                   yy_text, (int) moveType);
10463         return LoadGameOneMove(EndOfFile); /* tail recursion */
10464
10465       case IllegalMove:
10466         if (appData.testLegality) {
10467             if (appData.debugMode)
10468               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10469             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10470                     (forwardMostMove / 2) + 1,
10471                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10472             DisplayError(move, 0);
10473             done = TRUE;
10474         } else {
10475             if (appData.debugMode)
10476               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10477                       yy_text, currentMoveString);
10478             fromX = currentMoveString[0] - AAA;
10479             fromY = currentMoveString[1] - ONE;
10480             toX = currentMoveString[2] - AAA;
10481             toY = currentMoveString[3] - ONE;
10482             promoChar = currentMoveString[4];
10483         }
10484         break;
10485
10486       case AmbiguousMove:
10487         if (appData.debugMode)
10488           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10489         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10490                 (forwardMostMove / 2) + 1,
10491                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10492         DisplayError(move, 0);
10493         done = TRUE;
10494         break;
10495
10496       default:
10497       case ImpossibleMove:
10498         if (appData.debugMode)
10499           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10500         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10501                 (forwardMostMove / 2) + 1,
10502                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10503         DisplayError(move, 0);
10504         done = TRUE;
10505         break;
10506     }
10507
10508     if (done) {
10509         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10510             DrawPosition(FALSE, boards[currentMove]);
10511             DisplayBothClocks();
10512             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10513               DisplayComment(currentMove - 1, commentList[currentMove]);
10514         }
10515         (void) StopLoadGameTimer();
10516         gameFileFP = NULL;
10517         cmailOldMove = forwardMostMove;
10518         return FALSE;
10519     } else {
10520         /* currentMoveString is set as a side-effect of yylex */
10521
10522         thinkOutput[0] = NULLCHAR;
10523         MakeMove(fromX, fromY, toX, toY, promoChar);
10524         currentMove = forwardMostMove;
10525         return TRUE;
10526     }
10527 }
10528
10529 /* Load the nth game from the given file */
10530 int
10531 LoadGameFromFile(filename, n, title, useList)
10532      char *filename;
10533      int n;
10534      char *title;
10535      /*Boolean*/ int useList;
10536 {
10537     FILE *f;
10538     char buf[MSG_SIZ];
10539
10540     if (strcmp(filename, "-") == 0) {
10541         f = stdin;
10542         title = "stdin";
10543     } else {
10544         f = fopen(filename, "rb");
10545         if (f == NULL) {
10546           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10547             DisplayError(buf, errno);
10548             return FALSE;
10549         }
10550     }
10551     if (fseek(f, 0, 0) == -1) {
10552         /* f is not seekable; probably a pipe */
10553         useList = FALSE;
10554     }
10555     if (useList && n == 0) {
10556         int error = GameListBuild(f);
10557         if (error) {
10558             DisplayError(_("Cannot build game list"), error);
10559         } else if (!ListEmpty(&gameList) &&
10560                    ((ListGame *) gameList.tailPred)->number > 1) {
10561             GameListPopUp(f, title);
10562             return TRUE;
10563         }
10564         GameListDestroy();
10565         n = 1;
10566     }
10567     if (n == 0) n = 1;
10568     return LoadGame(f, n, title, FALSE);
10569 }
10570
10571
10572 void
10573 MakeRegisteredMove()
10574 {
10575     int fromX, fromY, toX, toY;
10576     char promoChar;
10577     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10578         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10579           case CMAIL_MOVE:
10580           case CMAIL_DRAW:
10581             if (appData.debugMode)
10582               fprintf(debugFP, "Restoring %s for game %d\n",
10583                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10584
10585             thinkOutput[0] = NULLCHAR;
10586             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10587             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10588             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10589             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10590             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10591             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10592             MakeMove(fromX, fromY, toX, toY, promoChar);
10593             ShowMove(fromX, fromY, toX, toY);
10594
10595             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10596               case MT_NONE:
10597               case MT_CHECK:
10598                 break;
10599
10600               case MT_CHECKMATE:
10601               case MT_STAINMATE:
10602                 if (WhiteOnMove(currentMove)) {
10603                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10604                 } else {
10605                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10606                 }
10607                 break;
10608
10609               case MT_STALEMATE:
10610                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10611                 break;
10612             }
10613
10614             break;
10615
10616           case CMAIL_RESIGN:
10617             if (WhiteOnMove(currentMove)) {
10618                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10619             } else {
10620                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10621             }
10622             break;
10623
10624           case CMAIL_ACCEPT:
10625             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10626             break;
10627
10628           default:
10629             break;
10630         }
10631     }
10632
10633     return;
10634 }
10635
10636 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10637 int
10638 CmailLoadGame(f, gameNumber, title, useList)
10639      FILE *f;
10640      int gameNumber;
10641      char *title;
10642      int useList;
10643 {
10644     int retVal;
10645
10646     if (gameNumber > nCmailGames) {
10647         DisplayError(_("No more games in this message"), 0);
10648         return FALSE;
10649     }
10650     if (f == lastLoadGameFP) {
10651         int offset = gameNumber - lastLoadGameNumber;
10652         if (offset == 0) {
10653             cmailMsg[0] = NULLCHAR;
10654             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10655                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10656                 nCmailMovesRegistered--;
10657             }
10658             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10659             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10660                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10661             }
10662         } else {
10663             if (! RegisterMove()) return FALSE;
10664         }
10665     }
10666
10667     retVal = LoadGame(f, gameNumber, title, useList);
10668
10669     /* Make move registered during previous look at this game, if any */
10670     MakeRegisteredMove();
10671
10672     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10673         commentList[currentMove]
10674           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10675         DisplayComment(currentMove - 1, commentList[currentMove]);
10676     }
10677
10678     return retVal;
10679 }
10680
10681 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10682 int
10683 ReloadGame(offset)
10684      int offset;
10685 {
10686     int gameNumber = lastLoadGameNumber + offset;
10687     if (lastLoadGameFP == NULL) {
10688         DisplayError(_("No game has been loaded yet"), 0);
10689         return FALSE;
10690     }
10691     if (gameNumber <= 0) {
10692         DisplayError(_("Can't back up any further"), 0);
10693         return FALSE;
10694     }
10695     if (cmailMsgLoaded) {
10696         return CmailLoadGame(lastLoadGameFP, gameNumber,
10697                              lastLoadGameTitle, lastLoadGameUseList);
10698     } else {
10699         return LoadGame(lastLoadGameFP, gameNumber,
10700                         lastLoadGameTitle, lastLoadGameUseList);
10701     }
10702 }
10703
10704
10705
10706 /* Load the nth game from open file f */
10707 int
10708 LoadGame(f, gameNumber, title, useList)
10709      FILE *f;
10710      int gameNumber;
10711      char *title;
10712      int useList;
10713 {
10714     ChessMove cm;
10715     char buf[MSG_SIZ];
10716     int gn = gameNumber;
10717     ListGame *lg = NULL;
10718     int numPGNTags = 0;
10719     int err;
10720     GameMode oldGameMode;
10721     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10722
10723     if (appData.debugMode)
10724         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10725
10726     if (gameMode == Training )
10727         SetTrainingModeOff();
10728
10729     oldGameMode = gameMode;
10730     if (gameMode != BeginningOfGame) {
10731       Reset(FALSE, TRUE);
10732     }
10733
10734     gameFileFP = f;
10735     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10736         fclose(lastLoadGameFP);
10737     }
10738
10739     if (useList) {
10740         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10741
10742         if (lg) {
10743             fseek(f, lg->offset, 0);
10744             GameListHighlight(gameNumber);
10745             gn = 1;
10746         }
10747         else {
10748             DisplayError(_("Game number out of range"), 0);
10749             return FALSE;
10750         }
10751     } else {
10752         GameListDestroy();
10753         if (fseek(f, 0, 0) == -1) {
10754             if (f == lastLoadGameFP ?
10755                 gameNumber == lastLoadGameNumber + 1 :
10756                 gameNumber == 1) {
10757                 gn = 1;
10758             } else {
10759                 DisplayError(_("Can't seek on game file"), 0);
10760                 return FALSE;
10761             }
10762         }
10763     }
10764     lastLoadGameFP = f;
10765     lastLoadGameNumber = gameNumber;
10766     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10767     lastLoadGameUseList = useList;
10768
10769     yynewfile(f);
10770
10771     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10772       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10773                 lg->gameInfo.black);
10774             DisplayTitle(buf);
10775     } else if (*title != NULLCHAR) {
10776         if (gameNumber > 1) {
10777           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10778             DisplayTitle(buf);
10779         } else {
10780             DisplayTitle(title);
10781         }
10782     }
10783
10784     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10785         gameMode = PlayFromGameFile;
10786         ModeHighlight();
10787     }
10788
10789     currentMove = forwardMostMove = backwardMostMove = 0;
10790     CopyBoard(boards[0], initialPosition);
10791     StopClocks();
10792
10793     /*
10794      * Skip the first gn-1 games in the file.
10795      * Also skip over anything that precedes an identifiable
10796      * start of game marker, to avoid being confused by
10797      * garbage at the start of the file.  Currently
10798      * recognized start of game markers are the move number "1",
10799      * the pattern "gnuchess .* game", the pattern
10800      * "^[#;%] [^ ]* game file", and a PGN tag block.
10801      * A game that starts with one of the latter two patterns
10802      * will also have a move number 1, possibly
10803      * following a position diagram.
10804      * 5-4-02: Let's try being more lenient and allowing a game to
10805      * start with an unnumbered move.  Does that break anything?
10806      */
10807     cm = lastLoadGameStart = EndOfFile;
10808     while (gn > 0) {
10809         yyboardindex = forwardMostMove;
10810         cm = (ChessMove) Myylex();
10811         switch (cm) {
10812           case EndOfFile:
10813             if (cmailMsgLoaded) {
10814                 nCmailGames = CMAIL_MAX_GAMES - gn;
10815             } else {
10816                 Reset(TRUE, TRUE);
10817                 DisplayError(_("Game not found in file"), 0);
10818             }
10819             return FALSE;
10820
10821           case GNUChessGame:
10822           case XBoardGame:
10823             gn--;
10824             lastLoadGameStart = cm;
10825             break;
10826
10827           case MoveNumberOne:
10828             switch (lastLoadGameStart) {
10829               case GNUChessGame:
10830               case XBoardGame:
10831               case PGNTag:
10832                 break;
10833               case MoveNumberOne:
10834               case EndOfFile:
10835                 gn--;           /* count this game */
10836                 lastLoadGameStart = cm;
10837                 break;
10838               default:
10839                 /* impossible */
10840                 break;
10841             }
10842             break;
10843
10844           case PGNTag:
10845             switch (lastLoadGameStart) {
10846               case GNUChessGame:
10847               case PGNTag:
10848               case MoveNumberOne:
10849               case EndOfFile:
10850                 gn--;           /* count this game */
10851                 lastLoadGameStart = cm;
10852                 break;
10853               case XBoardGame:
10854                 lastLoadGameStart = cm; /* game counted already */
10855                 break;
10856               default:
10857                 /* impossible */
10858                 break;
10859             }
10860             if (gn > 0) {
10861                 do {
10862                     yyboardindex = forwardMostMove;
10863                     cm = (ChessMove) Myylex();
10864                 } while (cm == PGNTag || cm == Comment);
10865             }
10866             break;
10867
10868           case WhiteWins:
10869           case BlackWins:
10870           case GameIsDrawn:
10871             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10872                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10873                     != CMAIL_OLD_RESULT) {
10874                     nCmailResults ++ ;
10875                     cmailResult[  CMAIL_MAX_GAMES
10876                                 - gn - 1] = CMAIL_OLD_RESULT;
10877                 }
10878             }
10879             break;
10880
10881           case NormalMove:
10882             /* Only a NormalMove can be at the start of a game
10883              * without a position diagram. */
10884             if (lastLoadGameStart == EndOfFile ) {
10885               gn--;
10886               lastLoadGameStart = MoveNumberOne;
10887             }
10888             break;
10889
10890           default:
10891             break;
10892         }
10893     }
10894
10895     if (appData.debugMode)
10896       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10897
10898     if (cm == XBoardGame) {
10899         /* Skip any header junk before position diagram and/or move 1 */
10900         for (;;) {
10901             yyboardindex = forwardMostMove;
10902             cm = (ChessMove) Myylex();
10903
10904             if (cm == EndOfFile ||
10905                 cm == GNUChessGame || cm == XBoardGame) {
10906                 /* Empty game; pretend end-of-file and handle later */
10907                 cm = EndOfFile;
10908                 break;
10909             }
10910
10911             if (cm == MoveNumberOne || cm == PositionDiagram ||
10912                 cm == PGNTag || cm == Comment)
10913               break;
10914         }
10915     } else if (cm == GNUChessGame) {
10916         if (gameInfo.event != NULL) {
10917             free(gameInfo.event);
10918         }
10919         gameInfo.event = StrSave(yy_text);
10920     }
10921
10922     startedFromSetupPosition = FALSE;
10923     while (cm == PGNTag) {
10924         if (appData.debugMode)
10925           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10926         err = ParsePGNTag(yy_text, &gameInfo);
10927         if (!err) numPGNTags++;
10928
10929         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10930         if(gameInfo.variant != oldVariant) {
10931             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10932             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10933             InitPosition(TRUE);
10934             oldVariant = gameInfo.variant;
10935             if (appData.debugMode)
10936               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10937         }
10938
10939
10940         if (gameInfo.fen != NULL) {
10941           Board initial_position;
10942           startedFromSetupPosition = TRUE;
10943           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10944             Reset(TRUE, TRUE);
10945             DisplayError(_("Bad FEN position in file"), 0);
10946             return FALSE;
10947           }
10948           CopyBoard(boards[0], initial_position);
10949           if (blackPlaysFirst) {
10950             currentMove = forwardMostMove = backwardMostMove = 1;
10951             CopyBoard(boards[1], initial_position);
10952             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10953             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10954             timeRemaining[0][1] = whiteTimeRemaining;
10955             timeRemaining[1][1] = blackTimeRemaining;
10956             if (commentList[0] != NULL) {
10957               commentList[1] = commentList[0];
10958               commentList[0] = NULL;
10959             }
10960           } else {
10961             currentMove = forwardMostMove = backwardMostMove = 0;
10962           }
10963           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10964           {   int i;
10965               initialRulePlies = FENrulePlies;
10966               for( i=0; i< nrCastlingRights; i++ )
10967                   initialRights[i] = initial_position[CASTLING][i];
10968           }
10969           yyboardindex = forwardMostMove;
10970           free(gameInfo.fen);
10971           gameInfo.fen = NULL;
10972         }
10973
10974         yyboardindex = forwardMostMove;
10975         cm = (ChessMove) Myylex();
10976
10977         /* Handle comments interspersed among the tags */
10978         while (cm == Comment) {
10979             char *p;
10980             if (appData.debugMode)
10981               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10982             p = yy_text;
10983             AppendComment(currentMove, p, FALSE);
10984             yyboardindex = forwardMostMove;
10985             cm = (ChessMove) Myylex();
10986         }
10987     }
10988
10989     /* don't rely on existence of Event tag since if game was
10990      * pasted from clipboard the Event tag may not exist
10991      */
10992     if (numPGNTags > 0){
10993         char *tags;
10994         if (gameInfo.variant == VariantNormal) {
10995           VariantClass v = StringToVariant(gameInfo.event);
10996           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10997           if(v < VariantShogi) gameInfo.variant = v;
10998         }
10999         if (!matchMode) {
11000           if( appData.autoDisplayTags ) {
11001             tags = PGNTags(&gameInfo);
11002             TagsPopUp(tags, CmailMsg());
11003             free(tags);
11004           }
11005         }
11006     } else {
11007         /* Make something up, but don't display it now */
11008         SetGameInfo();
11009         TagsPopDown();
11010     }
11011
11012     if (cm == PositionDiagram) {
11013         int i, j;
11014         char *p;
11015         Board initial_position;
11016
11017         if (appData.debugMode)
11018           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11019
11020         if (!startedFromSetupPosition) {
11021             p = yy_text;
11022             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11023               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11024                 switch (*p) {
11025                   case '{':
11026                   case '[':
11027                   case '-':
11028                   case ' ':
11029                   case '\t':
11030                   case '\n':
11031                   case '\r':
11032                     break;
11033                   default:
11034                     initial_position[i][j++] = CharToPiece(*p);
11035                     break;
11036                 }
11037             while (*p == ' ' || *p == '\t' ||
11038                    *p == '\n' || *p == '\r') p++;
11039
11040             if (strncmp(p, "black", strlen("black"))==0)
11041               blackPlaysFirst = TRUE;
11042             else
11043               blackPlaysFirst = FALSE;
11044             startedFromSetupPosition = TRUE;
11045
11046             CopyBoard(boards[0], initial_position);
11047             if (blackPlaysFirst) {
11048                 currentMove = forwardMostMove = backwardMostMove = 1;
11049                 CopyBoard(boards[1], initial_position);
11050                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11051                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11052                 timeRemaining[0][1] = whiteTimeRemaining;
11053                 timeRemaining[1][1] = blackTimeRemaining;
11054                 if (commentList[0] != NULL) {
11055                     commentList[1] = commentList[0];
11056                     commentList[0] = NULL;
11057                 }
11058             } else {
11059                 currentMove = forwardMostMove = backwardMostMove = 0;
11060             }
11061         }
11062         yyboardindex = forwardMostMove;
11063         cm = (ChessMove) Myylex();
11064     }
11065
11066     if (first.pr == NoProc) {
11067         StartChessProgram(&first);
11068     }
11069     InitChessProgram(&first, FALSE);
11070     SendToProgram("force\n", &first);
11071     if (startedFromSetupPosition) {
11072         SendBoard(&first, forwardMostMove);
11073     if (appData.debugMode) {
11074         fprintf(debugFP, "Load Game\n");
11075     }
11076         DisplayBothClocks();
11077     }
11078
11079     /* [HGM] server: flag to write setup moves in broadcast file as one */
11080     loadFlag = appData.suppressLoadMoves;
11081
11082     while (cm == Comment) {
11083         char *p;
11084         if (appData.debugMode)
11085           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11086         p = yy_text;
11087         AppendComment(currentMove, p, FALSE);
11088         yyboardindex = forwardMostMove;
11089         cm = (ChessMove) Myylex();
11090     }
11091
11092     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11093         cm == WhiteWins || cm == BlackWins ||
11094         cm == GameIsDrawn || cm == GameUnfinished) {
11095         DisplayMessage("", _("No moves in game"));
11096         if (cmailMsgLoaded) {
11097             if (appData.debugMode)
11098               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11099             ClearHighlights();
11100             flipView = FALSE;
11101         }
11102         DrawPosition(FALSE, boards[currentMove]);
11103         DisplayBothClocks();
11104         gameMode = EditGame;
11105         ModeHighlight();
11106         gameFileFP = NULL;
11107         cmailOldMove = 0;
11108         return TRUE;
11109     }
11110
11111     // [HGM] PV info: routine tests if comment empty
11112     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11113         DisplayComment(currentMove - 1, commentList[currentMove]);
11114     }
11115     if (!matchMode && appData.timeDelay != 0)
11116       DrawPosition(FALSE, boards[currentMove]);
11117
11118     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11119       programStats.ok_to_send = 1;
11120     }
11121
11122     /* if the first token after the PGN tags is a move
11123      * and not move number 1, retrieve it from the parser
11124      */
11125     if (cm != MoveNumberOne)
11126         LoadGameOneMove(cm);
11127
11128     /* load the remaining moves from the file */
11129     while (LoadGameOneMove(EndOfFile)) {
11130       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11131       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11132     }
11133
11134     /* rewind to the start of the game */
11135     currentMove = backwardMostMove;
11136
11137     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11138
11139     if (oldGameMode == AnalyzeFile ||
11140         oldGameMode == AnalyzeMode) {
11141       AnalyzeFileEvent();
11142     }
11143
11144     if (matchMode || appData.timeDelay == 0) {
11145       ToEndEvent();
11146       gameMode = EditGame;
11147       ModeHighlight();
11148     } else if (appData.timeDelay > 0) {
11149       AutoPlayGameLoop();
11150     }
11151
11152     if (appData.debugMode)
11153         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11154
11155     loadFlag = 0; /* [HGM] true game starts */
11156     return TRUE;
11157 }
11158
11159 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11160 int
11161 ReloadPosition(offset)
11162      int offset;
11163 {
11164     int positionNumber = lastLoadPositionNumber + offset;
11165     if (lastLoadPositionFP == NULL) {
11166         DisplayError(_("No position has been loaded yet"), 0);
11167         return FALSE;
11168     }
11169     if (positionNumber <= 0) {
11170         DisplayError(_("Can't back up any further"), 0);
11171         return FALSE;
11172     }
11173     return LoadPosition(lastLoadPositionFP, positionNumber,
11174                         lastLoadPositionTitle);
11175 }
11176
11177 /* Load the nth position from the given file */
11178 int
11179 LoadPositionFromFile(filename, n, title)
11180      char *filename;
11181      int n;
11182      char *title;
11183 {
11184     FILE *f;
11185     char buf[MSG_SIZ];
11186
11187     if (strcmp(filename, "-") == 0) {
11188         return LoadPosition(stdin, n, "stdin");
11189     } else {
11190         f = fopen(filename, "rb");
11191         if (f == NULL) {
11192             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11193             DisplayError(buf, errno);
11194             return FALSE;
11195         } else {
11196             return LoadPosition(f, n, title);
11197         }
11198     }
11199 }
11200
11201 /* Load the nth position from the given open file, and close it */
11202 int
11203 LoadPosition(f, positionNumber, title)
11204      FILE *f;
11205      int positionNumber;
11206      char *title;
11207 {
11208     char *p, line[MSG_SIZ];
11209     Board initial_position;
11210     int i, j, fenMode, pn;
11211
11212     if (gameMode == Training )
11213         SetTrainingModeOff();
11214
11215     if (gameMode != BeginningOfGame) {
11216         Reset(FALSE, TRUE);
11217     }
11218     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11219         fclose(lastLoadPositionFP);
11220     }
11221     if (positionNumber == 0) positionNumber = 1;
11222     lastLoadPositionFP = f;
11223     lastLoadPositionNumber = positionNumber;
11224     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11225     if (first.pr == NoProc) {
11226       StartChessProgram(&first);
11227       InitChessProgram(&first, FALSE);
11228     }
11229     pn = positionNumber;
11230     if (positionNumber < 0) {
11231         /* Negative position number means to seek to that byte offset */
11232         if (fseek(f, -positionNumber, 0) == -1) {
11233             DisplayError(_("Can't seek on position file"), 0);
11234             return FALSE;
11235         };
11236         pn = 1;
11237     } else {
11238         if (fseek(f, 0, 0) == -1) {
11239             if (f == lastLoadPositionFP ?
11240                 positionNumber == lastLoadPositionNumber + 1 :
11241                 positionNumber == 1) {
11242                 pn = 1;
11243             } else {
11244                 DisplayError(_("Can't seek on position file"), 0);
11245                 return FALSE;
11246             }
11247         }
11248     }
11249     /* See if this file is FEN or old-style xboard */
11250     if (fgets(line, MSG_SIZ, f) == NULL) {
11251         DisplayError(_("Position not found in file"), 0);
11252         return FALSE;
11253     }
11254     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11255     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11256
11257     if (pn >= 2) {
11258         if (fenMode || line[0] == '#') pn--;
11259         while (pn > 0) {
11260             /* skip positions before number pn */
11261             if (fgets(line, MSG_SIZ, f) == NULL) {
11262                 Reset(TRUE, TRUE);
11263                 DisplayError(_("Position not found in file"), 0);
11264                 return FALSE;
11265             }
11266             if (fenMode || line[0] == '#') pn--;
11267         }
11268     }
11269
11270     if (fenMode) {
11271         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11272             DisplayError(_("Bad FEN position in file"), 0);
11273             return FALSE;
11274         }
11275     } else {
11276         (void) fgets(line, MSG_SIZ, f);
11277         (void) fgets(line, MSG_SIZ, f);
11278
11279         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11280             (void) fgets(line, MSG_SIZ, f);
11281             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11282                 if (*p == ' ')
11283                   continue;
11284                 initial_position[i][j++] = CharToPiece(*p);
11285             }
11286         }
11287
11288         blackPlaysFirst = FALSE;
11289         if (!feof(f)) {
11290             (void) fgets(line, MSG_SIZ, f);
11291             if (strncmp(line, "black", strlen("black"))==0)
11292               blackPlaysFirst = TRUE;
11293         }
11294     }
11295     startedFromSetupPosition = TRUE;
11296
11297     SendToProgram("force\n", &first);
11298     CopyBoard(boards[0], initial_position);
11299     if (blackPlaysFirst) {
11300         currentMove = forwardMostMove = backwardMostMove = 1;
11301         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11302         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11303         CopyBoard(boards[1], initial_position);
11304         DisplayMessage("", _("Black to play"));
11305     } else {
11306         currentMove = forwardMostMove = backwardMostMove = 0;
11307         DisplayMessage("", _("White to play"));
11308     }
11309     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11310     SendBoard(&first, forwardMostMove);
11311     if (appData.debugMode) {
11312 int i, j;
11313   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11314   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11315         fprintf(debugFP, "Load Position\n");
11316     }
11317
11318     if (positionNumber > 1) {
11319       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11320         DisplayTitle(line);
11321     } else {
11322         DisplayTitle(title);
11323     }
11324     gameMode = EditGame;
11325     ModeHighlight();
11326     ResetClocks();
11327     timeRemaining[0][1] = whiteTimeRemaining;
11328     timeRemaining[1][1] = blackTimeRemaining;
11329     DrawPosition(FALSE, boards[currentMove]);
11330
11331     return TRUE;
11332 }
11333
11334
11335 void
11336 CopyPlayerNameIntoFileName(dest, src)
11337      char **dest, *src;
11338 {
11339     while (*src != NULLCHAR && *src != ',') {
11340         if (*src == ' ') {
11341             *(*dest)++ = '_';
11342             src++;
11343         } else {
11344             *(*dest)++ = *src++;
11345         }
11346     }
11347 }
11348
11349 char *DefaultFileName(ext)
11350      char *ext;
11351 {
11352     static char def[MSG_SIZ];
11353     char *p;
11354
11355     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11356         p = def;
11357         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11358         *p++ = '-';
11359         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11360         *p++ = '.';
11361         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11362     } else {
11363         def[0] = NULLCHAR;
11364     }
11365     return def;
11366 }
11367
11368 /* Save the current game to the given file */
11369 int
11370 SaveGameToFile(filename, append)
11371      char *filename;
11372      int append;
11373 {
11374     FILE *f;
11375     char buf[MSG_SIZ];
11376     int result;
11377
11378     if (strcmp(filename, "-") == 0) {
11379         return SaveGame(stdout, 0, NULL);
11380     } else {
11381         f = fopen(filename, append ? "a" : "w");
11382         if (f == NULL) {
11383             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11384             DisplayError(buf, errno);
11385             return FALSE;
11386         } else {
11387             safeStrCpy(buf, lastMsg, MSG_SIZ);
11388             DisplayMessage(_("Waiting for access to save file"), "");
11389             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11390             DisplayMessage(_("Saving game"), "");
11391             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11392             result = SaveGame(f, 0, NULL);
11393             DisplayMessage(buf, "");
11394             return result;
11395         }
11396     }
11397 }
11398
11399 char *
11400 SavePart(str)
11401      char *str;
11402 {
11403     static char buf[MSG_SIZ];
11404     char *p;
11405
11406     p = strchr(str, ' ');
11407     if (p == NULL) return str;
11408     strncpy(buf, str, p - str);
11409     buf[p - str] = NULLCHAR;
11410     return buf;
11411 }
11412
11413 #define PGN_MAX_LINE 75
11414
11415 #define PGN_SIDE_WHITE  0
11416 #define PGN_SIDE_BLACK  1
11417
11418 /* [AS] */
11419 static int FindFirstMoveOutOfBook( int side )
11420 {
11421     int result = -1;
11422
11423     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11424         int index = backwardMostMove;
11425         int has_book_hit = 0;
11426
11427         if( (index % 2) != side ) {
11428             index++;
11429         }
11430
11431         while( index < forwardMostMove ) {
11432             /* Check to see if engine is in book */
11433             int depth = pvInfoList[index].depth;
11434             int score = pvInfoList[index].score;
11435             int in_book = 0;
11436
11437             if( depth <= 2 ) {
11438                 in_book = 1;
11439             }
11440             else if( score == 0 && depth == 63 ) {
11441                 in_book = 1; /* Zappa */
11442             }
11443             else if( score == 2 && depth == 99 ) {
11444                 in_book = 1; /* Abrok */
11445             }
11446
11447             has_book_hit += in_book;
11448
11449             if( ! in_book ) {
11450                 result = index;
11451
11452                 break;
11453             }
11454
11455             index += 2;
11456         }
11457     }
11458
11459     return result;
11460 }
11461
11462 /* [AS] */
11463 void GetOutOfBookInfo( char * buf )
11464 {
11465     int oob[2];
11466     int i;
11467     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11468
11469     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11470     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11471
11472     *buf = '\0';
11473
11474     if( oob[0] >= 0 || oob[1] >= 0 ) {
11475         for( i=0; i<2; i++ ) {
11476             int idx = oob[i];
11477
11478             if( idx >= 0 ) {
11479                 if( i > 0 && oob[0] >= 0 ) {
11480                     strcat( buf, "   " );
11481                 }
11482
11483                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11484                 sprintf( buf+strlen(buf), "%s%.2f",
11485                     pvInfoList[idx].score >= 0 ? "+" : "",
11486                     pvInfoList[idx].score / 100.0 );
11487             }
11488         }
11489     }
11490 }
11491
11492 /* Save game in PGN style and close the file */
11493 int
11494 SaveGamePGN(f)
11495      FILE *f;
11496 {
11497     int i, offset, linelen, newblock;
11498     time_t tm;
11499 //    char *movetext;
11500     char numtext[32];
11501     int movelen, numlen, blank;
11502     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11503
11504     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11505
11506     tm = time((time_t *) NULL);
11507
11508     PrintPGNTags(f, &gameInfo);
11509
11510     if (backwardMostMove > 0 || startedFromSetupPosition) {
11511         char *fen = PositionToFEN(backwardMostMove, NULL);
11512         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11513         fprintf(f, "\n{--------------\n");
11514         PrintPosition(f, backwardMostMove);
11515         fprintf(f, "--------------}\n");
11516         free(fen);
11517     }
11518     else {
11519         /* [AS] Out of book annotation */
11520         if( appData.saveOutOfBookInfo ) {
11521             char buf[64];
11522
11523             GetOutOfBookInfo( buf );
11524
11525             if( buf[0] != '\0' ) {
11526                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11527             }
11528         }
11529
11530         fprintf(f, "\n");
11531     }
11532
11533     i = backwardMostMove;
11534     linelen = 0;
11535     newblock = TRUE;
11536
11537     while (i < forwardMostMove) {
11538         /* Print comments preceding this move */
11539         if (commentList[i] != NULL) {
11540             if (linelen > 0) fprintf(f, "\n");
11541             fprintf(f, "%s", commentList[i]);
11542             linelen = 0;
11543             newblock = TRUE;
11544         }
11545
11546         /* Format move number */
11547         if ((i % 2) == 0)
11548           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11549         else
11550           if (newblock)
11551             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11552           else
11553             numtext[0] = NULLCHAR;
11554
11555         numlen = strlen(numtext);
11556         newblock = FALSE;
11557
11558         /* Print move number */
11559         blank = linelen > 0 && numlen > 0;
11560         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11561             fprintf(f, "\n");
11562             linelen = 0;
11563             blank = 0;
11564         }
11565         if (blank) {
11566             fprintf(f, " ");
11567             linelen++;
11568         }
11569         fprintf(f, "%s", numtext);
11570         linelen += numlen;
11571
11572         /* Get move */
11573         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11574         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11575
11576         /* Print move */
11577         blank = linelen > 0 && movelen > 0;
11578         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11579             fprintf(f, "\n");
11580             linelen = 0;
11581             blank = 0;
11582         }
11583         if (blank) {
11584             fprintf(f, " ");
11585             linelen++;
11586         }
11587         fprintf(f, "%s", move_buffer);
11588         linelen += movelen;
11589
11590         /* [AS] Add PV info if present */
11591         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11592             /* [HGM] add time */
11593             char buf[MSG_SIZ]; int seconds;
11594
11595             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11596
11597             if( seconds <= 0)
11598               buf[0] = 0;
11599             else
11600               if( seconds < 30 )
11601                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11602               else
11603                 {
11604                   seconds = (seconds + 4)/10; // round to full seconds
11605                   if( seconds < 60 )
11606                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11607                   else
11608                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11609                 }
11610
11611             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11612                       pvInfoList[i].score >= 0 ? "+" : "",
11613                       pvInfoList[i].score / 100.0,
11614                       pvInfoList[i].depth,
11615                       buf );
11616
11617             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11618
11619             /* Print score/depth */
11620             blank = linelen > 0 && movelen > 0;
11621             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11622                 fprintf(f, "\n");
11623                 linelen = 0;
11624                 blank = 0;
11625             }
11626             if (blank) {
11627                 fprintf(f, " ");
11628                 linelen++;
11629             }
11630             fprintf(f, "%s", move_buffer);
11631             linelen += movelen;
11632         }
11633
11634         i++;
11635     }
11636
11637     /* Start a new line */
11638     if (linelen > 0) fprintf(f, "\n");
11639
11640     /* Print comments after last move */
11641     if (commentList[i] != NULL) {
11642         fprintf(f, "%s\n", commentList[i]);
11643     }
11644
11645     /* Print result */
11646     if (gameInfo.resultDetails != NULL &&
11647         gameInfo.resultDetails[0] != NULLCHAR) {
11648         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11649                 PGNResult(gameInfo.result));
11650     } else {
11651         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11652     }
11653
11654     fclose(f);
11655     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11656     return TRUE;
11657 }
11658
11659 /* Save game in old style and close the file */
11660 int
11661 SaveGameOldStyle(f)
11662      FILE *f;
11663 {
11664     int i, offset;
11665     time_t tm;
11666
11667     tm = time((time_t *) NULL);
11668
11669     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11670     PrintOpponents(f);
11671
11672     if (backwardMostMove > 0 || startedFromSetupPosition) {
11673         fprintf(f, "\n[--------------\n");
11674         PrintPosition(f, backwardMostMove);
11675         fprintf(f, "--------------]\n");
11676     } else {
11677         fprintf(f, "\n");
11678     }
11679
11680     i = backwardMostMove;
11681     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11682
11683     while (i < forwardMostMove) {
11684         if (commentList[i] != NULL) {
11685             fprintf(f, "[%s]\n", commentList[i]);
11686         }
11687
11688         if ((i % 2) == 1) {
11689             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11690             i++;
11691         } else {
11692             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11693             i++;
11694             if (commentList[i] != NULL) {
11695                 fprintf(f, "\n");
11696                 continue;
11697             }
11698             if (i >= forwardMostMove) {
11699                 fprintf(f, "\n");
11700                 break;
11701             }
11702             fprintf(f, "%s\n", parseList[i]);
11703             i++;
11704         }
11705     }
11706
11707     if (commentList[i] != NULL) {
11708         fprintf(f, "[%s]\n", commentList[i]);
11709     }
11710
11711     /* This isn't really the old style, but it's close enough */
11712     if (gameInfo.resultDetails != NULL &&
11713         gameInfo.resultDetails[0] != NULLCHAR) {
11714         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11715                 gameInfo.resultDetails);
11716     } else {
11717         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11718     }
11719
11720     fclose(f);
11721     return TRUE;
11722 }
11723
11724 /* Save the current game to open file f and close the file */
11725 int
11726 SaveGame(f, dummy, dummy2)
11727      FILE *f;
11728      int dummy;
11729      char *dummy2;
11730 {
11731     if (gameMode == EditPosition) EditPositionDone(TRUE);
11732     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11733     if (appData.oldSaveStyle)
11734       return SaveGameOldStyle(f);
11735     else
11736       return SaveGamePGN(f);
11737 }
11738
11739 /* Save the current position to the given file */
11740 int
11741 SavePositionToFile(filename)
11742      char *filename;
11743 {
11744     FILE *f;
11745     char buf[MSG_SIZ];
11746
11747     if (strcmp(filename, "-") == 0) {
11748         return SavePosition(stdout, 0, NULL);
11749     } else {
11750         f = fopen(filename, "a");
11751         if (f == NULL) {
11752             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11753             DisplayError(buf, errno);
11754             return FALSE;
11755         } else {
11756             safeStrCpy(buf, lastMsg, MSG_SIZ);
11757             DisplayMessage(_("Waiting for access to save file"), "");
11758             flock(fileno(f), LOCK_EX); // [HGM] lock
11759             DisplayMessage(_("Saving position"), "");
11760             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11761             SavePosition(f, 0, NULL);
11762             DisplayMessage(buf, "");
11763             return TRUE;
11764         }
11765     }
11766 }
11767
11768 /* Save the current position to the given open file and close the file */
11769 int
11770 SavePosition(f, dummy, dummy2)
11771      FILE *f;
11772      int dummy;
11773      char *dummy2;
11774 {
11775     time_t tm;
11776     char *fen;
11777
11778     if (gameMode == EditPosition) EditPositionDone(TRUE);
11779     if (appData.oldSaveStyle) {
11780         tm = time((time_t *) NULL);
11781
11782         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11783         PrintOpponents(f);
11784         fprintf(f, "[--------------\n");
11785         PrintPosition(f, currentMove);
11786         fprintf(f, "--------------]\n");
11787     } else {
11788         fen = PositionToFEN(currentMove, NULL);
11789         fprintf(f, "%s\n", fen);
11790         free(fen);
11791     }
11792     fclose(f);
11793     return TRUE;
11794 }
11795
11796 void
11797 ReloadCmailMsgEvent(unregister)
11798      int unregister;
11799 {
11800 #if !WIN32
11801     static char *inFilename = NULL;
11802     static char *outFilename;
11803     int i;
11804     struct stat inbuf, outbuf;
11805     int status;
11806
11807     /* Any registered moves are unregistered if unregister is set, */
11808     /* i.e. invoked by the signal handler */
11809     if (unregister) {
11810         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11811             cmailMoveRegistered[i] = FALSE;
11812             if (cmailCommentList[i] != NULL) {
11813                 free(cmailCommentList[i]);
11814                 cmailCommentList[i] = NULL;
11815             }
11816         }
11817         nCmailMovesRegistered = 0;
11818     }
11819
11820     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11821         cmailResult[i] = CMAIL_NOT_RESULT;
11822     }
11823     nCmailResults = 0;
11824
11825     if (inFilename == NULL) {
11826         /* Because the filenames are static they only get malloced once  */
11827         /* and they never get freed                                      */
11828         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11829         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11830
11831         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11832         sprintf(outFilename, "%s.out", appData.cmailGameName);
11833     }
11834
11835     status = stat(outFilename, &outbuf);
11836     if (status < 0) {
11837         cmailMailedMove = FALSE;
11838     } else {
11839         status = stat(inFilename, &inbuf);
11840         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11841     }
11842
11843     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11844        counts the games, notes how each one terminated, etc.
11845
11846        It would be nice to remove this kludge and instead gather all
11847        the information while building the game list.  (And to keep it
11848        in the game list nodes instead of having a bunch of fixed-size
11849        parallel arrays.)  Note this will require getting each game's
11850        termination from the PGN tags, as the game list builder does
11851        not process the game moves.  --mann
11852        */
11853     cmailMsgLoaded = TRUE;
11854     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11855
11856     /* Load first game in the file or popup game menu */
11857     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11858
11859 #endif /* !WIN32 */
11860     return;
11861 }
11862
11863 int
11864 RegisterMove()
11865 {
11866     FILE *f;
11867     char string[MSG_SIZ];
11868
11869     if (   cmailMailedMove
11870         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11871         return TRUE;            /* Allow free viewing  */
11872     }
11873
11874     /* Unregister move to ensure that we don't leave RegisterMove        */
11875     /* with the move registered when the conditions for registering no   */
11876     /* longer hold                                                       */
11877     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11878         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11879         nCmailMovesRegistered --;
11880
11881         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11882           {
11883               free(cmailCommentList[lastLoadGameNumber - 1]);
11884               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11885           }
11886     }
11887
11888     if (cmailOldMove == -1) {
11889         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11890         return FALSE;
11891     }
11892
11893     if (currentMove > cmailOldMove + 1) {
11894         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11895         return FALSE;
11896     }
11897
11898     if (currentMove < cmailOldMove) {
11899         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11900         return FALSE;
11901     }
11902
11903     if (forwardMostMove > currentMove) {
11904         /* Silently truncate extra moves */
11905         TruncateGame();
11906     }
11907
11908     if (   (currentMove == cmailOldMove + 1)
11909         || (   (currentMove == cmailOldMove)
11910             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11911                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11912         if (gameInfo.result != GameUnfinished) {
11913             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11914         }
11915
11916         if (commentList[currentMove] != NULL) {
11917             cmailCommentList[lastLoadGameNumber - 1]
11918               = StrSave(commentList[currentMove]);
11919         }
11920         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11921
11922         if (appData.debugMode)
11923           fprintf(debugFP, "Saving %s for game %d\n",
11924                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11925
11926         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11927
11928         f = fopen(string, "w");
11929         if (appData.oldSaveStyle) {
11930             SaveGameOldStyle(f); /* also closes the file */
11931
11932             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11933             f = fopen(string, "w");
11934             SavePosition(f, 0, NULL); /* also closes the file */
11935         } else {
11936             fprintf(f, "{--------------\n");
11937             PrintPosition(f, currentMove);
11938             fprintf(f, "--------------}\n\n");
11939
11940             SaveGame(f, 0, NULL); /* also closes the file*/
11941         }
11942
11943         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11944         nCmailMovesRegistered ++;
11945     } else if (nCmailGames == 1) {
11946         DisplayError(_("You have not made a move yet"), 0);
11947         return FALSE;
11948     }
11949
11950     return TRUE;
11951 }
11952
11953 void
11954 MailMoveEvent()
11955 {
11956 #if !WIN32
11957     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11958     FILE *commandOutput;
11959     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11960     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11961     int nBuffers;
11962     int i;
11963     int archived;
11964     char *arcDir;
11965
11966     if (! cmailMsgLoaded) {
11967         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11968         return;
11969     }
11970
11971     if (nCmailGames == nCmailResults) {
11972         DisplayError(_("No unfinished games"), 0);
11973         return;
11974     }
11975
11976 #if CMAIL_PROHIBIT_REMAIL
11977     if (cmailMailedMove) {
11978       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);
11979         DisplayError(msg, 0);
11980         return;
11981     }
11982 #endif
11983
11984     if (! (cmailMailedMove || RegisterMove())) return;
11985
11986     if (   cmailMailedMove
11987         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11988       snprintf(string, MSG_SIZ, partCommandString,
11989                appData.debugMode ? " -v" : "", appData.cmailGameName);
11990         commandOutput = popen(string, "r");
11991
11992         if (commandOutput == NULL) {
11993             DisplayError(_("Failed to invoke cmail"), 0);
11994         } else {
11995             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11996                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11997             }
11998             if (nBuffers > 1) {
11999                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12000                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12001                 nBytes = MSG_SIZ - 1;
12002             } else {
12003                 (void) memcpy(msg, buffer, nBytes);
12004             }
12005             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12006
12007             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12008                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12009
12010                 archived = TRUE;
12011                 for (i = 0; i < nCmailGames; i ++) {
12012                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12013                         archived = FALSE;
12014                     }
12015                 }
12016                 if (   archived
12017                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12018                         != NULL)) {
12019                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12020                            arcDir,
12021                            appData.cmailGameName,
12022                            gameInfo.date);
12023                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12024                     cmailMsgLoaded = FALSE;
12025                 }
12026             }
12027
12028             DisplayInformation(msg);
12029             pclose(commandOutput);
12030         }
12031     } else {
12032         if ((*cmailMsg) != '\0') {
12033             DisplayInformation(cmailMsg);
12034         }
12035     }
12036
12037     return;
12038 #endif /* !WIN32 */
12039 }
12040
12041 char *
12042 CmailMsg()
12043 {
12044 #if WIN32
12045     return NULL;
12046 #else
12047     int  prependComma = 0;
12048     char number[5];
12049     char string[MSG_SIZ];       /* Space for game-list */
12050     int  i;
12051
12052     if (!cmailMsgLoaded) return "";
12053
12054     if (cmailMailedMove) {
12055       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12056     } else {
12057         /* Create a list of games left */
12058       snprintf(string, MSG_SIZ, "[");
12059         for (i = 0; i < nCmailGames; i ++) {
12060             if (! (   cmailMoveRegistered[i]
12061                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12062                 if (prependComma) {
12063                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12064                 } else {
12065                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12066                     prependComma = 1;
12067                 }
12068
12069                 strcat(string, number);
12070             }
12071         }
12072         strcat(string, "]");
12073
12074         if (nCmailMovesRegistered + nCmailResults == 0) {
12075             switch (nCmailGames) {
12076               case 1:
12077                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12078                 break;
12079
12080               case 2:
12081                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12082                 break;
12083
12084               default:
12085                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12086                          nCmailGames);
12087                 break;
12088             }
12089         } else {
12090             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12091               case 1:
12092                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12093                          string);
12094                 break;
12095
12096               case 0:
12097                 if (nCmailResults == nCmailGames) {
12098                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12099                 } else {
12100                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12101                 }
12102                 break;
12103
12104               default:
12105                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12106                          string);
12107             }
12108         }
12109     }
12110     return cmailMsg;
12111 #endif /* WIN32 */
12112 }
12113
12114 void
12115 ResetGameEvent()
12116 {
12117     if (gameMode == Training)
12118       SetTrainingModeOff();
12119
12120     Reset(TRUE, TRUE);
12121     cmailMsgLoaded = FALSE;
12122     if (appData.icsActive) {
12123       SendToICS(ics_prefix);
12124       SendToICS("refresh\n");
12125     }
12126 }
12127
12128 void
12129 ExitEvent(status)
12130      int status;
12131 {
12132     exiting++;
12133     if (exiting > 2) {
12134       /* Give up on clean exit */
12135       exit(status);
12136     }
12137     if (exiting > 1) {
12138       /* Keep trying for clean exit */
12139       return;
12140     }
12141
12142     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12143
12144     if (telnetISR != NULL) {
12145       RemoveInputSource(telnetISR);
12146     }
12147     if (icsPR != NoProc) {
12148       DestroyChildProcess(icsPR, TRUE);
12149     }
12150
12151     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12152     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12153
12154     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12155     /* make sure this other one finishes before killing it!                  */
12156     if(endingGame) { int count = 0;
12157         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12158         while(endingGame && count++ < 10) DoSleep(1);
12159         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12160     }
12161
12162     /* Kill off chess programs */
12163     if (first.pr != NoProc) {
12164         ExitAnalyzeMode();
12165
12166         DoSleep( appData.delayBeforeQuit );
12167         SendToProgram("quit\n", &first);
12168         DoSleep( appData.delayAfterQuit );
12169         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12170     }
12171     if (second.pr != NoProc) {
12172         DoSleep( appData.delayBeforeQuit );
12173         SendToProgram("quit\n", &second);
12174         DoSleep( appData.delayAfterQuit );
12175         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12176     }
12177     if (first.isr != NULL) {
12178         RemoveInputSource(first.isr);
12179     }
12180     if (second.isr != NULL) {
12181         RemoveInputSource(second.isr);
12182     }
12183
12184     ShutDownFrontEnd();
12185     exit(status);
12186 }
12187
12188 void
12189 PauseEvent()
12190 {
12191     if (appData.debugMode)
12192         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12193     if (pausing) {
12194         pausing = FALSE;
12195         ModeHighlight();
12196         if (gameMode == MachinePlaysWhite ||
12197             gameMode == MachinePlaysBlack) {
12198             StartClocks();
12199         } else {
12200             DisplayBothClocks();
12201         }
12202         if (gameMode == PlayFromGameFile) {
12203             if (appData.timeDelay >= 0)
12204                 AutoPlayGameLoop();
12205         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12206             Reset(FALSE, TRUE);
12207             SendToICS(ics_prefix);
12208             SendToICS("refresh\n");
12209         } else if (currentMove < forwardMostMove) {
12210             ForwardInner(forwardMostMove);
12211         }
12212         pauseExamInvalid = FALSE;
12213     } else {
12214         switch (gameMode) {
12215           default:
12216             return;
12217           case IcsExamining:
12218             pauseExamForwardMostMove = forwardMostMove;
12219             pauseExamInvalid = FALSE;
12220             /* fall through */
12221           case IcsObserving:
12222           case IcsPlayingWhite:
12223           case IcsPlayingBlack:
12224             pausing = TRUE;
12225             ModeHighlight();
12226             return;
12227           case PlayFromGameFile:
12228             (void) StopLoadGameTimer();
12229             pausing = TRUE;
12230             ModeHighlight();
12231             break;
12232           case BeginningOfGame:
12233             if (appData.icsActive) return;
12234             /* else fall through */
12235           case MachinePlaysWhite:
12236           case MachinePlaysBlack:
12237           case TwoMachinesPlay:
12238             if (forwardMostMove == 0)
12239               return;           /* don't pause if no one has moved */
12240             if ((gameMode == MachinePlaysWhite &&
12241                  !WhiteOnMove(forwardMostMove)) ||
12242                 (gameMode == MachinePlaysBlack &&
12243                  WhiteOnMove(forwardMostMove))) {
12244                 StopClocks();
12245             }
12246             pausing = TRUE;
12247             ModeHighlight();
12248             break;
12249         }
12250     }
12251 }
12252
12253 void
12254 EditCommentEvent()
12255 {
12256     char title[MSG_SIZ];
12257
12258     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12259       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12260     } else {
12261       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12262                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12263                parseList[currentMove - 1]);
12264     }
12265
12266     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12267 }
12268
12269
12270 void
12271 EditTagsEvent()
12272 {
12273     char *tags = PGNTags(&gameInfo);
12274     EditTagsPopUp(tags, NULL);
12275     free(tags);
12276 }
12277
12278 void
12279 AnalyzeModeEvent()
12280 {
12281     if (appData.noChessProgram || gameMode == AnalyzeMode)
12282       return;
12283
12284     if (gameMode != AnalyzeFile) {
12285         if (!appData.icsEngineAnalyze) {
12286                EditGameEvent();
12287                if (gameMode != EditGame) return;
12288         }
12289         ResurrectChessProgram();
12290         SendToProgram("analyze\n", &first);
12291         first.analyzing = TRUE;
12292         /*first.maybeThinking = TRUE;*/
12293         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12294         EngineOutputPopUp();
12295     }
12296     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12297     pausing = FALSE;
12298     ModeHighlight();
12299     SetGameInfo();
12300
12301     StartAnalysisClock();
12302     GetTimeMark(&lastNodeCountTime);
12303     lastNodeCount = 0;
12304 }
12305
12306 void
12307 AnalyzeFileEvent()
12308 {
12309     if (appData.noChessProgram || gameMode == AnalyzeFile)
12310       return;
12311
12312     if (gameMode != AnalyzeMode) {
12313         EditGameEvent();
12314         if (gameMode != EditGame) return;
12315         ResurrectChessProgram();
12316         SendToProgram("analyze\n", &first);
12317         first.analyzing = TRUE;
12318         /*first.maybeThinking = TRUE;*/
12319         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12320         EngineOutputPopUp();
12321     }
12322     gameMode = AnalyzeFile;
12323     pausing = FALSE;
12324     ModeHighlight();
12325     SetGameInfo();
12326
12327     StartAnalysisClock();
12328     GetTimeMark(&lastNodeCountTime);
12329     lastNodeCount = 0;
12330 }
12331
12332 void
12333 MachineWhiteEvent()
12334 {
12335     char buf[MSG_SIZ];
12336     char *bookHit = NULL;
12337
12338     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12339       return;
12340
12341
12342     if (gameMode == PlayFromGameFile ||
12343         gameMode == TwoMachinesPlay  ||
12344         gameMode == Training         ||
12345         gameMode == AnalyzeMode      ||
12346         gameMode == EndOfGame)
12347         EditGameEvent();
12348
12349     if (gameMode == EditPosition)
12350         EditPositionDone(TRUE);
12351
12352     if (!WhiteOnMove(currentMove)) {
12353         DisplayError(_("It is not White's turn"), 0);
12354         return;
12355     }
12356
12357     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12358       ExitAnalyzeMode();
12359
12360     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12361         gameMode == AnalyzeFile)
12362         TruncateGame();
12363
12364     ResurrectChessProgram();    /* in case it isn't running */
12365     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12366         gameMode = MachinePlaysWhite;
12367         ResetClocks();
12368     } else
12369     gameMode = MachinePlaysWhite;
12370     pausing = FALSE;
12371     ModeHighlight();
12372     SetGameInfo();
12373     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12374     DisplayTitle(buf);
12375     if (first.sendName) {
12376       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12377       SendToProgram(buf, &first);
12378     }
12379     if (first.sendTime) {
12380       if (first.useColors) {
12381         SendToProgram("black\n", &first); /*gnu kludge*/
12382       }
12383       SendTimeRemaining(&first, TRUE);
12384     }
12385     if (first.useColors) {
12386       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12387     }
12388     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12389     SetMachineThinkingEnables();
12390     first.maybeThinking = TRUE;
12391     StartClocks();
12392     firstMove = FALSE;
12393
12394     if (appData.autoFlipView && !flipView) {
12395       flipView = !flipView;
12396       DrawPosition(FALSE, NULL);
12397       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12398     }
12399
12400     if(bookHit) { // [HGM] book: simulate book reply
12401         static char bookMove[MSG_SIZ]; // a bit generous?
12402
12403         programStats.nodes = programStats.depth = programStats.time =
12404         programStats.score = programStats.got_only_move = 0;
12405         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12406
12407         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12408         strcat(bookMove, bookHit);
12409         HandleMachineMove(bookMove, &first);
12410     }
12411 }
12412
12413 void
12414 MachineBlackEvent()
12415 {
12416   char buf[MSG_SIZ];
12417   char *bookHit = NULL;
12418
12419     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12420         return;
12421
12422
12423     if (gameMode == PlayFromGameFile ||
12424         gameMode == TwoMachinesPlay  ||
12425         gameMode == Training         ||
12426         gameMode == AnalyzeMode      ||
12427         gameMode == EndOfGame)
12428         EditGameEvent();
12429
12430     if (gameMode == EditPosition)
12431         EditPositionDone(TRUE);
12432
12433     if (WhiteOnMove(currentMove)) {
12434         DisplayError(_("It is not Black's turn"), 0);
12435         return;
12436     }
12437
12438     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12439       ExitAnalyzeMode();
12440
12441     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12442         gameMode == AnalyzeFile)
12443         TruncateGame();
12444
12445     ResurrectChessProgram();    /* in case it isn't running */
12446     gameMode = MachinePlaysBlack;
12447     pausing = FALSE;
12448     ModeHighlight();
12449     SetGameInfo();
12450     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12451     DisplayTitle(buf);
12452     if (first.sendName) {
12453       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12454       SendToProgram(buf, &first);
12455     }
12456     if (first.sendTime) {
12457       if (first.useColors) {
12458         SendToProgram("white\n", &first); /*gnu kludge*/
12459       }
12460       SendTimeRemaining(&first, FALSE);
12461     }
12462     if (first.useColors) {
12463       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12464     }
12465     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12466     SetMachineThinkingEnables();
12467     first.maybeThinking = TRUE;
12468     StartClocks();
12469
12470     if (appData.autoFlipView && flipView) {
12471       flipView = !flipView;
12472       DrawPosition(FALSE, NULL);
12473       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12474     }
12475     if(bookHit) { // [HGM] book: simulate book reply
12476         static char bookMove[MSG_SIZ]; // a bit generous?
12477
12478         programStats.nodes = programStats.depth = programStats.time =
12479         programStats.score = programStats.got_only_move = 0;
12480         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12481
12482         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12483         strcat(bookMove, bookHit);
12484         HandleMachineMove(bookMove, &first);
12485     }
12486 }
12487
12488
12489 void
12490 DisplayTwoMachinesTitle()
12491 {
12492     char buf[MSG_SIZ];
12493     if (appData.matchGames > 0) {
12494         if (first.twoMachinesColor[0] == 'w') {
12495           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12496                    gameInfo.white, gameInfo.black,
12497                    first.matchWins, second.matchWins,
12498                    matchGame - 1 - (first.matchWins + second.matchWins));
12499         } else {
12500           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12501                    gameInfo.white, gameInfo.black,
12502                    second.matchWins, first.matchWins,
12503                    matchGame - 1 - (first.matchWins + second.matchWins));
12504         }
12505     } else {
12506       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12507     }
12508     DisplayTitle(buf);
12509 }
12510
12511 void
12512 SettingsMenuIfReady()
12513 {
12514   if (second.lastPing != second.lastPong) {
12515     DisplayMessage("", _("Waiting for second chess program"));
12516     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12517     return;
12518   }
12519   ThawUI();
12520   DisplayMessage("", "");
12521   SettingsPopUp(&second);
12522 }
12523
12524 int
12525 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12526 {
12527     char buf[MSG_SIZ];
12528     if (cps->pr == NULL) {
12529         StartChessProgram(cps);
12530         if (cps->protocolVersion == 1) {
12531           retry();
12532         } else {
12533           /* kludge: allow timeout for initial "feature" command */
12534           FreezeUI();
12535           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12536           DisplayMessage("", buf);
12537           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12538         }
12539         return 1;
12540     }
12541     return 0;
12542 }
12543
12544 void
12545 TwoMachinesEvent P((void))
12546 {
12547     int i;
12548     char buf[MSG_SIZ];
12549     ChessProgramState *onmove;
12550     char *bookHit = NULL;
12551     static int stalling = 0;
12552     TimeMark now;
12553     long wait;
12554
12555     if (appData.noChessProgram) return;
12556
12557     switch (gameMode) {
12558       case TwoMachinesPlay:
12559         return;
12560       case MachinePlaysWhite:
12561       case MachinePlaysBlack:
12562         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12563             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12564             return;
12565         }
12566         /* fall through */
12567       case BeginningOfGame:
12568       case PlayFromGameFile:
12569       case EndOfGame:
12570         EditGameEvent();
12571         if (gameMode != EditGame) return;
12572         break;
12573       case EditPosition:
12574         EditPositionDone(TRUE);
12575         break;
12576       case AnalyzeMode:
12577       case AnalyzeFile:
12578         ExitAnalyzeMode();
12579         break;
12580       case EditGame:
12581       default:
12582         break;
12583     }
12584
12585 //    forwardMostMove = currentMove;
12586     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12587
12588     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12589
12590     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12591     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12592       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12593       return;
12594     }
12595     if(!stalling) {
12596       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12597       SendToProgram("force\n", &second);
12598       stalling = 1;
12599       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12600       return;
12601     }
12602     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12603     if(appData.matchPause>10000 || appData.matchPause<10)
12604                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12605     wait = SubtractTimeMarks(&now, &pauseStart);
12606     if(wait < appData.matchPause) {
12607         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12608         return;
12609     }
12610     stalling = 0;
12611     DisplayMessage("", "");
12612     if (startedFromSetupPosition) {
12613         SendBoard(&second, backwardMostMove);
12614     if (appData.debugMode) {
12615         fprintf(debugFP, "Two Machines\n");
12616     }
12617     }
12618     for (i = backwardMostMove; i < forwardMostMove; i++) {
12619         SendMoveToProgram(i, &second);
12620     }
12621
12622     gameMode = TwoMachinesPlay;
12623     pausing = FALSE;
12624     ModeHighlight();
12625     SetGameInfo();
12626     DisplayTwoMachinesTitle();
12627     firstMove = TRUE;
12628     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12629         onmove = &first;
12630     } else {
12631         onmove = &second;
12632     }
12633     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12634     SendToProgram(first.computerString, &first);
12635     if (first.sendName) {
12636       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12637       SendToProgram(buf, &first);
12638     }
12639     SendToProgram(second.computerString, &second);
12640     if (second.sendName) {
12641       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12642       SendToProgram(buf, &second);
12643     }
12644
12645     ResetClocks();
12646     if (!first.sendTime || !second.sendTime) {
12647         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12648         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12649     }
12650     if (onmove->sendTime) {
12651       if (onmove->useColors) {
12652         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12653       }
12654       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12655     }
12656     if (onmove->useColors) {
12657       SendToProgram(onmove->twoMachinesColor, onmove);
12658     }
12659     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12660 //    SendToProgram("go\n", onmove);
12661     onmove->maybeThinking = TRUE;
12662     SetMachineThinkingEnables();
12663
12664     StartClocks();
12665
12666     if(bookHit) { // [HGM] book: simulate book reply
12667         static char bookMove[MSG_SIZ]; // a bit generous?
12668
12669         programStats.nodes = programStats.depth = programStats.time =
12670         programStats.score = programStats.got_only_move = 0;
12671         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12672
12673         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12674         strcat(bookMove, bookHit);
12675         savedMessage = bookMove; // args for deferred call
12676         savedState = onmove;
12677         ScheduleDelayedEvent(DeferredBookMove, 1);
12678     }
12679 }
12680
12681 void
12682 TrainingEvent()
12683 {
12684     if (gameMode == Training) {
12685       SetTrainingModeOff();
12686       gameMode = PlayFromGameFile;
12687       DisplayMessage("", _("Training mode off"));
12688     } else {
12689       gameMode = Training;
12690       animateTraining = appData.animate;
12691
12692       /* make sure we are not already at the end of the game */
12693       if (currentMove < forwardMostMove) {
12694         SetTrainingModeOn();
12695         DisplayMessage("", _("Training mode on"));
12696       } else {
12697         gameMode = PlayFromGameFile;
12698         DisplayError(_("Already at end of game"), 0);
12699       }
12700     }
12701     ModeHighlight();
12702 }
12703
12704 void
12705 IcsClientEvent()
12706 {
12707     if (!appData.icsActive) return;
12708     switch (gameMode) {
12709       case IcsPlayingWhite:
12710       case IcsPlayingBlack:
12711       case IcsObserving:
12712       case IcsIdle:
12713       case BeginningOfGame:
12714       case IcsExamining:
12715         return;
12716
12717       case EditGame:
12718         break;
12719
12720       case EditPosition:
12721         EditPositionDone(TRUE);
12722         break;
12723
12724       case AnalyzeMode:
12725       case AnalyzeFile:
12726         ExitAnalyzeMode();
12727         break;
12728
12729       default:
12730         EditGameEvent();
12731         break;
12732     }
12733
12734     gameMode = IcsIdle;
12735     ModeHighlight();
12736     return;
12737 }
12738
12739
12740 void
12741 EditGameEvent()
12742 {
12743     int i;
12744
12745     switch (gameMode) {
12746       case Training:
12747         SetTrainingModeOff();
12748         break;
12749       case MachinePlaysWhite:
12750       case MachinePlaysBlack:
12751       case BeginningOfGame:
12752         SendToProgram("force\n", &first);
12753         SetUserThinkingEnables();
12754         break;
12755       case PlayFromGameFile:
12756         (void) StopLoadGameTimer();
12757         if (gameFileFP != NULL) {
12758             gameFileFP = NULL;
12759         }
12760         break;
12761       case EditPosition:
12762         EditPositionDone(TRUE);
12763         break;
12764       case AnalyzeMode:
12765       case AnalyzeFile:
12766         ExitAnalyzeMode();
12767         SendToProgram("force\n", &first);
12768         break;
12769       case TwoMachinesPlay:
12770         GameEnds(EndOfFile, NULL, GE_PLAYER);
12771         ResurrectChessProgram();
12772         SetUserThinkingEnables();
12773         break;
12774       case EndOfGame:
12775         ResurrectChessProgram();
12776         break;
12777       case IcsPlayingBlack:
12778       case IcsPlayingWhite:
12779         DisplayError(_("Warning: You are still playing a game"), 0);
12780         break;
12781       case IcsObserving:
12782         DisplayError(_("Warning: You are still observing a game"), 0);
12783         break;
12784       case IcsExamining:
12785         DisplayError(_("Warning: You are still examining a game"), 0);
12786         break;
12787       case IcsIdle:
12788         break;
12789       case EditGame:
12790       default:
12791         return;
12792     }
12793
12794     pausing = FALSE;
12795     StopClocks();
12796     first.offeredDraw = second.offeredDraw = 0;
12797
12798     if (gameMode == PlayFromGameFile) {
12799         whiteTimeRemaining = timeRemaining[0][currentMove];
12800         blackTimeRemaining = timeRemaining[1][currentMove];
12801         DisplayTitle("");
12802     }
12803
12804     if (gameMode == MachinePlaysWhite ||
12805         gameMode == MachinePlaysBlack ||
12806         gameMode == TwoMachinesPlay ||
12807         gameMode == EndOfGame) {
12808         i = forwardMostMove;
12809         while (i > currentMove) {
12810             SendToProgram("undo\n", &first);
12811             i--;
12812         }
12813         whiteTimeRemaining = timeRemaining[0][currentMove];
12814         blackTimeRemaining = timeRemaining[1][currentMove];
12815         DisplayBothClocks();
12816         if (whiteFlag || blackFlag) {
12817             whiteFlag = blackFlag = 0;
12818         }
12819         DisplayTitle("");
12820     }
12821
12822     gameMode = EditGame;
12823     ModeHighlight();
12824     SetGameInfo();
12825 }
12826
12827
12828 void
12829 EditPositionEvent()
12830 {
12831     if (gameMode == EditPosition) {
12832         EditGameEvent();
12833         return;
12834     }
12835
12836     EditGameEvent();
12837     if (gameMode != EditGame) return;
12838
12839     gameMode = EditPosition;
12840     ModeHighlight();
12841     SetGameInfo();
12842     if (currentMove > 0)
12843       CopyBoard(boards[0], boards[currentMove]);
12844
12845     blackPlaysFirst = !WhiteOnMove(currentMove);
12846     ResetClocks();
12847     currentMove = forwardMostMove = backwardMostMove = 0;
12848     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12849     DisplayMove(-1);
12850 }
12851
12852 void
12853 ExitAnalyzeMode()
12854 {
12855     /* [DM] icsEngineAnalyze - possible call from other functions */
12856     if (appData.icsEngineAnalyze) {
12857         appData.icsEngineAnalyze = FALSE;
12858
12859         DisplayMessage("",_("Close ICS engine analyze..."));
12860     }
12861     if (first.analysisSupport && first.analyzing) {
12862       SendToProgram("exit\n", &first);
12863       first.analyzing = FALSE;
12864     }
12865     thinkOutput[0] = NULLCHAR;
12866 }
12867
12868 void
12869 EditPositionDone(Boolean fakeRights)
12870 {
12871     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12872
12873     startedFromSetupPosition = TRUE;
12874     InitChessProgram(&first, FALSE);
12875     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12876       boards[0][EP_STATUS] = EP_NONE;
12877       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12878     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12879         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12880         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12881       } else boards[0][CASTLING][2] = NoRights;
12882     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12883         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12884         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12885       } else boards[0][CASTLING][5] = NoRights;
12886     }
12887     SendToProgram("force\n", &first);
12888     if (blackPlaysFirst) {
12889         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12890         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12891         currentMove = forwardMostMove = backwardMostMove = 1;
12892         CopyBoard(boards[1], boards[0]);
12893     } else {
12894         currentMove = forwardMostMove = backwardMostMove = 0;
12895     }
12896     SendBoard(&first, forwardMostMove);
12897     if (appData.debugMode) {
12898         fprintf(debugFP, "EditPosDone\n");
12899     }
12900     DisplayTitle("");
12901     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12902     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12903     gameMode = EditGame;
12904     ModeHighlight();
12905     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12906     ClearHighlights(); /* [AS] */
12907 }
12908
12909 /* Pause for `ms' milliseconds */
12910 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12911 void
12912 TimeDelay(ms)
12913      long ms;
12914 {
12915     TimeMark m1, m2;
12916
12917     GetTimeMark(&m1);
12918     do {
12919         GetTimeMark(&m2);
12920     } while (SubtractTimeMarks(&m2, &m1) < ms);
12921 }
12922
12923 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12924 void
12925 SendMultiLineToICS(buf)
12926      char *buf;
12927 {
12928     char temp[MSG_SIZ+1], *p;
12929     int len;
12930
12931     len = strlen(buf);
12932     if (len > MSG_SIZ)
12933       len = MSG_SIZ;
12934
12935     strncpy(temp, buf, len);
12936     temp[len] = 0;
12937
12938     p = temp;
12939     while (*p) {
12940         if (*p == '\n' || *p == '\r')
12941           *p = ' ';
12942         ++p;
12943     }
12944
12945     strcat(temp, "\n");
12946     SendToICS(temp);
12947     SendToPlayer(temp, strlen(temp));
12948 }
12949
12950 void
12951 SetWhiteToPlayEvent()
12952 {
12953     if (gameMode == EditPosition) {
12954         blackPlaysFirst = FALSE;
12955         DisplayBothClocks();    /* works because currentMove is 0 */
12956     } else if (gameMode == IcsExamining) {
12957         SendToICS(ics_prefix);
12958         SendToICS("tomove white\n");
12959     }
12960 }
12961
12962 void
12963 SetBlackToPlayEvent()
12964 {
12965     if (gameMode == EditPosition) {
12966         blackPlaysFirst = TRUE;
12967         currentMove = 1;        /* kludge */
12968         DisplayBothClocks();
12969         currentMove = 0;
12970     } else if (gameMode == IcsExamining) {
12971         SendToICS(ics_prefix);
12972         SendToICS("tomove black\n");
12973     }
12974 }
12975
12976 void
12977 EditPositionMenuEvent(selection, x, y)
12978      ChessSquare selection;
12979      int x, y;
12980 {
12981     char buf[MSG_SIZ];
12982     ChessSquare piece = boards[0][y][x];
12983
12984     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12985
12986     switch (selection) {
12987       case ClearBoard:
12988         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12989             SendToICS(ics_prefix);
12990             SendToICS("bsetup clear\n");
12991         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12992             SendToICS(ics_prefix);
12993             SendToICS("clearboard\n");
12994         } else {
12995             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12996                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12997                 for (y = 0; y < BOARD_HEIGHT; y++) {
12998                     if (gameMode == IcsExamining) {
12999                         if (boards[currentMove][y][x] != EmptySquare) {
13000                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13001                                     AAA + x, ONE + y);
13002                             SendToICS(buf);
13003                         }
13004                     } else {
13005                         boards[0][y][x] = p;
13006                     }
13007                 }
13008             }
13009         }
13010         if (gameMode == EditPosition) {
13011             DrawPosition(FALSE, boards[0]);
13012         }
13013         break;
13014
13015       case WhitePlay:
13016         SetWhiteToPlayEvent();
13017         break;
13018
13019       case BlackPlay:
13020         SetBlackToPlayEvent();
13021         break;
13022
13023       case EmptySquare:
13024         if (gameMode == IcsExamining) {
13025             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13026             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13027             SendToICS(buf);
13028         } else {
13029             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13030                 if(x == BOARD_LEFT-2) {
13031                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13032                     boards[0][y][1] = 0;
13033                 } else
13034                 if(x == BOARD_RGHT+1) {
13035                     if(y >= gameInfo.holdingsSize) break;
13036                     boards[0][y][BOARD_WIDTH-2] = 0;
13037                 } else break;
13038             }
13039             boards[0][y][x] = EmptySquare;
13040             DrawPosition(FALSE, boards[0]);
13041         }
13042         break;
13043
13044       case PromotePiece:
13045         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13046            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13047             selection = (ChessSquare) (PROMOTED piece);
13048         } else if(piece == EmptySquare) selection = WhiteSilver;
13049         else selection = (ChessSquare)((int)piece - 1);
13050         goto defaultlabel;
13051
13052       case DemotePiece:
13053         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13054            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13055             selection = (ChessSquare) (DEMOTED piece);
13056         } else if(piece == EmptySquare) selection = BlackSilver;
13057         else selection = (ChessSquare)((int)piece + 1);
13058         goto defaultlabel;
13059
13060       case WhiteQueen:
13061       case BlackQueen:
13062         if(gameInfo.variant == VariantShatranj ||
13063            gameInfo.variant == VariantXiangqi  ||
13064            gameInfo.variant == VariantCourier  ||
13065            gameInfo.variant == VariantMakruk     )
13066             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13067         goto defaultlabel;
13068
13069       case WhiteKing:
13070       case BlackKing:
13071         if(gameInfo.variant == VariantXiangqi)
13072             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13073         if(gameInfo.variant == VariantKnightmate)
13074             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13075       default:
13076         defaultlabel:
13077         if (gameMode == IcsExamining) {
13078             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13079             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13080                      PieceToChar(selection), AAA + x, ONE + y);
13081             SendToICS(buf);
13082         } else {
13083             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13084                 int n;
13085                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13086                     n = PieceToNumber(selection - BlackPawn);
13087                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13088                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13089                     boards[0][BOARD_HEIGHT-1-n][1]++;
13090                 } else
13091                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13092                     n = PieceToNumber(selection);
13093                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13094                     boards[0][n][BOARD_WIDTH-1] = selection;
13095                     boards[0][n][BOARD_WIDTH-2]++;
13096                 }
13097             } else
13098             boards[0][y][x] = selection;
13099             DrawPosition(TRUE, boards[0]);
13100         }
13101         break;
13102     }
13103 }
13104
13105
13106 void
13107 DropMenuEvent(selection, x, y)
13108      ChessSquare selection;
13109      int x, y;
13110 {
13111     ChessMove moveType;
13112
13113     switch (gameMode) {
13114       case IcsPlayingWhite:
13115       case MachinePlaysBlack:
13116         if (!WhiteOnMove(currentMove)) {
13117             DisplayMoveError(_("It is Black's turn"));
13118             return;
13119         }
13120         moveType = WhiteDrop;
13121         break;
13122       case IcsPlayingBlack:
13123       case MachinePlaysWhite:
13124         if (WhiteOnMove(currentMove)) {
13125             DisplayMoveError(_("It is White's turn"));
13126             return;
13127         }
13128         moveType = BlackDrop;
13129         break;
13130       case EditGame:
13131         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13132         break;
13133       default:
13134         return;
13135     }
13136
13137     if (moveType == BlackDrop && selection < BlackPawn) {
13138       selection = (ChessSquare) ((int) selection
13139                                  + (int) BlackPawn - (int) WhitePawn);
13140     }
13141     if (boards[currentMove][y][x] != EmptySquare) {
13142         DisplayMoveError(_("That square is occupied"));
13143         return;
13144     }
13145
13146     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13147 }
13148
13149 void
13150 AcceptEvent()
13151 {
13152     /* Accept a pending offer of any kind from opponent */
13153
13154     if (appData.icsActive) {
13155         SendToICS(ics_prefix);
13156         SendToICS("accept\n");
13157     } else if (cmailMsgLoaded) {
13158         if (currentMove == cmailOldMove &&
13159             commentList[cmailOldMove] != NULL &&
13160             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13161                    "Black offers a draw" : "White offers a draw")) {
13162             TruncateGame();
13163             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13164             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13165         } else {
13166             DisplayError(_("There is no pending offer on this move"), 0);
13167             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13168         }
13169     } else {
13170         /* Not used for offers from chess program */
13171     }
13172 }
13173
13174 void
13175 DeclineEvent()
13176 {
13177     /* Decline a pending offer of any kind from opponent */
13178
13179     if (appData.icsActive) {
13180         SendToICS(ics_prefix);
13181         SendToICS("decline\n");
13182     } else if (cmailMsgLoaded) {
13183         if (currentMove == cmailOldMove &&
13184             commentList[cmailOldMove] != NULL &&
13185             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13186                    "Black offers a draw" : "White offers a draw")) {
13187 #ifdef NOTDEF
13188             AppendComment(cmailOldMove, "Draw declined", TRUE);
13189             DisplayComment(cmailOldMove - 1, "Draw declined");
13190 #endif /*NOTDEF*/
13191         } else {
13192             DisplayError(_("There is no pending offer on this move"), 0);
13193         }
13194     } else {
13195         /* Not used for offers from chess program */
13196     }
13197 }
13198
13199 void
13200 RematchEvent()
13201 {
13202     /* Issue ICS rematch command */
13203     if (appData.icsActive) {
13204         SendToICS(ics_prefix);
13205         SendToICS("rematch\n");
13206     }
13207 }
13208
13209 void
13210 CallFlagEvent()
13211 {
13212     /* Call your opponent's flag (claim a win on time) */
13213     if (appData.icsActive) {
13214         SendToICS(ics_prefix);
13215         SendToICS("flag\n");
13216     } else {
13217         switch (gameMode) {
13218           default:
13219             return;
13220           case MachinePlaysWhite:
13221             if (whiteFlag) {
13222                 if (blackFlag)
13223                   GameEnds(GameIsDrawn, "Both players ran out of time",
13224                            GE_PLAYER);
13225                 else
13226                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13227             } else {
13228                 DisplayError(_("Your opponent is not out of time"), 0);
13229             }
13230             break;
13231           case MachinePlaysBlack:
13232             if (blackFlag) {
13233                 if (whiteFlag)
13234                   GameEnds(GameIsDrawn, "Both players ran out of time",
13235                            GE_PLAYER);
13236                 else
13237                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13238             } else {
13239                 DisplayError(_("Your opponent is not out of time"), 0);
13240             }
13241             break;
13242         }
13243     }
13244 }
13245
13246 void
13247 ClockClick(int which)
13248 {       // [HGM] code moved to back-end from winboard.c
13249         if(which) { // black clock
13250           if (gameMode == EditPosition || gameMode == IcsExamining) {
13251             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13252             SetBlackToPlayEvent();
13253           } else if (gameMode == EditGame || shiftKey) {
13254             AdjustClock(which, -1);
13255           } else if (gameMode == IcsPlayingWhite ||
13256                      gameMode == MachinePlaysBlack) {
13257             CallFlagEvent();
13258           }
13259         } else { // white clock
13260           if (gameMode == EditPosition || gameMode == IcsExamining) {
13261             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13262             SetWhiteToPlayEvent();
13263           } else if (gameMode == EditGame || shiftKey) {
13264             AdjustClock(which, -1);
13265           } else if (gameMode == IcsPlayingBlack ||
13266                    gameMode == MachinePlaysWhite) {
13267             CallFlagEvent();
13268           }
13269         }
13270 }
13271
13272 void
13273 DrawEvent()
13274 {
13275     /* Offer draw or accept pending draw offer from opponent */
13276
13277     if (appData.icsActive) {
13278         /* Note: tournament rules require draw offers to be
13279            made after you make your move but before you punch
13280            your clock.  Currently ICS doesn't let you do that;
13281            instead, you immediately punch your clock after making
13282            a move, but you can offer a draw at any time. */
13283
13284         SendToICS(ics_prefix);
13285         SendToICS("draw\n");
13286         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13287     } else if (cmailMsgLoaded) {
13288         if (currentMove == cmailOldMove &&
13289             commentList[cmailOldMove] != NULL &&
13290             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13291                    "Black offers a draw" : "White offers a draw")) {
13292             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13293             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13294         } else if (currentMove == cmailOldMove + 1) {
13295             char *offer = WhiteOnMove(cmailOldMove) ?
13296               "White offers a draw" : "Black offers a draw";
13297             AppendComment(currentMove, offer, TRUE);
13298             DisplayComment(currentMove - 1, offer);
13299             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13300         } else {
13301             DisplayError(_("You must make your move before offering a draw"), 0);
13302             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13303         }
13304     } else if (first.offeredDraw) {
13305         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13306     } else {
13307         if (first.sendDrawOffers) {
13308             SendToProgram("draw\n", &first);
13309             userOfferedDraw = TRUE;
13310         }
13311     }
13312 }
13313
13314 void
13315 AdjournEvent()
13316 {
13317     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13318
13319     if (appData.icsActive) {
13320         SendToICS(ics_prefix);
13321         SendToICS("adjourn\n");
13322     } else {
13323         /* Currently GNU Chess doesn't offer or accept Adjourns */
13324     }
13325 }
13326
13327
13328 void
13329 AbortEvent()
13330 {
13331     /* Offer Abort or accept pending Abort offer from opponent */
13332
13333     if (appData.icsActive) {
13334         SendToICS(ics_prefix);
13335         SendToICS("abort\n");
13336     } else {
13337         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13338     }
13339 }
13340
13341 void
13342 ResignEvent()
13343 {
13344     /* Resign.  You can do this even if it's not your turn. */
13345
13346     if (appData.icsActive) {
13347         SendToICS(ics_prefix);
13348         SendToICS("resign\n");
13349     } else {
13350         switch (gameMode) {
13351           case MachinePlaysWhite:
13352             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13353             break;
13354           case MachinePlaysBlack:
13355             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13356             break;
13357           case EditGame:
13358             if (cmailMsgLoaded) {
13359                 TruncateGame();
13360                 if (WhiteOnMove(cmailOldMove)) {
13361                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13362                 } else {
13363                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13364                 }
13365                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13366             }
13367             break;
13368           default:
13369             break;
13370         }
13371     }
13372 }
13373
13374
13375 void
13376 StopObservingEvent()
13377 {
13378     /* Stop observing current games */
13379     SendToICS(ics_prefix);
13380     SendToICS("unobserve\n");
13381 }
13382
13383 void
13384 StopExaminingEvent()
13385 {
13386     /* Stop observing current game */
13387     SendToICS(ics_prefix);
13388     SendToICS("unexamine\n");
13389 }
13390
13391 void
13392 ForwardInner(target)
13393      int target;
13394 {
13395     int limit;
13396
13397     if (appData.debugMode)
13398         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13399                 target, currentMove, forwardMostMove);
13400
13401     if (gameMode == EditPosition)
13402       return;
13403
13404     if (gameMode == PlayFromGameFile && !pausing)
13405       PauseEvent();
13406
13407     if (gameMode == IcsExamining && pausing)
13408       limit = pauseExamForwardMostMove;
13409     else
13410       limit = forwardMostMove;
13411
13412     if (target > limit) target = limit;
13413
13414     if (target > 0 && moveList[target - 1][0]) {
13415         int fromX, fromY, toX, toY;
13416         toX = moveList[target - 1][2] - AAA;
13417         toY = moveList[target - 1][3] - ONE;
13418         if (moveList[target - 1][1] == '@') {
13419             if (appData.highlightLastMove) {
13420                 SetHighlights(-1, -1, toX, toY);
13421             }
13422         } else {
13423             fromX = moveList[target - 1][0] - AAA;
13424             fromY = moveList[target - 1][1] - ONE;
13425             if (target == currentMove + 1) {
13426                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13427             }
13428             if (appData.highlightLastMove) {
13429                 SetHighlights(fromX, fromY, toX, toY);
13430             }
13431         }
13432     }
13433     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13434         gameMode == Training || gameMode == PlayFromGameFile ||
13435         gameMode == AnalyzeFile) {
13436         while (currentMove < target) {
13437             SendMoveToProgram(currentMove++, &first);
13438         }
13439     } else {
13440         currentMove = target;
13441     }
13442
13443     if (gameMode == EditGame || gameMode == EndOfGame) {
13444         whiteTimeRemaining = timeRemaining[0][currentMove];
13445         blackTimeRemaining = timeRemaining[1][currentMove];
13446     }
13447     DisplayBothClocks();
13448     DisplayMove(currentMove - 1);
13449     DrawPosition(FALSE, boards[currentMove]);
13450     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13451     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13452         DisplayComment(currentMove - 1, commentList[currentMove]);
13453     }
13454 }
13455
13456
13457 void
13458 ForwardEvent()
13459 {
13460     if (gameMode == IcsExamining && !pausing) {
13461         SendToICS(ics_prefix);
13462         SendToICS("forward\n");
13463     } else {
13464         ForwardInner(currentMove + 1);
13465     }
13466 }
13467
13468 void
13469 ToEndEvent()
13470 {
13471     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13472         /* to optimze, we temporarily turn off analysis mode while we feed
13473          * the remaining moves to the engine. Otherwise we get analysis output
13474          * after each move.
13475          */
13476         if (first.analysisSupport) {
13477           SendToProgram("exit\nforce\n", &first);
13478           first.analyzing = FALSE;
13479         }
13480     }
13481
13482     if (gameMode == IcsExamining && !pausing) {
13483         SendToICS(ics_prefix);
13484         SendToICS("forward 999999\n");
13485     } else {
13486         ForwardInner(forwardMostMove);
13487     }
13488
13489     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13490         /* we have fed all the moves, so reactivate analysis mode */
13491         SendToProgram("analyze\n", &first);
13492         first.analyzing = TRUE;
13493         /*first.maybeThinking = TRUE;*/
13494         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13495     }
13496 }
13497
13498 void
13499 BackwardInner(target)
13500      int target;
13501 {
13502     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13503
13504     if (appData.debugMode)
13505         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13506                 target, currentMove, forwardMostMove);
13507
13508     if (gameMode == EditPosition) return;
13509     if (currentMove <= backwardMostMove) {
13510         ClearHighlights();
13511         DrawPosition(full_redraw, boards[currentMove]);
13512         return;
13513     }
13514     if (gameMode == PlayFromGameFile && !pausing)
13515       PauseEvent();
13516
13517     if (moveList[target][0]) {
13518         int fromX, fromY, toX, toY;
13519         toX = moveList[target][2] - AAA;
13520         toY = moveList[target][3] - ONE;
13521         if (moveList[target][1] == '@') {
13522             if (appData.highlightLastMove) {
13523                 SetHighlights(-1, -1, toX, toY);
13524             }
13525         } else {
13526             fromX = moveList[target][0] - AAA;
13527             fromY = moveList[target][1] - ONE;
13528             if (target == currentMove - 1) {
13529                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13530             }
13531             if (appData.highlightLastMove) {
13532                 SetHighlights(fromX, fromY, toX, toY);
13533             }
13534         }
13535     }
13536     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13537         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13538         while (currentMove > target) {
13539             SendToProgram("undo\n", &first);
13540             currentMove--;
13541         }
13542     } else {
13543         currentMove = target;
13544     }
13545
13546     if (gameMode == EditGame || gameMode == EndOfGame) {
13547         whiteTimeRemaining = timeRemaining[0][currentMove];
13548         blackTimeRemaining = timeRemaining[1][currentMove];
13549     }
13550     DisplayBothClocks();
13551     DisplayMove(currentMove - 1);
13552     DrawPosition(full_redraw, boards[currentMove]);
13553     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13554     // [HGM] PV info: routine tests if comment empty
13555     DisplayComment(currentMove - 1, commentList[currentMove]);
13556 }
13557
13558 void
13559 BackwardEvent()
13560 {
13561     if (gameMode == IcsExamining && !pausing) {
13562         SendToICS(ics_prefix);
13563         SendToICS("backward\n");
13564     } else {
13565         BackwardInner(currentMove - 1);
13566     }
13567 }
13568
13569 void
13570 ToStartEvent()
13571 {
13572     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13573         /* to optimize, we temporarily turn off analysis mode while we undo
13574          * all the moves. Otherwise we get analysis output after each undo.
13575          */
13576         if (first.analysisSupport) {
13577           SendToProgram("exit\nforce\n", &first);
13578           first.analyzing = FALSE;
13579         }
13580     }
13581
13582     if (gameMode == IcsExamining && !pausing) {
13583         SendToICS(ics_prefix);
13584         SendToICS("backward 999999\n");
13585     } else {
13586         BackwardInner(backwardMostMove);
13587     }
13588
13589     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13590         /* we have fed all the moves, so reactivate analysis mode */
13591         SendToProgram("analyze\n", &first);
13592         first.analyzing = TRUE;
13593         /*first.maybeThinking = TRUE;*/
13594         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13595     }
13596 }
13597
13598 void
13599 ToNrEvent(int to)
13600 {
13601   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13602   if (to >= forwardMostMove) to = forwardMostMove;
13603   if (to <= backwardMostMove) to = backwardMostMove;
13604   if (to < currentMove) {
13605     BackwardInner(to);
13606   } else {
13607     ForwardInner(to);
13608   }
13609 }
13610
13611 void
13612 RevertEvent(Boolean annotate)
13613 {
13614     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13615         return;
13616     }
13617     if (gameMode != IcsExamining) {
13618         DisplayError(_("You are not examining a game"), 0);
13619         return;
13620     }
13621     if (pausing) {
13622         DisplayError(_("You can't revert while pausing"), 0);
13623         return;
13624     }
13625     SendToICS(ics_prefix);
13626     SendToICS("revert\n");
13627 }
13628
13629 void
13630 RetractMoveEvent()
13631 {
13632     switch (gameMode) {
13633       case MachinePlaysWhite:
13634       case MachinePlaysBlack:
13635         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13636             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13637             return;
13638         }
13639         if (forwardMostMove < 2) return;
13640         currentMove = forwardMostMove = forwardMostMove - 2;
13641         whiteTimeRemaining = timeRemaining[0][currentMove];
13642         blackTimeRemaining = timeRemaining[1][currentMove];
13643         DisplayBothClocks();
13644         DisplayMove(currentMove - 1);
13645         ClearHighlights();/*!! could figure this out*/
13646         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13647         SendToProgram("remove\n", &first);
13648         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13649         break;
13650
13651       case BeginningOfGame:
13652       default:
13653         break;
13654
13655       case IcsPlayingWhite:
13656       case IcsPlayingBlack:
13657         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13658             SendToICS(ics_prefix);
13659             SendToICS("takeback 2\n");
13660         } else {
13661             SendToICS(ics_prefix);
13662             SendToICS("takeback 1\n");
13663         }
13664         break;
13665     }
13666 }
13667
13668 void
13669 MoveNowEvent()
13670 {
13671     ChessProgramState *cps;
13672
13673     switch (gameMode) {
13674       case MachinePlaysWhite:
13675         if (!WhiteOnMove(forwardMostMove)) {
13676             DisplayError(_("It is your turn"), 0);
13677             return;
13678         }
13679         cps = &first;
13680         break;
13681       case MachinePlaysBlack:
13682         if (WhiteOnMove(forwardMostMove)) {
13683             DisplayError(_("It is your turn"), 0);
13684             return;
13685         }
13686         cps = &first;
13687         break;
13688       case TwoMachinesPlay:
13689         if (WhiteOnMove(forwardMostMove) ==
13690             (first.twoMachinesColor[0] == 'w')) {
13691             cps = &first;
13692         } else {
13693             cps = &second;
13694         }
13695         break;
13696       case BeginningOfGame:
13697       default:
13698         return;
13699     }
13700     SendToProgram("?\n", cps);
13701 }
13702
13703 void
13704 TruncateGameEvent()
13705 {
13706     EditGameEvent();
13707     if (gameMode != EditGame) return;
13708     TruncateGame();
13709 }
13710
13711 void
13712 TruncateGame()
13713 {
13714     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13715     if (forwardMostMove > currentMove) {
13716         if (gameInfo.resultDetails != NULL) {
13717             free(gameInfo.resultDetails);
13718             gameInfo.resultDetails = NULL;
13719             gameInfo.result = GameUnfinished;
13720         }
13721         forwardMostMove = currentMove;
13722         HistorySet(parseList, backwardMostMove, forwardMostMove,
13723                    currentMove-1);
13724     }
13725 }
13726
13727 void
13728 HintEvent()
13729 {
13730     if (appData.noChessProgram) return;
13731     switch (gameMode) {
13732       case MachinePlaysWhite:
13733         if (WhiteOnMove(forwardMostMove)) {
13734             DisplayError(_("Wait until your turn"), 0);
13735             return;
13736         }
13737         break;
13738       case BeginningOfGame:
13739       case MachinePlaysBlack:
13740         if (!WhiteOnMove(forwardMostMove)) {
13741             DisplayError(_("Wait until your turn"), 0);
13742             return;
13743         }
13744         break;
13745       default:
13746         DisplayError(_("No hint available"), 0);
13747         return;
13748     }
13749     SendToProgram("hint\n", &first);
13750     hintRequested = TRUE;
13751 }
13752
13753 void
13754 BookEvent()
13755 {
13756     if (appData.noChessProgram) return;
13757     switch (gameMode) {
13758       case MachinePlaysWhite:
13759         if (WhiteOnMove(forwardMostMove)) {
13760             DisplayError(_("Wait until your turn"), 0);
13761             return;
13762         }
13763         break;
13764       case BeginningOfGame:
13765       case MachinePlaysBlack:
13766         if (!WhiteOnMove(forwardMostMove)) {
13767             DisplayError(_("Wait until your turn"), 0);
13768             return;
13769         }
13770         break;
13771       case EditPosition:
13772         EditPositionDone(TRUE);
13773         break;
13774       case TwoMachinesPlay:
13775         return;
13776       default:
13777         break;
13778     }
13779     SendToProgram("bk\n", &first);
13780     bookOutput[0] = NULLCHAR;
13781     bookRequested = TRUE;
13782 }
13783
13784 void
13785 AboutGameEvent()
13786 {
13787     char *tags = PGNTags(&gameInfo);
13788     TagsPopUp(tags, CmailMsg());
13789     free(tags);
13790 }
13791
13792 /* end button procedures */
13793
13794 void
13795 PrintPosition(fp, move)
13796      FILE *fp;
13797      int move;
13798 {
13799     int i, j;
13800
13801     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13802         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13803             char c = PieceToChar(boards[move][i][j]);
13804             fputc(c == 'x' ? '.' : c, fp);
13805             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13806         }
13807     }
13808     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13809       fprintf(fp, "white to play\n");
13810     else
13811       fprintf(fp, "black to play\n");
13812 }
13813
13814 void
13815 PrintOpponents(fp)
13816      FILE *fp;
13817 {
13818     if (gameInfo.white != NULL) {
13819         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13820     } else {
13821         fprintf(fp, "\n");
13822     }
13823 }
13824
13825 /* Find last component of program's own name, using some heuristics */
13826 void
13827 TidyProgramName(prog, host, buf)
13828      char *prog, *host, buf[MSG_SIZ];
13829 {
13830     char *p, *q;
13831     int local = (strcmp(host, "localhost") == 0);
13832     while (!local && (p = strchr(prog, ';')) != NULL) {
13833         p++;
13834         while (*p == ' ') p++;
13835         prog = p;
13836     }
13837     if (*prog == '"' || *prog == '\'') {
13838         q = strchr(prog + 1, *prog);
13839     } else {
13840         q = strchr(prog, ' ');
13841     }
13842     if (q == NULL) q = prog + strlen(prog);
13843     p = q;
13844     while (p >= prog && *p != '/' && *p != '\\') p--;
13845     p++;
13846     if(p == prog && *p == '"') p++;
13847     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13848     memcpy(buf, p, q - p);
13849     buf[q - p] = NULLCHAR;
13850     if (!local) {
13851         strcat(buf, "@");
13852         strcat(buf, host);
13853     }
13854 }
13855
13856 char *
13857 TimeControlTagValue()
13858 {
13859     char buf[MSG_SIZ];
13860     if (!appData.clockMode) {
13861       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13862     } else if (movesPerSession > 0) {
13863       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13864     } else if (timeIncrement == 0) {
13865       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13866     } else {
13867       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13868     }
13869     return StrSave(buf);
13870 }
13871
13872 void
13873 SetGameInfo()
13874 {
13875     /* This routine is used only for certain modes */
13876     VariantClass v = gameInfo.variant;
13877     ChessMove r = GameUnfinished;
13878     char *p = NULL;
13879
13880     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13881         r = gameInfo.result;
13882         p = gameInfo.resultDetails;
13883         gameInfo.resultDetails = NULL;
13884     }
13885     ClearGameInfo(&gameInfo);
13886     gameInfo.variant = v;
13887
13888     switch (gameMode) {
13889       case MachinePlaysWhite:
13890         gameInfo.event = StrSave( appData.pgnEventHeader );
13891         gameInfo.site = StrSave(HostName());
13892         gameInfo.date = PGNDate();
13893         gameInfo.round = StrSave("-");
13894         gameInfo.white = StrSave(first.tidy);
13895         gameInfo.black = StrSave(UserName());
13896         gameInfo.timeControl = TimeControlTagValue();
13897         break;
13898
13899       case MachinePlaysBlack:
13900         gameInfo.event = StrSave( appData.pgnEventHeader );
13901         gameInfo.site = StrSave(HostName());
13902         gameInfo.date = PGNDate();
13903         gameInfo.round = StrSave("-");
13904         gameInfo.white = StrSave(UserName());
13905         gameInfo.black = StrSave(first.tidy);
13906         gameInfo.timeControl = TimeControlTagValue();
13907         break;
13908
13909       case TwoMachinesPlay:
13910         gameInfo.event = StrSave( appData.pgnEventHeader );
13911         gameInfo.site = StrSave(HostName());
13912         gameInfo.date = PGNDate();
13913         if (roundNr > 0) {
13914             char buf[MSG_SIZ];
13915             snprintf(buf, MSG_SIZ, "%d", roundNr);
13916             gameInfo.round = StrSave(buf);
13917         } else {
13918             gameInfo.round = StrSave("-");
13919         }
13920         if (first.twoMachinesColor[0] == 'w') {
13921             gameInfo.white = StrSave(first.tidy);
13922             gameInfo.black = StrSave(second.tidy);
13923         } else {
13924             gameInfo.white = StrSave(second.tidy);
13925             gameInfo.black = StrSave(first.tidy);
13926         }
13927         gameInfo.timeControl = TimeControlTagValue();
13928         break;
13929
13930       case EditGame:
13931         gameInfo.event = StrSave("Edited game");
13932         gameInfo.site = StrSave(HostName());
13933         gameInfo.date = PGNDate();
13934         gameInfo.round = StrSave("-");
13935         gameInfo.white = StrSave("-");
13936         gameInfo.black = StrSave("-");
13937         gameInfo.result = r;
13938         gameInfo.resultDetails = p;
13939         break;
13940
13941       case EditPosition:
13942         gameInfo.event = StrSave("Edited position");
13943         gameInfo.site = StrSave(HostName());
13944         gameInfo.date = PGNDate();
13945         gameInfo.round = StrSave("-");
13946         gameInfo.white = StrSave("-");
13947         gameInfo.black = StrSave("-");
13948         break;
13949
13950       case IcsPlayingWhite:
13951       case IcsPlayingBlack:
13952       case IcsObserving:
13953       case IcsExamining:
13954         break;
13955
13956       case PlayFromGameFile:
13957         gameInfo.event = StrSave("Game from non-PGN file");
13958         gameInfo.site = StrSave(HostName());
13959         gameInfo.date = PGNDate();
13960         gameInfo.round = StrSave("-");
13961         gameInfo.white = StrSave("?");
13962         gameInfo.black = StrSave("?");
13963         break;
13964
13965       default:
13966         break;
13967     }
13968 }
13969
13970 void
13971 ReplaceComment(index, text)
13972      int index;
13973      char *text;
13974 {
13975     int len;
13976     char *p;
13977     float score;
13978
13979     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13980        pvInfoList[index-1].depth == len &&
13981        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13982        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13983     while (*text == '\n') text++;
13984     len = strlen(text);
13985     while (len > 0 && text[len - 1] == '\n') len--;
13986
13987     if (commentList[index] != NULL)
13988       free(commentList[index]);
13989
13990     if (len == 0) {
13991         commentList[index] = NULL;
13992         return;
13993     }
13994   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13995       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13996       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13997     commentList[index] = (char *) malloc(len + 2);
13998     strncpy(commentList[index], text, len);
13999     commentList[index][len] = '\n';
14000     commentList[index][len + 1] = NULLCHAR;
14001   } else {
14002     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14003     char *p;
14004     commentList[index] = (char *) malloc(len + 7);
14005     safeStrCpy(commentList[index], "{\n", 3);
14006     safeStrCpy(commentList[index]+2, text, len+1);
14007     commentList[index][len+2] = NULLCHAR;
14008     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14009     strcat(commentList[index], "\n}\n");
14010   }
14011 }
14012
14013 void
14014 CrushCRs(text)
14015      char *text;
14016 {
14017   char *p = text;
14018   char *q = text;
14019   char ch;
14020
14021   do {
14022     ch = *p++;
14023     if (ch == '\r') continue;
14024     *q++ = ch;
14025   } while (ch != '\0');
14026 }
14027
14028 void
14029 AppendComment(index, text, addBraces)
14030      int index;
14031      char *text;
14032      Boolean addBraces; // [HGM] braces: tells if we should add {}
14033 {
14034     int oldlen, len;
14035     char *old;
14036
14037 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14038     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14039
14040     CrushCRs(text);
14041     while (*text == '\n') text++;
14042     len = strlen(text);
14043     while (len > 0 && text[len - 1] == '\n') len--;
14044
14045     if (len == 0) return;
14046
14047     if (commentList[index] != NULL) {
14048         old = commentList[index];
14049         oldlen = strlen(old);
14050         while(commentList[index][oldlen-1] ==  '\n')
14051           commentList[index][--oldlen] = NULLCHAR;
14052         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14053         safeStrCpy(commentList[index], old, oldlen + len + 6);
14054         free(old);
14055         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14056         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14057           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14058           while (*text == '\n') { text++; len--; }
14059           commentList[index][--oldlen] = NULLCHAR;
14060       }
14061         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14062         else          strcat(commentList[index], "\n");
14063         strcat(commentList[index], text);
14064         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14065         else          strcat(commentList[index], "\n");
14066     } else {
14067         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14068         if(addBraces)
14069           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14070         else commentList[index][0] = NULLCHAR;
14071         strcat(commentList[index], text);
14072         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14073         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14074     }
14075 }
14076
14077 static char * FindStr( char * text, char * sub_text )
14078 {
14079     char * result = strstr( text, sub_text );
14080
14081     if( result != NULL ) {
14082         result += strlen( sub_text );
14083     }
14084
14085     return result;
14086 }
14087
14088 /* [AS] Try to extract PV info from PGN comment */
14089 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14090 char *GetInfoFromComment( int index, char * text )
14091 {
14092     char * sep = text, *p;
14093
14094     if( text != NULL && index > 0 ) {
14095         int score = 0;
14096         int depth = 0;
14097         int time = -1, sec = 0, deci;
14098         char * s_eval = FindStr( text, "[%eval " );
14099         char * s_emt = FindStr( text, "[%emt " );
14100
14101         if( s_eval != NULL || s_emt != NULL ) {
14102             /* New style */
14103             char delim;
14104
14105             if( s_eval != NULL ) {
14106                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14107                     return text;
14108                 }
14109
14110                 if( delim != ']' ) {
14111                     return text;
14112                 }
14113             }
14114
14115             if( s_emt != NULL ) {
14116             }
14117                 return text;
14118         }
14119         else {
14120             /* We expect something like: [+|-]nnn.nn/dd */
14121             int score_lo = 0;
14122
14123             if(*text != '{') return text; // [HGM] braces: must be normal comment
14124
14125             sep = strchr( text, '/' );
14126             if( sep == NULL || sep < (text+4) ) {
14127                 return text;
14128             }
14129
14130             p = text;
14131             if(p[1] == '(') { // comment starts with PV
14132                p = strchr(p, ')'); // locate end of PV
14133                if(p == NULL || sep < p+5) return text;
14134                // at this point we have something like "{(.*) +0.23/6 ..."
14135                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14136                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14137                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14138             }
14139             time = -1; sec = -1; deci = -1;
14140             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14141                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14142                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14143                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14144                 return text;
14145             }
14146
14147             if( score_lo < 0 || score_lo >= 100 ) {
14148                 return text;
14149             }
14150
14151             if(sec >= 0) time = 600*time + 10*sec; else
14152             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14153
14154             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14155
14156             /* [HGM] PV time: now locate end of PV info */
14157             while( *++sep >= '0' && *sep <= '9'); // strip depth
14158             if(time >= 0)
14159             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14160             if(sec >= 0)
14161             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14162             if(deci >= 0)
14163             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14164             while(*sep == ' ') sep++;
14165         }
14166
14167         if( depth <= 0 ) {
14168             return text;
14169         }
14170
14171         if( time < 0 ) {
14172             time = -1;
14173         }
14174
14175         pvInfoList[index-1].depth = depth;
14176         pvInfoList[index-1].score = score;
14177         pvInfoList[index-1].time  = 10*time; // centi-sec
14178         if(*sep == '}') *sep = 0; else *--sep = '{';
14179         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14180     }
14181     return sep;
14182 }
14183
14184 void
14185 SendToProgram(message, cps)
14186      char *message;
14187      ChessProgramState *cps;
14188 {
14189     int count, outCount, error;
14190     char buf[MSG_SIZ];
14191
14192     if (cps->pr == NULL) return;
14193     Attention(cps);
14194
14195     if (appData.debugMode) {
14196         TimeMark now;
14197         GetTimeMark(&now);
14198         fprintf(debugFP, "%ld >%-6s: %s",
14199                 SubtractTimeMarks(&now, &programStartTime),
14200                 cps->which, message);
14201     }
14202
14203     count = strlen(message);
14204     outCount = OutputToProcess(cps->pr, message, count, &error);
14205     if (outCount < count && !exiting
14206                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14207       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14208       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14209         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14210             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14211                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14212                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14213                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14214             } else {
14215                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14216                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14217                 gameInfo.result = res;
14218             }
14219             gameInfo.resultDetails = StrSave(buf);
14220         }
14221         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14222         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14223     }
14224 }
14225
14226 void
14227 ReceiveFromProgram(isr, closure, message, count, error)
14228      InputSourceRef isr;
14229      VOIDSTAR closure;
14230      char *message;
14231      int count;
14232      int error;
14233 {
14234     char *end_str;
14235     char buf[MSG_SIZ];
14236     ChessProgramState *cps = (ChessProgramState *)closure;
14237
14238     if (isr != cps->isr) return; /* Killed intentionally */
14239     if (count <= 0) {
14240         if (count == 0) {
14241             RemoveInputSource(cps->isr);
14242             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14243                     _(cps->which), cps->program);
14244         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14245                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14246                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14247                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14248                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14249                 } else {
14250                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14251                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14252                     gameInfo.result = res;
14253                 }
14254                 gameInfo.resultDetails = StrSave(buf);
14255             }
14256             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14257             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14258         } else {
14259             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14260                     _(cps->which), cps->program);
14261             RemoveInputSource(cps->isr);
14262
14263             /* [AS] Program is misbehaving badly... kill it */
14264             if( count == -2 ) {
14265                 DestroyChildProcess( cps->pr, 9 );
14266                 cps->pr = NoProc;
14267             }
14268
14269             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14270         }
14271         return;
14272     }
14273
14274     if ((end_str = strchr(message, '\r')) != NULL)
14275       *end_str = NULLCHAR;
14276     if ((end_str = strchr(message, '\n')) != NULL)
14277       *end_str = NULLCHAR;
14278
14279     if (appData.debugMode) {
14280         TimeMark now; int print = 1;
14281         char *quote = ""; char c; int i;
14282
14283         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14284                 char start = message[0];
14285                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14286                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14287                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14288                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14289                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14290                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14291                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14292                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14293                    sscanf(message, "hint: %c", &c)!=1 && 
14294                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14295                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14296                     print = (appData.engineComments >= 2);
14297                 }
14298                 message[0] = start; // restore original message
14299         }
14300         if(print) {
14301                 GetTimeMark(&now);
14302                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14303                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14304                         quote,
14305                         message);
14306         }
14307     }
14308
14309     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14310     if (appData.icsEngineAnalyze) {
14311         if (strstr(message, "whisper") != NULL ||
14312              strstr(message, "kibitz") != NULL ||
14313             strstr(message, "tellics") != NULL) return;
14314     }
14315
14316     HandleMachineMove(message, cps);
14317 }
14318
14319
14320 void
14321 SendTimeControl(cps, mps, tc, inc, sd, st)
14322      ChessProgramState *cps;
14323      int mps, inc, sd, st;
14324      long tc;
14325 {
14326     char buf[MSG_SIZ];
14327     int seconds;
14328
14329     if( timeControl_2 > 0 ) {
14330         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14331             tc = timeControl_2;
14332         }
14333     }
14334     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14335     inc /= cps->timeOdds;
14336     st  /= cps->timeOdds;
14337
14338     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14339
14340     if (st > 0) {
14341       /* Set exact time per move, normally using st command */
14342       if (cps->stKludge) {
14343         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14344         seconds = st % 60;
14345         if (seconds == 0) {
14346           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14347         } else {
14348           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14349         }
14350       } else {
14351         snprintf(buf, MSG_SIZ, "st %d\n", st);
14352       }
14353     } else {
14354       /* Set conventional or incremental time control, using level command */
14355       if (seconds == 0) {
14356         /* Note old gnuchess bug -- minutes:seconds used to not work.
14357            Fixed in later versions, but still avoid :seconds
14358            when seconds is 0. */
14359         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14360       } else {
14361         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14362                  seconds, inc/1000.);
14363       }
14364     }
14365     SendToProgram(buf, cps);
14366
14367     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14368     /* Orthogonally, limit search to given depth */
14369     if (sd > 0) {
14370       if (cps->sdKludge) {
14371         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14372       } else {
14373         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14374       }
14375       SendToProgram(buf, cps);
14376     }
14377
14378     if(cps->nps >= 0) { /* [HGM] nps */
14379         if(cps->supportsNPS == FALSE)
14380           cps->nps = -1; // don't use if engine explicitly says not supported!
14381         else {
14382           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14383           SendToProgram(buf, cps);
14384         }
14385     }
14386 }
14387
14388 ChessProgramState *WhitePlayer()
14389 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14390 {
14391     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14392        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14393         return &second;
14394     return &first;
14395 }
14396
14397 void
14398 SendTimeRemaining(cps, machineWhite)
14399      ChessProgramState *cps;
14400      int /*boolean*/ machineWhite;
14401 {
14402     char message[MSG_SIZ];
14403     long time, otime;
14404
14405     /* Note: this routine must be called when the clocks are stopped
14406        or when they have *just* been set or switched; otherwise
14407        it will be off by the time since the current tick started.
14408     */
14409     if (machineWhite) {
14410         time = whiteTimeRemaining / 10;
14411         otime = blackTimeRemaining / 10;
14412     } else {
14413         time = blackTimeRemaining / 10;
14414         otime = whiteTimeRemaining / 10;
14415     }
14416     /* [HGM] translate opponent's time by time-odds factor */
14417     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14418     if (appData.debugMode) {
14419         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14420     }
14421
14422     if (time <= 0) time = 1;
14423     if (otime <= 0) otime = 1;
14424
14425     snprintf(message, MSG_SIZ, "time %ld\n", time);
14426     SendToProgram(message, cps);
14427
14428     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14429     SendToProgram(message, cps);
14430 }
14431
14432 int
14433 BoolFeature(p, name, loc, cps)
14434      char **p;
14435      char *name;
14436      int *loc;
14437      ChessProgramState *cps;
14438 {
14439   char buf[MSG_SIZ];
14440   int len = strlen(name);
14441   int val;
14442
14443   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14444     (*p) += len + 1;
14445     sscanf(*p, "%d", &val);
14446     *loc = (val != 0);
14447     while (**p && **p != ' ')
14448       (*p)++;
14449     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14450     SendToProgram(buf, cps);
14451     return TRUE;
14452   }
14453   return FALSE;
14454 }
14455
14456 int
14457 IntFeature(p, name, loc, cps)
14458      char **p;
14459      char *name;
14460      int *loc;
14461      ChessProgramState *cps;
14462 {
14463   char buf[MSG_SIZ];
14464   int len = strlen(name);
14465   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14466     (*p) += len + 1;
14467     sscanf(*p, "%d", loc);
14468     while (**p && **p != ' ') (*p)++;
14469     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14470     SendToProgram(buf, cps);
14471     return TRUE;
14472   }
14473   return FALSE;
14474 }
14475
14476 int
14477 StringFeature(p, name, loc, cps)
14478      char **p;
14479      char *name;
14480      char loc[];
14481      ChessProgramState *cps;
14482 {
14483   char buf[MSG_SIZ];
14484   int len = strlen(name);
14485   if (strncmp((*p), name, len) == 0
14486       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14487     (*p) += len + 2;
14488     sscanf(*p, "%[^\"]", loc);
14489     while (**p && **p != '\"') (*p)++;
14490     if (**p == '\"') (*p)++;
14491     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14492     SendToProgram(buf, cps);
14493     return TRUE;
14494   }
14495   return FALSE;
14496 }
14497
14498 int
14499 ParseOption(Option *opt, ChessProgramState *cps)
14500 // [HGM] options: process the string that defines an engine option, and determine
14501 // name, type, default value, and allowed value range
14502 {
14503         char *p, *q, buf[MSG_SIZ];
14504         int n, min = (-1)<<31, max = 1<<31, def;
14505
14506         if(p = strstr(opt->name, " -spin ")) {
14507             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14508             if(max < min) max = min; // enforce consistency
14509             if(def < min) def = min;
14510             if(def > max) def = max;
14511             opt->value = def;
14512             opt->min = min;
14513             opt->max = max;
14514             opt->type = Spin;
14515         } else if((p = strstr(opt->name, " -slider "))) {
14516             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14517             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14518             if(max < min) max = min; // enforce consistency
14519             if(def < min) def = min;
14520             if(def > max) def = max;
14521             opt->value = def;
14522             opt->min = min;
14523             opt->max = max;
14524             opt->type = Spin; // Slider;
14525         } else if((p = strstr(opt->name, " -string "))) {
14526             opt->textValue = p+9;
14527             opt->type = TextBox;
14528         } else if((p = strstr(opt->name, " -file "))) {
14529             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14530             opt->textValue = p+7;
14531             opt->type = FileName; // FileName;
14532         } else if((p = strstr(opt->name, " -path "))) {
14533             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14534             opt->textValue = p+7;
14535             opt->type = PathName; // PathName;
14536         } else if(p = strstr(opt->name, " -check ")) {
14537             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14538             opt->value = (def != 0);
14539             opt->type = CheckBox;
14540         } else if(p = strstr(opt->name, " -combo ")) {
14541             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14542             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14543             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14544             opt->value = n = 0;
14545             while(q = StrStr(q, " /// ")) {
14546                 n++; *q = 0;    // count choices, and null-terminate each of them
14547                 q += 5;
14548                 if(*q == '*') { // remember default, which is marked with * prefix
14549                     q++;
14550                     opt->value = n;
14551                 }
14552                 cps->comboList[cps->comboCnt++] = q;
14553             }
14554             cps->comboList[cps->comboCnt++] = NULL;
14555             opt->max = n + 1;
14556             opt->type = ComboBox;
14557         } else if(p = strstr(opt->name, " -button")) {
14558             opt->type = Button;
14559         } else if(p = strstr(opt->name, " -save")) {
14560             opt->type = SaveButton;
14561         } else return FALSE;
14562         *p = 0; // terminate option name
14563         // now look if the command-line options define a setting for this engine option.
14564         if(cps->optionSettings && cps->optionSettings[0])
14565             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14566         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14567           snprintf(buf, MSG_SIZ, "option %s", p);
14568                 if(p = strstr(buf, ",")) *p = 0;
14569                 if(q = strchr(buf, '=')) switch(opt->type) {
14570                     case ComboBox:
14571                         for(n=0; n<opt->max; n++)
14572                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14573                         break;
14574                     case TextBox:
14575                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14576                         break;
14577                     case Spin:
14578                     case CheckBox:
14579                         opt->value = atoi(q+1);
14580                     default:
14581                         break;
14582                 }
14583                 strcat(buf, "\n");
14584                 SendToProgram(buf, cps);
14585         }
14586         return TRUE;
14587 }
14588
14589 void
14590 FeatureDone(cps, val)
14591      ChessProgramState* cps;
14592      int val;
14593 {
14594   DelayedEventCallback cb = GetDelayedEvent();
14595   if ((cb == InitBackEnd3 && cps == &first) ||
14596       (cb == SettingsMenuIfReady && cps == &second) ||
14597       (cb == LoadEngine) ||
14598       (cb == TwoMachinesEventIfReady)) {
14599     CancelDelayedEvent();
14600     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14601   }
14602   cps->initDone = val;
14603 }
14604
14605 /* Parse feature command from engine */
14606 void
14607 ParseFeatures(args, cps)
14608      char* args;
14609      ChessProgramState *cps;
14610 {
14611   char *p = args;
14612   char *q;
14613   int val;
14614   char buf[MSG_SIZ];
14615
14616   for (;;) {
14617     while (*p == ' ') p++;
14618     if (*p == NULLCHAR) return;
14619
14620     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14621     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14622     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14623     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14624     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14625     if (BoolFeature(&p, "reuse", &val, cps)) {
14626       /* Engine can disable reuse, but can't enable it if user said no */
14627       if (!val) cps->reuse = FALSE;
14628       continue;
14629     }
14630     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14631     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14632       if (gameMode == TwoMachinesPlay) {
14633         DisplayTwoMachinesTitle();
14634       } else {
14635         DisplayTitle("");
14636       }
14637       continue;
14638     }
14639     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14640     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14641     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14642     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14643     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14644     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14645     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14646     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14647     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14648     if (IntFeature(&p, "done", &val, cps)) {
14649       FeatureDone(cps, val);
14650       continue;
14651     }
14652     /* Added by Tord: */
14653     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14654     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14655     /* End of additions by Tord */
14656
14657     /* [HGM] added features: */
14658     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14659     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14660     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14661     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14662     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14663     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14664     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14665         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14666           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14667             SendToProgram(buf, cps);
14668             continue;
14669         }
14670         if(cps->nrOptions >= MAX_OPTIONS) {
14671             cps->nrOptions--;
14672             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14673             DisplayError(buf, 0);
14674         }
14675         continue;
14676     }
14677     /* End of additions by HGM */
14678
14679     /* unknown feature: complain and skip */
14680     q = p;
14681     while (*q && *q != '=') q++;
14682     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14683     SendToProgram(buf, cps);
14684     p = q;
14685     if (*p == '=') {
14686       p++;
14687       if (*p == '\"') {
14688         p++;
14689         while (*p && *p != '\"') p++;
14690         if (*p == '\"') p++;
14691       } else {
14692         while (*p && *p != ' ') p++;
14693       }
14694     }
14695   }
14696
14697 }
14698
14699 void
14700 PeriodicUpdatesEvent(newState)
14701      int newState;
14702 {
14703     if (newState == appData.periodicUpdates)
14704       return;
14705
14706     appData.periodicUpdates=newState;
14707
14708     /* Display type changes, so update it now */
14709 //    DisplayAnalysis();
14710
14711     /* Get the ball rolling again... */
14712     if (newState) {
14713         AnalysisPeriodicEvent(1);
14714         StartAnalysisClock();
14715     }
14716 }
14717
14718 void
14719 PonderNextMoveEvent(newState)
14720      int newState;
14721 {
14722     if (newState == appData.ponderNextMove) return;
14723     if (gameMode == EditPosition) EditPositionDone(TRUE);
14724     if (newState) {
14725         SendToProgram("hard\n", &first);
14726         if (gameMode == TwoMachinesPlay) {
14727             SendToProgram("hard\n", &second);
14728         }
14729     } else {
14730         SendToProgram("easy\n", &first);
14731         thinkOutput[0] = NULLCHAR;
14732         if (gameMode == TwoMachinesPlay) {
14733             SendToProgram("easy\n", &second);
14734         }
14735     }
14736     appData.ponderNextMove = newState;
14737 }
14738
14739 void
14740 NewSettingEvent(option, feature, command, value)
14741      char *command;
14742      int option, value, *feature;
14743 {
14744     char buf[MSG_SIZ];
14745
14746     if (gameMode == EditPosition) EditPositionDone(TRUE);
14747     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14748     if(feature == NULL || *feature) SendToProgram(buf, &first);
14749     if (gameMode == TwoMachinesPlay) {
14750         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14751     }
14752 }
14753
14754 void
14755 ShowThinkingEvent()
14756 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14757 {
14758     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14759     int newState = appData.showThinking
14760         // [HGM] thinking: other features now need thinking output as well
14761         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14762
14763     if (oldState == newState) return;
14764     oldState = newState;
14765     if (gameMode == EditPosition) EditPositionDone(TRUE);
14766     if (oldState) {
14767         SendToProgram("post\n", &first);
14768         if (gameMode == TwoMachinesPlay) {
14769             SendToProgram("post\n", &second);
14770         }
14771     } else {
14772         SendToProgram("nopost\n", &first);
14773         thinkOutput[0] = NULLCHAR;
14774         if (gameMode == TwoMachinesPlay) {
14775             SendToProgram("nopost\n", &second);
14776         }
14777     }
14778 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14779 }
14780
14781 void
14782 AskQuestionEvent(title, question, replyPrefix, which)
14783      char *title; char *question; char *replyPrefix; char *which;
14784 {
14785   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14786   if (pr == NoProc) return;
14787   AskQuestion(title, question, replyPrefix, pr);
14788 }
14789
14790 void
14791 TypeInEvent(char firstChar)
14792 {
14793     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14794         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14795         gameMode == AnalyzeMode || gameMode == EditGame || \r
14796         gameMode == EditPosition || gameMode == IcsExamining ||\r
14797         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14798         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14799                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14800                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14801         gameMode == Training) PopUpMoveDialog(firstChar);
14802 }
14803
14804 void
14805 TypeInDoneEvent(char *move)
14806 {
14807         Board board;
14808         int n, fromX, fromY, toX, toY;
14809         char promoChar;
14810         ChessMove moveType;\r
14811
14812         // [HGM] FENedit\r
14813         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14814                 EditPositionPasteFEN(move);\r
14815                 return;\r
14816         }\r
14817         // [HGM] movenum: allow move number to be typed in any mode\r
14818         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14819           ToNrEvent(2*n-1);\r
14820           return;\r
14821         }\r
14822
14823       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14824         gameMode != Training) {\r
14825         DisplayMoveError(_("Displayed move is not current"));\r
14826       } else {\r
14827         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14828           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14829         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14830         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14831           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14832           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14833         } else {\r
14834           DisplayMoveError(_("Could not parse move"));\r
14835         }
14836       }\r
14837 }\r
14838
14839 void
14840 DisplayMove(moveNumber)
14841      int moveNumber;
14842 {
14843     char message[MSG_SIZ];
14844     char res[MSG_SIZ];
14845     char cpThinkOutput[MSG_SIZ];
14846
14847     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14848
14849     if (moveNumber == forwardMostMove - 1 ||
14850         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14851
14852         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14853
14854         if (strchr(cpThinkOutput, '\n')) {
14855             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14856         }
14857     } else {
14858         *cpThinkOutput = NULLCHAR;
14859     }
14860
14861     /* [AS] Hide thinking from human user */
14862     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14863         *cpThinkOutput = NULLCHAR;
14864         if( thinkOutput[0] != NULLCHAR ) {
14865             int i;
14866
14867             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14868                 cpThinkOutput[i] = '.';
14869             }
14870             cpThinkOutput[i] = NULLCHAR;
14871             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14872         }
14873     }
14874
14875     if (moveNumber == forwardMostMove - 1 &&
14876         gameInfo.resultDetails != NULL) {
14877         if (gameInfo.resultDetails[0] == NULLCHAR) {
14878           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14879         } else {
14880           snprintf(res, MSG_SIZ, " {%s} %s",
14881                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14882         }
14883     } else {
14884         res[0] = NULLCHAR;
14885     }
14886
14887     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14888         DisplayMessage(res, cpThinkOutput);
14889     } else {
14890       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14891                 WhiteOnMove(moveNumber) ? " " : ".. ",
14892                 parseList[moveNumber], res);
14893         DisplayMessage(message, cpThinkOutput);
14894     }
14895 }
14896
14897 void
14898 DisplayComment(moveNumber, text)
14899      int moveNumber;
14900      char *text;
14901 {
14902     char title[MSG_SIZ];
14903     char buf[8000]; // comment can be long!
14904     int score, depth;
14905
14906     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14907       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14908     } else {
14909       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14910               WhiteOnMove(moveNumber) ? " " : ".. ",
14911               parseList[moveNumber]);
14912     }
14913     // [HGM] PV info: display PV info together with (or as) comment
14914     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14915       if(text == NULL) text = "";
14916       score = pvInfoList[moveNumber].score;
14917       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14918               depth, (pvInfoList[moveNumber].time+50)/100, text);
14919       text = buf;
14920     }
14921     if (text != NULL && (appData.autoDisplayComment || commentUp))
14922         CommentPopUp(title, text);
14923 }
14924
14925 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14926  * might be busy thinking or pondering.  It can be omitted if your
14927  * gnuchess is configured to stop thinking immediately on any user
14928  * input.  However, that gnuchess feature depends on the FIONREAD
14929  * ioctl, which does not work properly on some flavors of Unix.
14930  */
14931 void
14932 Attention(cps)
14933      ChessProgramState *cps;
14934 {
14935 #if ATTENTION
14936     if (!cps->useSigint) return;
14937     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14938     switch (gameMode) {
14939       case MachinePlaysWhite:
14940       case MachinePlaysBlack:
14941       case TwoMachinesPlay:
14942       case IcsPlayingWhite:
14943       case IcsPlayingBlack:
14944       case AnalyzeMode:
14945       case AnalyzeFile:
14946         /* Skip if we know it isn't thinking */
14947         if (!cps->maybeThinking) return;
14948         if (appData.debugMode)
14949           fprintf(debugFP, "Interrupting %s\n", cps->which);
14950         InterruptChildProcess(cps->pr);
14951         cps->maybeThinking = FALSE;
14952         break;
14953       default:
14954         break;
14955     }
14956 #endif /*ATTENTION*/
14957 }
14958
14959 int
14960 CheckFlags()
14961 {
14962     if (whiteTimeRemaining <= 0) {
14963         if (!whiteFlag) {
14964             whiteFlag = TRUE;
14965             if (appData.icsActive) {
14966                 if (appData.autoCallFlag &&
14967                     gameMode == IcsPlayingBlack && !blackFlag) {
14968                   SendToICS(ics_prefix);
14969                   SendToICS("flag\n");
14970                 }
14971             } else {
14972                 if (blackFlag) {
14973                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14974                 } else {
14975                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14976                     if (appData.autoCallFlag) {
14977                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14978                         return TRUE;
14979                     }
14980                 }
14981             }
14982         }
14983     }
14984     if (blackTimeRemaining <= 0) {
14985         if (!blackFlag) {
14986             blackFlag = TRUE;
14987             if (appData.icsActive) {
14988                 if (appData.autoCallFlag &&
14989                     gameMode == IcsPlayingWhite && !whiteFlag) {
14990                   SendToICS(ics_prefix);
14991                   SendToICS("flag\n");
14992                 }
14993             } else {
14994                 if (whiteFlag) {
14995                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14996                 } else {
14997                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14998                     if (appData.autoCallFlag) {
14999                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15000                         return TRUE;
15001                     }
15002                 }
15003             }
15004         }
15005     }
15006     return FALSE;
15007 }
15008
15009 void
15010 CheckTimeControl()
15011 {
15012     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15013         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15014
15015     /*
15016      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15017      */
15018     if ( !WhiteOnMove(forwardMostMove) ) {
15019         /* White made time control */
15020         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15021         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15022         /* [HGM] time odds: correct new time quota for time odds! */
15023                                             / WhitePlayer()->timeOdds;
15024         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15025     } else {
15026         lastBlack -= blackTimeRemaining;
15027         /* Black made time control */
15028         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15029                                             / WhitePlayer()->other->timeOdds;
15030         lastWhite = whiteTimeRemaining;
15031     }
15032 }
15033
15034 void
15035 DisplayBothClocks()
15036 {
15037     int wom = gameMode == EditPosition ?
15038       !blackPlaysFirst : WhiteOnMove(currentMove);
15039     DisplayWhiteClock(whiteTimeRemaining, wom);
15040     DisplayBlackClock(blackTimeRemaining, !wom);
15041 }
15042
15043
15044 /* Timekeeping seems to be a portability nightmare.  I think everyone
15045    has ftime(), but I'm really not sure, so I'm including some ifdefs
15046    to use other calls if you don't.  Clocks will be less accurate if
15047    you have neither ftime nor gettimeofday.
15048 */
15049
15050 /* VS 2008 requires the #include outside of the function */
15051 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15052 #include <sys/timeb.h>
15053 #endif
15054
15055 /* Get the current time as a TimeMark */
15056 void
15057 GetTimeMark(tm)
15058      TimeMark *tm;
15059 {
15060 #if HAVE_GETTIMEOFDAY
15061
15062     struct timeval timeVal;
15063     struct timezone timeZone;
15064
15065     gettimeofday(&timeVal, &timeZone);
15066     tm->sec = (long) timeVal.tv_sec;
15067     tm->ms = (int) (timeVal.tv_usec / 1000L);
15068
15069 #else /*!HAVE_GETTIMEOFDAY*/
15070 #if HAVE_FTIME
15071
15072 // include <sys/timeb.h> / moved to just above start of function
15073     struct timeb timeB;
15074
15075     ftime(&timeB);
15076     tm->sec = (long) timeB.time;
15077     tm->ms = (int) timeB.millitm;
15078
15079 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15080     tm->sec = (long) time(NULL);
15081     tm->ms = 0;
15082 #endif
15083 #endif
15084 }
15085
15086 /* Return the difference in milliseconds between two
15087    time marks.  We assume the difference will fit in a long!
15088 */
15089 long
15090 SubtractTimeMarks(tm2, tm1)
15091      TimeMark *tm2, *tm1;
15092 {
15093     return 1000L*(tm2->sec - tm1->sec) +
15094            (long) (tm2->ms - tm1->ms);
15095 }
15096
15097
15098 /*
15099  * Code to manage the game clocks.
15100  *
15101  * In tournament play, black starts the clock and then white makes a move.
15102  * We give the human user a slight advantage if he is playing white---the
15103  * clocks don't run until he makes his first move, so it takes zero time.
15104  * Also, we don't account for network lag, so we could get out of sync
15105  * with GNU Chess's clock -- but then, referees are always right.
15106  */
15107
15108 static TimeMark tickStartTM;
15109 static long intendedTickLength;
15110
15111 long
15112 NextTickLength(timeRemaining)
15113      long timeRemaining;
15114 {
15115     long nominalTickLength, nextTickLength;
15116
15117     if (timeRemaining > 0L && timeRemaining <= 10000L)
15118       nominalTickLength = 100L;
15119     else
15120       nominalTickLength = 1000L;
15121     nextTickLength = timeRemaining % nominalTickLength;
15122     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15123
15124     return nextTickLength;
15125 }
15126
15127 /* Adjust clock one minute up or down */
15128 void
15129 AdjustClock(Boolean which, int dir)
15130 {
15131     if(which) blackTimeRemaining += 60000*dir;
15132     else      whiteTimeRemaining += 60000*dir;
15133     DisplayBothClocks();
15134 }
15135
15136 /* Stop clocks and reset to a fresh time control */
15137 void
15138 ResetClocks()
15139 {
15140     (void) StopClockTimer();
15141     if (appData.icsActive) {
15142         whiteTimeRemaining = blackTimeRemaining = 0;
15143     } else if (searchTime) {
15144         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15145         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15146     } else { /* [HGM] correct new time quote for time odds */
15147         whiteTC = blackTC = fullTimeControlString;
15148         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15149         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15150     }
15151     if (whiteFlag || blackFlag) {
15152         DisplayTitle("");
15153         whiteFlag = blackFlag = FALSE;
15154     }
15155     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15156     DisplayBothClocks();
15157 }
15158
15159 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15160
15161 /* Decrement running clock by amount of time that has passed */
15162 void
15163 DecrementClocks()
15164 {
15165     long timeRemaining;
15166     long lastTickLength, fudge;
15167     TimeMark now;
15168
15169     if (!appData.clockMode) return;
15170     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15171
15172     GetTimeMark(&now);
15173
15174     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15175
15176     /* Fudge if we woke up a little too soon */
15177     fudge = intendedTickLength - lastTickLength;
15178     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15179
15180     if (WhiteOnMove(forwardMostMove)) {
15181         if(whiteNPS >= 0) lastTickLength = 0;
15182         timeRemaining = whiteTimeRemaining -= lastTickLength;
15183         if(timeRemaining < 0 && !appData.icsActive) {
15184             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15185             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15186                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15187                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15188             }
15189         }
15190         DisplayWhiteClock(whiteTimeRemaining - fudge,
15191                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15192     } else {
15193         if(blackNPS >= 0) lastTickLength = 0;
15194         timeRemaining = blackTimeRemaining -= lastTickLength;
15195         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15196             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15197             if(suddenDeath) {
15198                 blackStartMove = forwardMostMove;
15199                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15200             }
15201         }
15202         DisplayBlackClock(blackTimeRemaining - fudge,
15203                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15204     }
15205     if (CheckFlags()) return;
15206
15207     tickStartTM = now;
15208     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15209     StartClockTimer(intendedTickLength);
15210
15211     /* if the time remaining has fallen below the alarm threshold, sound the
15212      * alarm. if the alarm has sounded and (due to a takeback or time control
15213      * with increment) the time remaining has increased to a level above the
15214      * threshold, reset the alarm so it can sound again.
15215      */
15216
15217     if (appData.icsActive && appData.icsAlarm) {
15218
15219         /* make sure we are dealing with the user's clock */
15220         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15221                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15222            )) return;
15223
15224         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15225             alarmSounded = FALSE;
15226         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15227             PlayAlarmSound();
15228             alarmSounded = TRUE;
15229         }
15230     }
15231 }
15232
15233
15234 /* A player has just moved, so stop the previously running
15235    clock and (if in clock mode) start the other one.
15236    We redisplay both clocks in case we're in ICS mode, because
15237    ICS gives us an update to both clocks after every move.
15238    Note that this routine is called *after* forwardMostMove
15239    is updated, so the last fractional tick must be subtracted
15240    from the color that is *not* on move now.
15241 */
15242 void
15243 SwitchClocks(int newMoveNr)
15244 {
15245     long lastTickLength;
15246     TimeMark now;
15247     int flagged = FALSE;
15248
15249     GetTimeMark(&now);
15250
15251     if (StopClockTimer() && appData.clockMode) {
15252         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15253         if (!WhiteOnMove(forwardMostMove)) {
15254             if(blackNPS >= 0) lastTickLength = 0;
15255             blackTimeRemaining -= lastTickLength;
15256            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15257 //         if(pvInfoList[forwardMostMove].time == -1)
15258                  pvInfoList[forwardMostMove].time =               // use GUI time
15259                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15260         } else {
15261            if(whiteNPS >= 0) lastTickLength = 0;
15262            whiteTimeRemaining -= lastTickLength;
15263            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15264 //         if(pvInfoList[forwardMostMove].time == -1)
15265                  pvInfoList[forwardMostMove].time =
15266                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15267         }
15268         flagged = CheckFlags();
15269     }
15270     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15271     CheckTimeControl();
15272
15273     if (flagged || !appData.clockMode) return;
15274
15275     switch (gameMode) {
15276       case MachinePlaysBlack:
15277       case MachinePlaysWhite:
15278       case BeginningOfGame:
15279         if (pausing) return;
15280         break;
15281
15282       case EditGame:
15283       case PlayFromGameFile:
15284       case IcsExamining:
15285         return;
15286
15287       default:
15288         break;
15289     }
15290
15291     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15292         if(WhiteOnMove(forwardMostMove))
15293              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15294         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15295     }
15296
15297     tickStartTM = now;
15298     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15299       whiteTimeRemaining : blackTimeRemaining);
15300     StartClockTimer(intendedTickLength);
15301 }
15302
15303
15304 /* Stop both clocks */
15305 void
15306 StopClocks()
15307 {
15308     long lastTickLength;
15309     TimeMark now;
15310
15311     if (!StopClockTimer()) return;
15312     if (!appData.clockMode) return;
15313
15314     GetTimeMark(&now);
15315
15316     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15317     if (WhiteOnMove(forwardMostMove)) {
15318         if(whiteNPS >= 0) lastTickLength = 0;
15319         whiteTimeRemaining -= lastTickLength;
15320         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15321     } else {
15322         if(blackNPS >= 0) lastTickLength = 0;
15323         blackTimeRemaining -= lastTickLength;
15324         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15325     }
15326     CheckFlags();
15327 }
15328
15329 /* Start clock of player on move.  Time may have been reset, so
15330    if clock is already running, stop and restart it. */
15331 void
15332 StartClocks()
15333 {
15334     (void) StopClockTimer(); /* in case it was running already */
15335     DisplayBothClocks();
15336     if (CheckFlags()) return;
15337
15338     if (!appData.clockMode) return;
15339     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15340
15341     GetTimeMark(&tickStartTM);
15342     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15343       whiteTimeRemaining : blackTimeRemaining);
15344
15345    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15346     whiteNPS = blackNPS = -1;
15347     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15348        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15349         whiteNPS = first.nps;
15350     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15351        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15352         blackNPS = first.nps;
15353     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15354         whiteNPS = second.nps;
15355     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15356         blackNPS = second.nps;
15357     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15358
15359     StartClockTimer(intendedTickLength);
15360 }
15361
15362 char *
15363 TimeString(ms)
15364      long ms;
15365 {
15366     long second, minute, hour, day;
15367     char *sign = "";
15368     static char buf[32];
15369
15370     if (ms > 0 && ms <= 9900) {
15371       /* convert milliseconds to tenths, rounding up */
15372       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15373
15374       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15375       return buf;
15376     }
15377
15378     /* convert milliseconds to seconds, rounding up */
15379     /* use floating point to avoid strangeness of integer division
15380        with negative dividends on many machines */
15381     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15382
15383     if (second < 0) {
15384         sign = "-";
15385         second = -second;
15386     }
15387
15388     day = second / (60 * 60 * 24);
15389     second = second % (60 * 60 * 24);
15390     hour = second / (60 * 60);
15391     second = second % (60 * 60);
15392     minute = second / 60;
15393     second = second % 60;
15394
15395     if (day > 0)
15396       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15397               sign, day, hour, minute, second);
15398     else if (hour > 0)
15399       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15400     else
15401       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15402
15403     return buf;
15404 }
15405
15406
15407 /*
15408  * This is necessary because some C libraries aren't ANSI C compliant yet.
15409  */
15410 char *
15411 StrStr(string, match)
15412      char *string, *match;
15413 {
15414     int i, length;
15415
15416     length = strlen(match);
15417
15418     for (i = strlen(string) - length; i >= 0; i--, string++)
15419       if (!strncmp(match, string, length))
15420         return string;
15421
15422     return NULL;
15423 }
15424
15425 char *
15426 StrCaseStr(string, match)
15427      char *string, *match;
15428 {
15429     int i, j, length;
15430
15431     length = strlen(match);
15432
15433     for (i = strlen(string) - length; i >= 0; i--, string++) {
15434         for (j = 0; j < length; j++) {
15435             if (ToLower(match[j]) != ToLower(string[j]))
15436               break;
15437         }
15438         if (j == length) return string;
15439     }
15440
15441     return NULL;
15442 }
15443
15444 #ifndef _amigados
15445 int
15446 StrCaseCmp(s1, s2)
15447      char *s1, *s2;
15448 {
15449     char c1, c2;
15450
15451     for (;;) {
15452         c1 = ToLower(*s1++);
15453         c2 = ToLower(*s2++);
15454         if (c1 > c2) return 1;
15455         if (c1 < c2) return -1;
15456         if (c1 == NULLCHAR) return 0;
15457     }
15458 }
15459
15460
15461 int
15462 ToLower(c)
15463      int c;
15464 {
15465     return isupper(c) ? tolower(c) : c;
15466 }
15467
15468
15469 int
15470 ToUpper(c)
15471      int c;
15472 {
15473     return islower(c) ? toupper(c) : c;
15474 }
15475 #endif /* !_amigados    */
15476
15477 char *
15478 StrSave(s)
15479      char *s;
15480 {
15481   char *ret;
15482
15483   if ((ret = (char *) malloc(strlen(s) + 1)))
15484     {
15485       safeStrCpy(ret, s, strlen(s)+1);
15486     }
15487   return ret;
15488 }
15489
15490 char *
15491 StrSavePtr(s, savePtr)
15492      char *s, **savePtr;
15493 {
15494     if (*savePtr) {
15495         free(*savePtr);
15496     }
15497     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15498       safeStrCpy(*savePtr, s, strlen(s)+1);
15499     }
15500     return(*savePtr);
15501 }
15502
15503 char *
15504 PGNDate()
15505 {
15506     time_t clock;
15507     struct tm *tm;
15508     char buf[MSG_SIZ];
15509
15510     clock = time((time_t *)NULL);
15511     tm = localtime(&clock);
15512     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15513             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15514     return StrSave(buf);
15515 }
15516
15517
15518 char *
15519 PositionToFEN(move, overrideCastling)
15520      int move;
15521      char *overrideCastling;
15522 {
15523     int i, j, fromX, fromY, toX, toY;
15524     int whiteToPlay;
15525     char buf[128];
15526     char *p, *q;
15527     int emptycount;
15528     ChessSquare piece;
15529
15530     whiteToPlay = (gameMode == EditPosition) ?
15531       !blackPlaysFirst : (move % 2 == 0);
15532     p = buf;
15533
15534     /* Piece placement data */
15535     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15536         emptycount = 0;
15537         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15538             if (boards[move][i][j] == EmptySquare) {
15539                 emptycount++;
15540             } else { ChessSquare piece = boards[move][i][j];
15541                 if (emptycount > 0) {
15542                     if(emptycount<10) /* [HGM] can be >= 10 */
15543                         *p++ = '0' + emptycount;
15544                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15545                     emptycount = 0;
15546                 }
15547                 if(PieceToChar(piece) == '+') {
15548                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15549                     *p++ = '+';
15550                     piece = (ChessSquare)(DEMOTED piece);
15551                 }
15552                 *p++ = PieceToChar(piece);
15553                 if(p[-1] == '~') {
15554                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15555                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15556                     *p++ = '~';
15557                 }
15558             }
15559         }
15560         if (emptycount > 0) {
15561             if(emptycount<10) /* [HGM] can be >= 10 */
15562                 *p++ = '0' + emptycount;
15563             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15564             emptycount = 0;
15565         }
15566         *p++ = '/';
15567     }
15568     *(p - 1) = ' ';
15569
15570     /* [HGM] print Crazyhouse or Shogi holdings */
15571     if( gameInfo.holdingsWidth ) {
15572         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15573         q = p;
15574         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15575             piece = boards[move][i][BOARD_WIDTH-1];
15576             if( piece != EmptySquare )
15577               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15578                   *p++ = PieceToChar(piece);
15579         }
15580         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15581             piece = boards[move][BOARD_HEIGHT-i-1][0];
15582             if( piece != EmptySquare )
15583               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15584                   *p++ = PieceToChar(piece);
15585         }
15586
15587         if( q == p ) *p++ = '-';
15588         *p++ = ']';
15589         *p++ = ' ';
15590     }
15591
15592     /* Active color */
15593     *p++ = whiteToPlay ? 'w' : 'b';
15594     *p++ = ' ';
15595
15596   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15597     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15598   } else {
15599   if(nrCastlingRights) {
15600      q = p;
15601      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15602        /* [HGM] write directly from rights */
15603            if(boards[move][CASTLING][2] != NoRights &&
15604               boards[move][CASTLING][0] != NoRights   )
15605                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15606            if(boards[move][CASTLING][2] != NoRights &&
15607               boards[move][CASTLING][1] != NoRights   )
15608                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15609            if(boards[move][CASTLING][5] != NoRights &&
15610               boards[move][CASTLING][3] != NoRights   )
15611                 *p++ = boards[move][CASTLING][3] + AAA;
15612            if(boards[move][CASTLING][5] != NoRights &&
15613               boards[move][CASTLING][4] != NoRights   )
15614                 *p++ = boards[move][CASTLING][4] + AAA;
15615      } else {
15616
15617         /* [HGM] write true castling rights */
15618         if( nrCastlingRights == 6 ) {
15619             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15620                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15621             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15622                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15623             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15624                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15625             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15626                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15627         }
15628      }
15629      if (q == p) *p++ = '-'; /* No castling rights */
15630      *p++ = ' ';
15631   }
15632
15633   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15634      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15635     /* En passant target square */
15636     if (move > backwardMostMove) {
15637         fromX = moveList[move - 1][0] - AAA;
15638         fromY = moveList[move - 1][1] - ONE;
15639         toX = moveList[move - 1][2] - AAA;
15640         toY = moveList[move - 1][3] - ONE;
15641         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15642             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15643             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15644             fromX == toX) {
15645             /* 2-square pawn move just happened */
15646             *p++ = toX + AAA;
15647             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15648         } else {
15649             *p++ = '-';
15650         }
15651     } else if(move == backwardMostMove) {
15652         // [HGM] perhaps we should always do it like this, and forget the above?
15653         if((signed char)boards[move][EP_STATUS] >= 0) {
15654             *p++ = boards[move][EP_STATUS] + AAA;
15655             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15656         } else {
15657             *p++ = '-';
15658         }
15659     } else {
15660         *p++ = '-';
15661     }
15662     *p++ = ' ';
15663   }
15664   }
15665
15666     /* [HGM] find reversible plies */
15667     {   int i = 0, j=move;
15668
15669         if (appData.debugMode) { int k;
15670             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15671             for(k=backwardMostMove; k<=forwardMostMove; k++)
15672                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15673
15674         }
15675
15676         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15677         if( j == backwardMostMove ) i += initialRulePlies;
15678         sprintf(p, "%d ", i);
15679         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15680     }
15681     /* Fullmove number */
15682     sprintf(p, "%d", (move / 2) + 1);
15683
15684     return StrSave(buf);
15685 }
15686
15687 Boolean
15688 ParseFEN(board, blackPlaysFirst, fen)
15689     Board board;
15690      int *blackPlaysFirst;
15691      char *fen;
15692 {
15693     int i, j;
15694     char *p, c;
15695     int emptycount;
15696     ChessSquare piece;
15697
15698     p = fen;
15699
15700     /* [HGM] by default clear Crazyhouse holdings, if present */
15701     if(gameInfo.holdingsWidth) {
15702        for(i=0; i<BOARD_HEIGHT; i++) {
15703            board[i][0]             = EmptySquare; /* black holdings */
15704            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15705            board[i][1]             = (ChessSquare) 0; /* black counts */
15706            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15707        }
15708     }
15709
15710     /* Piece placement data */
15711     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15712         j = 0;
15713         for (;;) {
15714             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15715                 if (*p == '/') p++;
15716                 emptycount = gameInfo.boardWidth - j;
15717                 while (emptycount--)
15718                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15719                 break;
15720 #if(BOARD_FILES >= 10)
15721             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15722                 p++; emptycount=10;
15723                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15724                 while (emptycount--)
15725                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15726 #endif
15727             } else if (isdigit(*p)) {
15728                 emptycount = *p++ - '0';
15729                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15730                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15731                 while (emptycount--)
15732                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15733             } else if (*p == '+' || isalpha(*p)) {
15734                 if (j >= gameInfo.boardWidth) return FALSE;
15735                 if(*p=='+') {
15736                     piece = CharToPiece(*++p);
15737                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15738                     piece = (ChessSquare) (PROMOTED piece ); p++;
15739                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15740                 } else piece = CharToPiece(*p++);
15741
15742                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15743                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15744                     piece = (ChessSquare) (PROMOTED piece);
15745                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15746                     p++;
15747                 }
15748                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15749             } else {
15750                 return FALSE;
15751             }
15752         }
15753     }
15754     while (*p == '/' || *p == ' ') p++;
15755
15756     /* [HGM] look for Crazyhouse holdings here */
15757     while(*p==' ') p++;
15758     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15759         if(*p == '[') p++;
15760         if(*p == '-' ) p++; /* empty holdings */ else {
15761             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15762             /* if we would allow FEN reading to set board size, we would   */
15763             /* have to add holdings and shift the board read so far here   */
15764             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15765                 p++;
15766                 if((int) piece >= (int) BlackPawn ) {
15767                     i = (int)piece - (int)BlackPawn;
15768                     i = PieceToNumber((ChessSquare)i);
15769                     if( i >= gameInfo.holdingsSize ) return FALSE;
15770                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15771                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15772                 } else {
15773                     i = (int)piece - (int)WhitePawn;
15774                     i = PieceToNumber((ChessSquare)i);
15775                     if( i >= gameInfo.holdingsSize ) return FALSE;
15776                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15777                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15778                 }
15779             }
15780         }
15781         if(*p == ']') p++;
15782     }
15783
15784     while(*p == ' ') p++;
15785
15786     /* Active color */
15787     c = *p++;
15788     if(appData.colorNickNames) {
15789       if( c == appData.colorNickNames[0] ) c = 'w'; else
15790       if( c == appData.colorNickNames[1] ) c = 'b';
15791     }
15792     switch (c) {
15793       case 'w':
15794         *blackPlaysFirst = FALSE;
15795         break;
15796       case 'b':
15797         *blackPlaysFirst = TRUE;
15798         break;
15799       default:
15800         return FALSE;
15801     }
15802
15803     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15804     /* return the extra info in global variiables             */
15805
15806     /* set defaults in case FEN is incomplete */
15807     board[EP_STATUS] = EP_UNKNOWN;
15808     for(i=0; i<nrCastlingRights; i++ ) {
15809         board[CASTLING][i] =
15810             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15811     }   /* assume possible unless obviously impossible */
15812     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15813     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15814     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15815                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15816     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15817     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15818     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15819                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15820     FENrulePlies = 0;
15821
15822     while(*p==' ') p++;
15823     if(nrCastlingRights) {
15824       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15825           /* castling indicator present, so default becomes no castlings */
15826           for(i=0; i<nrCastlingRights; i++ ) {
15827                  board[CASTLING][i] = NoRights;
15828           }
15829       }
15830       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15831              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15832              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15833              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15834         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15835
15836         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15837             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15838             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15839         }
15840         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15841             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15842         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15843                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15844         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15845                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15846         switch(c) {
15847           case'K':
15848               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15849               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15850               board[CASTLING][2] = whiteKingFile;
15851               break;
15852           case'Q':
15853               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15854               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15855               board[CASTLING][2] = whiteKingFile;
15856               break;
15857           case'k':
15858               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15859               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15860               board[CASTLING][5] = blackKingFile;
15861               break;
15862           case'q':
15863               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15864               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15865               board[CASTLING][5] = blackKingFile;
15866           case '-':
15867               break;
15868           default: /* FRC castlings */
15869               if(c >= 'a') { /* black rights */
15870                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15871                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15872                   if(i == BOARD_RGHT) break;
15873                   board[CASTLING][5] = i;
15874                   c -= AAA;
15875                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15876                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15877                   if(c > i)
15878                       board[CASTLING][3] = c;
15879                   else
15880                       board[CASTLING][4] = c;
15881               } else { /* white rights */
15882                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15883                     if(board[0][i] == WhiteKing) break;
15884                   if(i == BOARD_RGHT) break;
15885                   board[CASTLING][2] = i;
15886                   c -= AAA - 'a' + 'A';
15887                   if(board[0][c] >= WhiteKing) break;
15888                   if(c > i)
15889                       board[CASTLING][0] = c;
15890                   else
15891                       board[CASTLING][1] = c;
15892               }
15893         }
15894       }
15895       for(i=0; i<nrCastlingRights; i++)
15896         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15897     if (appData.debugMode) {
15898         fprintf(debugFP, "FEN castling rights:");
15899         for(i=0; i<nrCastlingRights; i++)
15900         fprintf(debugFP, " %d", board[CASTLING][i]);
15901         fprintf(debugFP, "\n");
15902     }
15903
15904       while(*p==' ') p++;
15905     }
15906
15907     /* read e.p. field in games that know e.p. capture */
15908     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15909        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15910       if(*p=='-') {
15911         p++; board[EP_STATUS] = EP_NONE;
15912       } else {
15913          char c = *p++ - AAA;
15914
15915          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15916          if(*p >= '0' && *p <='9') p++;
15917          board[EP_STATUS] = c;
15918       }
15919     }
15920
15921
15922     if(sscanf(p, "%d", &i) == 1) {
15923         FENrulePlies = i; /* 50-move ply counter */
15924         /* (The move number is still ignored)    */
15925     }
15926
15927     return TRUE;
15928 }
15929
15930 void
15931 EditPositionPasteFEN(char *fen)
15932 {
15933   if (fen != NULL) {
15934     Board initial_position;
15935
15936     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15937       DisplayError(_("Bad FEN position in clipboard"), 0);
15938       return ;
15939     } else {
15940       int savedBlackPlaysFirst = blackPlaysFirst;
15941       EditPositionEvent();
15942       blackPlaysFirst = savedBlackPlaysFirst;
15943       CopyBoard(boards[0], initial_position);
15944       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15945       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15946       DisplayBothClocks();
15947       DrawPosition(FALSE, boards[currentMove]);
15948     }
15949   }
15950 }
15951
15952 static char cseq[12] = "\\   ";
15953
15954 Boolean set_cont_sequence(char *new_seq)
15955 {
15956     int len;
15957     Boolean ret;
15958
15959     // handle bad attempts to set the sequence
15960         if (!new_seq)
15961                 return 0; // acceptable error - no debug
15962
15963     len = strlen(new_seq);
15964     ret = (len > 0) && (len < sizeof(cseq));
15965     if (ret)
15966       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15967     else if (appData.debugMode)
15968       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15969     return ret;
15970 }
15971
15972 /*
15973     reformat a source message so words don't cross the width boundary.  internal
15974     newlines are not removed.  returns the wrapped size (no null character unless
15975     included in source message).  If dest is NULL, only calculate the size required
15976     for the dest buffer.  lp argument indicats line position upon entry, and it's
15977     passed back upon exit.
15978 */
15979 int wrap(char *dest, char *src, int count, int width, int *lp)
15980 {
15981     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15982
15983     cseq_len = strlen(cseq);
15984     old_line = line = *lp;
15985     ansi = len = clen = 0;
15986
15987     for (i=0; i < count; i++)
15988     {
15989         if (src[i] == '\033')
15990             ansi = 1;
15991
15992         // if we hit the width, back up
15993         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15994         {
15995             // store i & len in case the word is too long
15996             old_i = i, old_len = len;
15997
15998             // find the end of the last word
15999             while (i && src[i] != ' ' && src[i] != '\n')
16000             {
16001                 i--;
16002                 len--;
16003             }
16004
16005             // word too long?  restore i & len before splitting it
16006             if ((old_i-i+clen) >= width)
16007             {
16008                 i = old_i;
16009                 len = old_len;
16010             }
16011
16012             // extra space?
16013             if (i && src[i-1] == ' ')
16014                 len--;
16015
16016             if (src[i] != ' ' && src[i] != '\n')
16017             {
16018                 i--;
16019                 if (len)
16020                     len--;
16021             }
16022
16023             // now append the newline and continuation sequence
16024             if (dest)
16025                 dest[len] = '\n';
16026             len++;
16027             if (dest)
16028                 strncpy(dest+len, cseq, cseq_len);
16029             len += cseq_len;
16030             line = cseq_len;
16031             clen = cseq_len;
16032             continue;
16033         }
16034
16035         if (dest)
16036             dest[len] = src[i];
16037         len++;
16038         if (!ansi)
16039             line++;
16040         if (src[i] == '\n')
16041             line = 0;
16042         if (src[i] == 'm')
16043             ansi = 0;
16044     }
16045     if (dest && appData.debugMode)
16046     {
16047         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16048             count, width, line, len, *lp);
16049         show_bytes(debugFP, src, count);
16050         fprintf(debugFP, "\ndest: ");
16051         show_bytes(debugFP, dest, len);
16052         fprintf(debugFP, "\n");
16053     }
16054     *lp = dest ? line : old_line;
16055
16056     return len;
16057 }
16058
16059 // [HGM] vari: routines for shelving variations
16060
16061 void
16062 PushTail(int firstMove, int lastMove)
16063 {
16064         int i, j, nrMoves = lastMove - firstMove;
16065
16066         if(appData.icsActive) { // only in local mode
16067                 forwardMostMove = currentMove; // mimic old ICS behavior
16068                 return;
16069         }
16070         if(storedGames >= MAX_VARIATIONS-1) return;
16071
16072         // push current tail of game on stack
16073         savedResult[storedGames] = gameInfo.result;
16074         savedDetails[storedGames] = gameInfo.resultDetails;
16075         gameInfo.resultDetails = NULL;
16076         savedFirst[storedGames] = firstMove;
16077         savedLast [storedGames] = lastMove;
16078         savedFramePtr[storedGames] = framePtr;
16079         framePtr -= nrMoves; // reserve space for the boards
16080         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16081             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16082             for(j=0; j<MOVE_LEN; j++)
16083                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16084             for(j=0; j<2*MOVE_LEN; j++)
16085                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16086             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16087             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16088             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16089             pvInfoList[firstMove+i-1].depth = 0;
16090             commentList[framePtr+i] = commentList[firstMove+i];
16091             commentList[firstMove+i] = NULL;
16092         }
16093
16094         storedGames++;
16095         forwardMostMove = firstMove; // truncate game so we can start variation
16096         if(storedGames == 1) GreyRevert(FALSE);
16097 }
16098
16099 Boolean
16100 PopTail(Boolean annotate)
16101 {
16102         int i, j, nrMoves;
16103         char buf[8000], moveBuf[20];
16104
16105         if(appData.icsActive) return FALSE; // only in local mode
16106         if(!storedGames) return FALSE; // sanity
16107         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16108
16109         storedGames--;
16110         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16111         nrMoves = savedLast[storedGames] - currentMove;
16112         if(annotate) {
16113                 int cnt = 10;
16114                 if(!WhiteOnMove(currentMove))
16115                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16116                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16117                 for(i=currentMove; i<forwardMostMove; i++) {
16118                         if(WhiteOnMove(i))
16119                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16120                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16121                         strcat(buf, moveBuf);
16122                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16123                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16124                 }
16125                 strcat(buf, ")");
16126         }
16127         for(i=1; i<=nrMoves; i++) { // copy last variation back
16128             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16129             for(j=0; j<MOVE_LEN; j++)
16130                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16131             for(j=0; j<2*MOVE_LEN; j++)
16132                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16133             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16134             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16135             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16136             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16137             commentList[currentMove+i] = commentList[framePtr+i];
16138             commentList[framePtr+i] = NULL;
16139         }
16140         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16141         framePtr = savedFramePtr[storedGames];
16142         gameInfo.result = savedResult[storedGames];
16143         if(gameInfo.resultDetails != NULL) {
16144             free(gameInfo.resultDetails);
16145       }
16146         gameInfo.resultDetails = savedDetails[storedGames];
16147         forwardMostMove = currentMove + nrMoves;
16148         if(storedGames == 0) GreyRevert(TRUE);
16149         return TRUE;
16150 }
16151
16152 void
16153 CleanupTail()
16154 {       // remove all shelved variations
16155         int i;
16156         for(i=0; i<storedGames; i++) {
16157             if(savedDetails[i])
16158                 free(savedDetails[i]);
16159             savedDetails[i] = NULL;
16160         }
16161         for(i=framePtr; i<MAX_MOVES; i++) {
16162                 if(commentList[i]) free(commentList[i]);
16163                 commentList[i] = NULL;
16164         }
16165         framePtr = MAX_MOVES-1;
16166         storedGames = 0;
16167 }
16168
16169 void
16170 LoadVariation(int index, char *text)
16171 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16172         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16173         int level = 0, move;
16174
16175         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16176         // first find outermost bracketing variation
16177         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16178             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16179                 if(*p == '{') wait = '}'; else
16180                 if(*p == '[') wait = ']'; else
16181                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16182                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16183             }
16184             if(*p == wait) wait = NULLCHAR; // closing ]} found
16185             p++;
16186         }
16187         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16188         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16189         end[1] = NULLCHAR; // clip off comment beyond variation
16190         ToNrEvent(currentMove-1);
16191         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16192         // kludge: use ParsePV() to append variation to game
16193         move = currentMove;
16194         ParsePV(start, TRUE);
16195         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16196         ClearPremoveHighlights();
16197         CommentPopDown();
16198         ToNrEvent(currentMove+1);
16199 }
16200