cb369090a527ab54a6483f05afe40d41d04d3695
[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 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 /* A point in time */
151 typedef struct {
152     long sec;  /* Assuming this is >= 32 bits */
153     int ms;    /* Assuming this is >= 16 bits */
154 } TimeMark;
155
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158                          char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160                       char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
172                                                                                 Board board));
173 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
174 void ShowMove P((int fromX, int fromY, int toX, int toY));
175 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
176                    /*char*/int promoChar));
177 void BackwardInner P((int target));
178 void ForwardInner P((int target));
179 int Adjudicate P((ChessProgramState *cps));
180 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
181 void EditPositionDone P((Boolean fakeRights));
182 void PrintOpponents P((FILE *fp));
183 void PrintPosition P((FILE *fp, int move));
184 void StartChessProgram P((ChessProgramState *cps));
185 void SendToProgram P((char *message, ChessProgramState *cps));
186 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
187 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
188                            char *buf, int count, int error));
189 void SendTimeControl P((ChessProgramState *cps,
190                         int mps, long tc, int inc, int sd, int st));
191 char *TimeControlTagValue P((void));
192 void Attention P((ChessProgramState *cps));
193 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
194 int ResurrectChessProgram P((void));
195 void DisplayComment P((int moveNumber, char *text));
196 void DisplayMove P((int moveNumber));
197
198 void ParseGameHistory P((char *game));
199 void ParseBoard12 P((char *string));
200 void KeepAlive P((void));
201 void StartClocks P((void));
202 void SwitchClocks P((int nr));
203 void StopClocks P((void));
204 void ResetClocks P((void));
205 char *PGNDate P((void));
206 void SetGameInfo P((void));
207 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
208 int RegisterMove P((void));
209 void MakeRegisteredMove P((void));
210 void TruncateGame P((void));
211 int looking_at P((char *, int *, char *));
212 void CopyPlayerNameIntoFileName P((char **, char *));
213 char *SavePart P((char *));
214 int SaveGameOldStyle P((FILE *));
215 int SaveGamePGN P((FILE *));
216 void GetTimeMark P((TimeMark *));
217 long SubtractTimeMarks P((TimeMark *, TimeMark *));
218 int CheckFlags P((void));
219 long NextTickLength P((long));
220 void CheckTimeControl P((void));
221 void show_bytes P((FILE *, char *, int));
222 int string_to_rating P((char *str));
223 void ParseFeatures P((char* args, ChessProgramState *cps));
224 void InitBackEnd3 P((void));
225 void FeatureDone P((ChessProgramState* cps, int val));
226 void InitChessProgram P((ChessProgramState *cps, int setup));
227 void OutputKibitz(int window, char *text);
228 int PerpetualChase(int first, int last);
229 int EngineOutputIsUp();
230 void InitDrawingSizes(int x, int y);
231 void NextMatchGame P((void));
232 int NextTourneyGame P((int nr, int *swap));
233 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
234 FILE *WriteTourneyFile P((char *results, FILE *f));
235 void DisplayTwoMachinesTitle P(());
236
237 #ifdef WIN32
238        extern void ConsoleCreate();
239 #endif
240
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
244
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
252 Boolean abortMatch;
253
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
257 int endPV = -1;
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
261 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
265 Boolean partnerUp;
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
277 int chattingPartner;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
283
284 /* States for ics_getting_history */
285 #define H_FALSE 0
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
291
292 /* whosays values for GameEnds */
293 #define GE_ICS 0
294 #define GE_ENGINE 1
295 #define GE_PLAYER 2
296 #define GE_FILE 3
297 #define GE_XBOARD 4
298 #define GE_ENGINE1 5
299 #define GE_ENGINE2 6
300
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
303
304 /* Different types of move when calling RegisterMove */
305 #define CMAIL_MOVE   0
306 #define CMAIL_RESIGN 1
307 #define CMAIL_DRAW   2
308 #define CMAIL_ACCEPT 3
309
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
314
315 /* Telnet protocol constants */
316 #define TN_WILL 0373
317 #define TN_WONT 0374
318 #define TN_DO   0375
319 #define TN_DONT 0376
320 #define TN_IAC  0377
321 #define TN_ECHO 0001
322 #define TN_SGA  0003
323 #define TN_PORT 23
324
325 char*
326 safeStrCpy( char *dst, const char *src, size_t count )
327 { // [HGM] made safe
328   int i;
329   assert( dst != NULL );
330   assert( src != NULL );
331   assert( count > 0 );
332
333   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334   if(  i == count && dst[count-1] != NULLCHAR)
335     {
336       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337       if(appData.debugMode)
338       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339     }
340
341   return dst;
342 }
343
344 /* Some compiler can't cast u64 to double
345  * This function do the job for us:
346
347  * We use the highest bit for cast, this only
348  * works if the highest bit is not
349  * in use (This should not happen)
350  *
351  * We used this for all compiler
352  */
353 double
354 u64ToDouble(u64 value)
355 {
356   double r;
357   u64 tmp = value & u64Const(0x7fffffffffffffff);
358   r = (double)(s64)tmp;
359   if (value & u64Const(0x8000000000000000))
360        r +=  9.2233720368547758080e18; /* 2^63 */
361  return r;
362 }
363
364 /* Fake up flags for now, as we aren't keeping track of castling
365    availability yet. [HGM] Change of logic: the flag now only
366    indicates the type of castlings allowed by the rule of the game.
367    The actual rights themselves are maintained in the array
368    castlingRights, as part of the game history, and are not probed
369    by this function.
370  */
371 int
372 PosFlags(index)
373 {
374   int flags = F_ALL_CASTLE_OK;
375   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376   switch (gameInfo.variant) {
377   case VariantSuicide:
378     flags &= ~F_ALL_CASTLE_OK;
379   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380     flags |= F_IGNORE_CHECK;
381   case VariantLosers:
382     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
383     break;
384   case VariantAtomic:
385     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
386     break;
387   case VariantKriegspiel:
388     flags |= F_KRIEGSPIEL_CAPTURE;
389     break;
390   case VariantCapaRandom:
391   case VariantFischeRandom:
392     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393   case VariantNoCastle:
394   case VariantShatranj:
395   case VariantCourier:
396   case VariantMakruk:
397   case VariantGrand:
398     flags &= ~F_ALL_CASTLE_OK;
399     break;
400   default:
401     break;
402   }
403   return flags;
404 }
405
406 FILE *gameFileFP, *debugFP;
407
408 /*
409     [AS] Note: sometimes, the sscanf() function is used to parse the input
410     into a fixed-size buffer. Because of this, we must be prepared to
411     receive strings as long as the size of the input buffer, which is currently
412     set to 4K for Windows and 8K for the rest.
413     So, we must either allocate sufficiently large buffers here, or
414     reduce the size of the input buffer in the input reading part.
415 */
416
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
420
421 ChessProgramState first, second, pairing;
422
423 /* premove variables */
424 int premoveToX = 0;
425 int premoveToY = 0;
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
429 int gotPremove = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
432
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
435
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
463
464 int have_sent_ICS_logon = 0;
465 int movesPerSession;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
476
477 /* animateTraining preserves the state of appData.animate
478  * when Training mode is activated. This allows the
479  * response to be animated when appData.animate == TRUE and
480  * appData.animateDragging == TRUE.
481  */
482 Boolean animateTraining;
483
484 GameInfo gameInfo;
485
486 AppData appData;
487
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char  initialRights[BOARD_FILES];
492 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int   initialRulePlies, FENrulePlies;
494 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int loadFlag = 0;
496 Boolean shuffleOpenings;
497 int mute; // mute all sounds
498
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int storedGames = 0;
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
508
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
514
515 ChessSquare  FIDEArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackBishop, BlackKnight, BlackRook }
520 };
521
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526         BlackKing, BlackKing, BlackKnight, BlackRook }
527 };
528
529 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532     { BlackRook, BlackMan, BlackBishop, BlackQueen,
533         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 };
535
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 };
542
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 };
549
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 };
556
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackMan, BlackFerz,
561         BlackKing, BlackMan, BlackKnight, BlackRook }
562 };
563
564
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 };
572
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 };
579
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 };
586
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 };
593
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
599 };
600
601 ChessSquare GrandArray[2][BOARD_FILES] = {
602     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
603         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
604     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
605         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
606 };
607
608 #ifdef GOTHIC
609 ChessSquare GothicArray[2][BOARD_FILES] = {
610     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
611         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
612     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
613         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
614 };
615 #else // !GOTHIC
616 #define GothicArray CapablancaArray
617 #endif // !GOTHIC
618
619 #ifdef FALCON
620 ChessSquare FalconArray[2][BOARD_FILES] = {
621     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
622         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
623     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
624         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
625 };
626 #else // !FALCON
627 #define FalconArray CapablancaArray
628 #endif // !FALCON
629
630 #else // !(BOARD_FILES>=10)
631 #define XiangqiPosition FIDEArray
632 #define CapablancaArray FIDEArray
633 #define GothicArray FIDEArray
634 #define GreatArray FIDEArray
635 #endif // !(BOARD_FILES>=10)
636
637 #if (BOARD_FILES>=12)
638 ChessSquare CourierArray[2][BOARD_FILES] = {
639     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
640         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
641     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
642         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
643 };
644 #else // !(BOARD_FILES>=12)
645 #define CourierArray CapablancaArray
646 #endif // !(BOARD_FILES>=12)
647
648
649 Board initialPosition;
650
651
652 /* Convert str to a rating. Checks for special cases of "----",
653
654    "++++", etc. Also strips ()'s */
655 int
656 string_to_rating(str)
657   char *str;
658 {
659   while(*str && !isdigit(*str)) ++str;
660   if (!*str)
661     return 0;   /* One of the special "no rating" cases */
662   else
663     return atoi(str);
664 }
665
666 void
667 ClearProgramStats()
668 {
669     /* Init programStats */
670     programStats.movelist[0] = 0;
671     programStats.depth = 0;
672     programStats.nr_moves = 0;
673     programStats.moves_left = 0;
674     programStats.nodes = 0;
675     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
676     programStats.score = 0;
677     programStats.got_only_move = 0;
678     programStats.got_fail = 0;
679     programStats.line_is_book = 0;
680 }
681
682 void
683 CommonEngineInit()
684 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
685     if (appData.firstPlaysBlack) {
686         first.twoMachinesColor = "black\n";
687         second.twoMachinesColor = "white\n";
688     } else {
689         first.twoMachinesColor = "white\n";
690         second.twoMachinesColor = "black\n";
691     }
692
693     first.other = &second;
694     second.other = &first;
695
696     { float norm = 1;
697         if(appData.timeOddsMode) {
698             norm = appData.timeOdds[0];
699             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
700         }
701         first.timeOdds  = appData.timeOdds[0]/norm;
702         second.timeOdds = appData.timeOdds[1]/norm;
703     }
704
705     if(programVersion) free(programVersion);
706     if (appData.noChessProgram) {
707         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
708         sprintf(programVersion, "%s", PACKAGE_STRING);
709     } else {
710       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
711       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
712       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
713     }
714 }
715
716 void
717 UnloadEngine(ChessProgramState *cps)
718 {
719         /* Kill off first chess program */
720         if (cps->isr != NULL)
721           RemoveInputSource(cps->isr);
722         cps->isr = NULL;
723
724         if (cps->pr != NoProc) {
725             ExitAnalyzeMode();
726             DoSleep( appData.delayBeforeQuit );
727             SendToProgram("quit\n", cps);
728             DoSleep( appData.delayAfterQuit );
729             DestroyChildProcess(cps->pr, cps->useSigterm);
730         }
731         cps->pr = NoProc;
732         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
733 }
734
735 void
736 ClearOptions(ChessProgramState *cps)
737 {
738     int i;
739     cps->nrOptions = cps->comboCnt = 0;
740     for(i=0; i<MAX_OPTIONS; i++) {
741         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
742         cps->option[i].textValue = 0;
743     }
744 }
745
746 char *engineNames[] = {
747 "first",
748 "second"
749 };
750
751 void
752 InitEngine(ChessProgramState *cps, int n)
753 {   // [HGM] all engine initialiation put in a function that does one engine
754
755     ClearOptions(cps);
756
757     cps->which = engineNames[n];
758     cps->maybeThinking = FALSE;
759     cps->pr = NoProc;
760     cps->isr = NULL;
761     cps->sendTime = 2;
762     cps->sendDrawOffers = 1;
763
764     cps->program = appData.chessProgram[n];
765     cps->host = appData.host[n];
766     cps->dir = appData.directory[n];
767     cps->initString = appData.engInitString[n];
768     cps->computerString = appData.computerString[n];
769     cps->useSigint  = TRUE;
770     cps->useSigterm = TRUE;
771     cps->reuse = appData.reuse[n];
772     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
773     cps->useSetboard = FALSE;
774     cps->useSAN = FALSE;
775     cps->usePing = FALSE;
776     cps->lastPing = 0;
777     cps->lastPong = 0;
778     cps->usePlayother = FALSE;
779     cps->useColors = TRUE;
780     cps->useUsermove = FALSE;
781     cps->sendICS = FALSE;
782     cps->sendName = appData.icsActive;
783     cps->sdKludge = FALSE;
784     cps->stKludge = FALSE;
785     TidyProgramName(cps->program, cps->host, cps->tidy);
786     cps->matchWins = 0;
787     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
788     cps->analysisSupport = 2; /* detect */
789     cps->analyzing = FALSE;
790     cps->initDone = FALSE;
791
792     /* New features added by Tord: */
793     cps->useFEN960 = FALSE;
794     cps->useOOCastle = TRUE;
795     /* End of new features added by Tord. */
796     cps->fenOverride  = appData.fenOverride[n];
797
798     /* [HGM] time odds: set factor for each machine */
799     cps->timeOdds  = appData.timeOdds[n];
800
801     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
802     cps->accumulateTC = appData.accumulateTC[n];
803     cps->maxNrOfSessions = 1;
804
805     /* [HGM] debug */
806     cps->debug = FALSE;
807
808     cps->supportsNPS = UNKNOWN;
809     cps->memSize = FALSE;
810     cps->maxCores = FALSE;
811     cps->egtFormats[0] = NULLCHAR;
812
813     /* [HGM] options */
814     cps->optionSettings  = appData.engOptions[n];
815
816     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
817     cps->isUCI = appData.isUCI[n]; /* [AS] */
818     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
819
820     if (appData.protocolVersion[n] > PROTOVER
821         || appData.protocolVersion[n] < 1)
822       {
823         char buf[MSG_SIZ];
824         int len;
825
826         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
827                        appData.protocolVersion[n]);
828         if( (len > MSG_SIZ) && appData.debugMode )
829           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
830
831         DisplayFatalError(buf, 0, 2);
832       }
833     else
834       {
835         cps->protocolVersion = appData.protocolVersion[n];
836       }
837
838     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
839 }
840
841 ChessProgramState *savCps;
842
843 void
844 LoadEngine()
845 {
846     int i;
847     if(WaitForEngine(savCps, LoadEngine)) return;
848     CommonEngineInit(); // recalculate time odds
849     if(gameInfo.variant != StringToVariant(appData.variant)) {
850         // we changed variant when loading the engine; this forces us to reset
851         Reset(TRUE, savCps != &first);
852         EditGameEvent(); // for consistency with other path, as Reset changes mode
853     }
854     InitChessProgram(savCps, FALSE);
855     SendToProgram("force\n", savCps);
856     DisplayMessage("", "");
857     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
859     ThawUI();
860     SetGNUMode();
861 }
862
863 void
864 ReplaceEngine(ChessProgramState *cps, int n)
865 {
866     EditGameEvent();
867     UnloadEngine(cps);
868     appData.noChessProgram = FALSE;
869     appData.clockMode = TRUE;
870     InitEngine(cps, n);
871     UpdateLogos(TRUE);
872     if(n) return; // only startup first engine immediately; second can wait
873     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
874     LoadEngine();
875 }
876
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
879
880 static char resetOptions[] = 
881         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
883
884 void
885 Load(ChessProgramState *cps, int i)
886 {
887     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
888     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
889         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
890         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
891         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
892         ParseArgsFromString(buf);
893         SwapEngines(i);
894         ReplaceEngine(cps, i);
895         return;
896     }
897     p = engineName;
898     while(q = strchr(p, SLASH)) p = q+1;
899     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
900     if(engineDir[0] != NULLCHAR)
901         appData.directory[i] = engineDir;
902     else if(p != engineName) { // derive directory from engine path, when not given
903         p[-1] = 0;
904         appData.directory[i] = strdup(engineName);
905         p[-1] = SLASH;
906     } else appData.directory[i] = ".";
907     if(params[0]) {
908         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
909         snprintf(command, MSG_SIZ, "%s %s", p, params);
910         p = command;
911     }
912     appData.chessProgram[i] = strdup(p);
913     appData.isUCI[i] = isUCI;
914     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
915     appData.hasOwnBookUCI[i] = hasBook;
916     if(!nickName[0]) useNick = FALSE;
917     if(useNick) ASSIGN(appData.pgnName[i], nickName);
918     if(addToList) {
919         int len;
920         char quote;
921         q = firstChessProgramNames;
922         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
923         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
924         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
925                         quote, p, quote, appData.directory[i], 
926                         useNick ? " -fn \"" : "",
927                         useNick ? nickName : "",
928                         useNick ? "\"" : "",
929                         v1 ? " -firstProtocolVersion 1" : "",
930                         hasBook ? "" : " -fNoOwnBookUCI",
931                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
932                         storeVariant ? " -variant " : "",
933                         storeVariant ? VariantName(gameInfo.variant) : "");
934         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
935         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
936         if(q)   free(q);
937     }
938     ReplaceEngine(cps, i);
939 }
940
941 void
942 InitTimeControls()
943 {
944     int matched, min, sec;
945     /*
946      * Parse timeControl resource
947      */
948     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
949                           appData.movesPerSession)) {
950         char buf[MSG_SIZ];
951         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
952         DisplayFatalError(buf, 0, 2);
953     }
954
955     /*
956      * Parse searchTime resource
957      */
958     if (*appData.searchTime != NULLCHAR) {
959         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
960         if (matched == 1) {
961             searchTime = min * 60;
962         } else if (matched == 2) {
963             searchTime = min * 60 + sec;
964         } else {
965             char buf[MSG_SIZ];
966             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
967             DisplayFatalError(buf, 0, 2);
968         }
969     }
970 }
971
972 void
973 InitBackEnd1()
974 {
975
976     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
977     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
978
979     GetTimeMark(&programStartTime);
980     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
981     appData.seedBase = random() + (random()<<15);
982     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
983
984     ClearProgramStats();
985     programStats.ok_to_send = 1;
986     programStats.seen_stat = 0;
987
988     /*
989      * Initialize game list
990      */
991     ListNew(&gameList);
992
993
994     /*
995      * Internet chess server status
996      */
997     if (appData.icsActive) {
998         appData.matchMode = FALSE;
999         appData.matchGames = 0;
1000 #if ZIPPY
1001         appData.noChessProgram = !appData.zippyPlay;
1002 #else
1003         appData.zippyPlay = FALSE;
1004         appData.zippyTalk = FALSE;
1005         appData.noChessProgram = TRUE;
1006 #endif
1007         if (*appData.icsHelper != NULLCHAR) {
1008             appData.useTelnet = TRUE;
1009             appData.telnetProgram = appData.icsHelper;
1010         }
1011     } else {
1012         appData.zippyTalk = appData.zippyPlay = FALSE;
1013     }
1014
1015     /* [AS] Initialize pv info list [HGM] and game state */
1016     {
1017         int i, j;
1018
1019         for( i=0; i<=framePtr; i++ ) {
1020             pvInfoList[i].depth = -1;
1021             boards[i][EP_STATUS] = EP_NONE;
1022             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1023         }
1024     }
1025
1026     InitTimeControls();
1027
1028     /* [AS] Adjudication threshold */
1029     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1030
1031     InitEngine(&first, 0);
1032     InitEngine(&second, 1);
1033     CommonEngineInit();
1034
1035     pairing.which = "pairing"; // pairing engine
1036     pairing.pr = NoProc;
1037     pairing.isr = NULL;
1038     pairing.program = appData.pairingEngine;
1039     pairing.host = "localhost";
1040     pairing.dir = ".";
1041
1042     if (appData.icsActive) {
1043         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1044     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1045         appData.clockMode = FALSE;
1046         first.sendTime = second.sendTime = 0;
1047     }
1048
1049 #if ZIPPY
1050     /* Override some settings from environment variables, for backward
1051        compatibility.  Unfortunately it's not feasible to have the env
1052        vars just set defaults, at least in xboard.  Ugh.
1053     */
1054     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1055       ZippyInit();
1056     }
1057 #endif
1058
1059     if (!appData.icsActive) {
1060       char buf[MSG_SIZ];
1061       int len;
1062
1063       /* Check for variants that are supported only in ICS mode,
1064          or not at all.  Some that are accepted here nevertheless
1065          have bugs; see comments below.
1066       */
1067       VariantClass variant = StringToVariant(appData.variant);
1068       switch (variant) {
1069       case VariantBughouse:     /* need four players and two boards */
1070       case VariantKriegspiel:   /* need to hide pieces and move details */
1071         /* case VariantFischeRandom: (Fabien: moved below) */
1072         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1073         if( (len > MSG_SIZ) && appData.debugMode )
1074           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1075
1076         DisplayFatalError(buf, 0, 2);
1077         return;
1078
1079       case VariantUnknown:
1080       case VariantLoadable:
1081       case Variant29:
1082       case Variant30:
1083       case Variant31:
1084       case Variant32:
1085       case Variant33:
1086       case Variant34:
1087       case Variant35:
1088       case Variant36:
1089       default:
1090         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1091         if( (len > MSG_SIZ) && appData.debugMode )
1092           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1093
1094         DisplayFatalError(buf, 0, 2);
1095         return;
1096
1097       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1098       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1099       case VariantGothic:     /* [HGM] should work */
1100       case VariantCapablanca: /* [HGM] should work */
1101       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1102       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1103       case VariantKnightmate: /* [HGM] should work */
1104       case VariantCylinder:   /* [HGM] untested */
1105       case VariantFalcon:     /* [HGM] untested */
1106       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1107                                  offboard interposition not understood */
1108       case VariantNormal:     /* definitely works! */
1109       case VariantWildCastle: /* pieces not automatically shuffled */
1110       case VariantNoCastle:   /* pieces not automatically shuffled */
1111       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1112       case VariantLosers:     /* should work except for win condition,
1113                                  and doesn't know captures are mandatory */
1114       case VariantSuicide:    /* should work except for win condition,
1115                                  and doesn't know captures are mandatory */
1116       case VariantGiveaway:   /* should work except for win condition,
1117                                  and doesn't know captures are mandatory */
1118       case VariantTwoKings:   /* should work */
1119       case VariantAtomic:     /* should work except for win condition */
1120       case Variant3Check:     /* should work except for win condition */
1121       case VariantShatranj:   /* should work except for all win conditions */
1122       case VariantMakruk:     /* should work except for draw countdown */
1123       case VariantBerolina:   /* might work if TestLegality is off */
1124       case VariantCapaRandom: /* should work */
1125       case VariantJanus:      /* should work */
1126       case VariantSuper:      /* experimental */
1127       case VariantGreat:      /* experimental, requires legality testing to be off */
1128       case VariantSChess:     /* S-Chess, should work */
1129       case VariantGrand:      /* should work */
1130       case VariantSpartan:    /* should work */
1131         break;
1132       }
1133     }
1134
1135 }
1136
1137 int NextIntegerFromString( char ** str, long * value )
1138 {
1139     int result = -1;
1140     char * s = *str;
1141
1142     while( *s == ' ' || *s == '\t' ) {
1143         s++;
1144     }
1145
1146     *value = 0;
1147
1148     if( *s >= '0' && *s <= '9' ) {
1149         while( *s >= '0' && *s <= '9' ) {
1150             *value = *value * 10 + (*s - '0');
1151             s++;
1152         }
1153
1154         result = 0;
1155     }
1156
1157     *str = s;
1158
1159     return result;
1160 }
1161
1162 int NextTimeControlFromString( char ** str, long * value )
1163 {
1164     long temp;
1165     int result = NextIntegerFromString( str, &temp );
1166
1167     if( result == 0 ) {
1168         *value = temp * 60; /* Minutes */
1169         if( **str == ':' ) {
1170             (*str)++;
1171             result = NextIntegerFromString( str, &temp );
1172             *value += temp; /* Seconds */
1173         }
1174     }
1175
1176     return result;
1177 }
1178
1179 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1180 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1181     int result = -1, type = 0; long temp, temp2;
1182
1183     if(**str != ':') return -1; // old params remain in force!
1184     (*str)++;
1185     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1186     if( NextIntegerFromString( str, &temp ) ) return -1;
1187     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1188
1189     if(**str != '/') {
1190         /* time only: incremental or sudden-death time control */
1191         if(**str == '+') { /* increment follows; read it */
1192             (*str)++;
1193             if(**str == '!') type = *(*str)++; // Bronstein TC
1194             if(result = NextIntegerFromString( str, &temp2)) return -1;
1195             *inc = temp2 * 1000;
1196             if(**str == '.') { // read fraction of increment
1197                 char *start = ++(*str);
1198                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1199                 temp2 *= 1000;
1200                 while(start++ < *str) temp2 /= 10;
1201                 *inc += temp2;
1202             }
1203         } else *inc = 0;
1204         *moves = 0; *tc = temp * 1000; *incType = type;
1205         return 0;
1206     }
1207
1208     (*str)++; /* classical time control */
1209     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1210
1211     if(result == 0) {
1212         *moves = temp;
1213         *tc    = temp2 * 1000;
1214         *inc   = 0;
1215         *incType = type;
1216     }
1217     return result;
1218 }
1219
1220 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1221 {   /* [HGM] get time to add from the multi-session time-control string */
1222     int incType, moves=1; /* kludge to force reading of first session */
1223     long time, increment;
1224     char *s = tcString;
1225
1226     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1227     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1228     do {
1229         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1230         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1231         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1232         if(movenr == -1) return time;    /* last move before new session     */
1233         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1234         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1235         if(!moves) return increment;     /* current session is incremental   */
1236         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1237     } while(movenr >= -1);               /* try again for next session       */
1238
1239     return 0; // no new time quota on this move
1240 }
1241
1242 int
1243 ParseTimeControl(tc, ti, mps)
1244      char *tc;
1245      float ti;
1246      int mps;
1247 {
1248   long tc1;
1249   long tc2;
1250   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1251   int min, sec=0;
1252
1253   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1254   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1255       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1256   if(ti > 0) {
1257
1258     if(mps)
1259       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1260     else 
1261       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1262   } else {
1263     if(mps)
1264       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1265     else 
1266       snprintf(buf, MSG_SIZ, ":%s", mytc);
1267   }
1268   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1269   
1270   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1271     return FALSE;
1272   }
1273
1274   if( *tc == '/' ) {
1275     /* Parse second time control */
1276     tc++;
1277
1278     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1279       return FALSE;
1280     }
1281
1282     if( tc2 == 0 ) {
1283       return FALSE;
1284     }
1285
1286     timeControl_2 = tc2 * 1000;
1287   }
1288   else {
1289     timeControl_2 = 0;
1290   }
1291
1292   if( tc1 == 0 ) {
1293     return FALSE;
1294   }
1295
1296   timeControl = tc1 * 1000;
1297
1298   if (ti >= 0) {
1299     timeIncrement = ti * 1000;  /* convert to ms */
1300     movesPerSession = 0;
1301   } else {
1302     timeIncrement = 0;
1303     movesPerSession = mps;
1304   }
1305   return TRUE;
1306 }
1307
1308 void
1309 InitBackEnd2()
1310 {
1311     if (appData.debugMode) {
1312         fprintf(debugFP, "%s\n", programVersion);
1313     }
1314
1315     set_cont_sequence(appData.wrapContSeq);
1316     if (appData.matchGames > 0) {
1317         appData.matchMode = TRUE;
1318     } else if (appData.matchMode) {
1319         appData.matchGames = 1;
1320     }
1321     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1322         appData.matchGames = appData.sameColorGames;
1323     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1324         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1325         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1326     }
1327     Reset(TRUE, FALSE);
1328     if (appData.noChessProgram || first.protocolVersion == 1) {
1329       InitBackEnd3();
1330     } else {
1331       /* kludge: allow timeout for initial "feature" commands */
1332       FreezeUI();
1333       DisplayMessage("", _("Starting chess program"));
1334       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1335     }
1336 }
1337
1338 int
1339 CalculateIndex(int index, int gameNr)
1340 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1341     int res;
1342     if(index > 0) return index; // fixed nmber
1343     if(index == 0) return 1;
1344     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1345     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1346     return res;
1347 }
1348
1349 int
1350 LoadGameOrPosition(int gameNr)
1351 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1352     if (*appData.loadGameFile != NULLCHAR) {
1353         if (!LoadGameFromFile(appData.loadGameFile,
1354                 CalculateIndex(appData.loadGameIndex, gameNr),
1355                               appData.loadGameFile, FALSE)) {
1356             DisplayFatalError(_("Bad game file"), 0, 1);
1357             return 0;
1358         }
1359     } else if (*appData.loadPositionFile != NULLCHAR) {
1360         if (!LoadPositionFromFile(appData.loadPositionFile,
1361                 CalculateIndex(appData.loadPositionIndex, gameNr),
1362                                   appData.loadPositionFile)) {
1363             DisplayFatalError(_("Bad position file"), 0, 1);
1364             return 0;
1365         }
1366     }
1367     return 1;
1368 }
1369
1370 void
1371 ReserveGame(int gameNr, char resChar)
1372 {
1373     FILE *tf = fopen(appData.tourneyFile, "r+");
1374     char *p, *q, c, buf[MSG_SIZ];
1375     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1376     safeStrCpy(buf, lastMsg, MSG_SIZ);
1377     DisplayMessage(_("Pick new game"), "");
1378     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1379     ParseArgsFromFile(tf);
1380     p = q = appData.results;
1381     if(appData.debugMode) {
1382       char *r = appData.participants;
1383       fprintf(debugFP, "results = '%s'\n", p);
1384       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1385       fprintf(debugFP, "\n");
1386     }
1387     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1388     nextGame = q - p;
1389     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1390     safeStrCpy(q, p, strlen(p) + 2);
1391     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1392     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1393     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1394         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1395         q[nextGame] = '*';
1396     }
1397     fseek(tf, -(strlen(p)+4), SEEK_END);
1398     c = fgetc(tf);
1399     if(c != '"') // depending on DOS or Unix line endings we can be one off
1400          fseek(tf, -(strlen(p)+2), SEEK_END);
1401     else fseek(tf, -(strlen(p)+3), SEEK_END);
1402     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1403     DisplayMessage(buf, "");
1404     free(p); appData.results = q;
1405     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1406        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1407         UnloadEngine(&first);  // next game belongs to other pairing;
1408         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1409     }
1410 }
1411
1412 void
1413 MatchEvent(int mode)
1414 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1415         int dummy;
1416         if(matchMode) { // already in match mode: switch it off
1417             abortMatch = TRUE;
1418             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1419             return;
1420         }
1421 //      if(gameMode != BeginningOfGame) {
1422 //          DisplayError(_("You can only start a match from the initial position."), 0);
1423 //          return;
1424 //      }
1425         abortMatch = FALSE;
1426         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1427         /* Set up machine vs. machine match */
1428         nextGame = 0;
1429         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1430         if(appData.tourneyFile[0]) {
1431             ReserveGame(-1, 0);
1432             if(nextGame > appData.matchGames) {
1433                 char buf[MSG_SIZ];
1434                 if(strchr(appData.results, '*') == NULL) {
1435                     FILE *f;
1436                     appData.tourneyCycles++;
1437                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1438                         fclose(f);
1439                         NextTourneyGame(-1, &dummy);
1440                         ReserveGame(-1, 0);
1441                         if(nextGame <= appData.matchGames) {
1442                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1443                             matchMode = mode;
1444                             ScheduleDelayedEvent(NextMatchGame, 10000);
1445                             return;
1446                         }
1447                     }
1448                 }
1449                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1450                 DisplayError(buf, 0);
1451                 appData.tourneyFile[0] = 0;
1452                 return;
1453             }
1454         } else
1455         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1456             DisplayFatalError(_("Can't have a match with no chess programs"),
1457                               0, 2);
1458             return;
1459         }
1460         matchMode = mode;
1461         matchGame = roundNr = 1;
1462         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1463         NextMatchGame();
1464 }
1465
1466 void
1467 InitBackEnd3 P((void))
1468 {
1469     GameMode initialMode;
1470     char buf[MSG_SIZ];
1471     int err, len;
1472
1473     InitChessProgram(&first, startedFromSetupPosition);
1474
1475     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1476         free(programVersion);
1477         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1478         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1479     }
1480
1481     if (appData.icsActive) {
1482 #ifdef WIN32
1483         /* [DM] Make a console window if needed [HGM] merged ifs */
1484         ConsoleCreate();
1485 #endif
1486         err = establish();
1487         if (err != 0)
1488           {
1489             if (*appData.icsCommPort != NULLCHAR)
1490               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1491                              appData.icsCommPort);
1492             else
1493               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1494                         appData.icsHost, appData.icsPort);
1495
1496             if( (len > MSG_SIZ) && appData.debugMode )
1497               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1498
1499             DisplayFatalError(buf, err, 1);
1500             return;
1501         }
1502         SetICSMode();
1503         telnetISR =
1504           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1505         fromUserISR =
1506           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1507         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1508             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1509     } else if (appData.noChessProgram) {
1510         SetNCPMode();
1511     } else {
1512         SetGNUMode();
1513     }
1514
1515     if (*appData.cmailGameName != NULLCHAR) {
1516         SetCmailMode();
1517         OpenLoopback(&cmailPR);
1518         cmailISR =
1519           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1520     }
1521
1522     ThawUI();
1523     DisplayMessage("", "");
1524     if (StrCaseCmp(appData.initialMode, "") == 0) {
1525       initialMode = BeginningOfGame;
1526       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1527         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1528         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1529         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1530         ModeHighlight();
1531       }
1532     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1533       initialMode = TwoMachinesPlay;
1534     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1535       initialMode = AnalyzeFile;
1536     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1537       initialMode = AnalyzeMode;
1538     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1539       initialMode = MachinePlaysWhite;
1540     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1541       initialMode = MachinePlaysBlack;
1542     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1543       initialMode = EditGame;
1544     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1545       initialMode = EditPosition;
1546     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1547       initialMode = Training;
1548     } else {
1549       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1550       if( (len > MSG_SIZ) && appData.debugMode )
1551         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1552
1553       DisplayFatalError(buf, 0, 2);
1554       return;
1555     }
1556
1557     if (appData.matchMode) {
1558         if(appData.tourneyFile[0]) { // start tourney from command line
1559             FILE *f;
1560             if(f = fopen(appData.tourneyFile, "r")) {
1561                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1562                 fclose(f);
1563                 appData.clockMode = TRUE;
1564                 SetGNUMode();
1565             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1566         }
1567         MatchEvent(TRUE);
1568     } else if (*appData.cmailGameName != NULLCHAR) {
1569         /* Set up cmail mode */
1570         ReloadCmailMsgEvent(TRUE);
1571     } else {
1572         /* Set up other modes */
1573         if (initialMode == AnalyzeFile) {
1574           if (*appData.loadGameFile == NULLCHAR) {
1575             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1576             return;
1577           }
1578         }
1579         if (*appData.loadGameFile != NULLCHAR) {
1580             (void) LoadGameFromFile(appData.loadGameFile,
1581                                     appData.loadGameIndex,
1582                                     appData.loadGameFile, TRUE);
1583         } else if (*appData.loadPositionFile != NULLCHAR) {
1584             (void) LoadPositionFromFile(appData.loadPositionFile,
1585                                         appData.loadPositionIndex,
1586                                         appData.loadPositionFile);
1587             /* [HGM] try to make self-starting even after FEN load */
1588             /* to allow automatic setup of fairy variants with wtm */
1589             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1590                 gameMode = BeginningOfGame;
1591                 setboardSpoiledMachineBlack = 1;
1592             }
1593             /* [HGM] loadPos: make that every new game uses the setup */
1594             /* from file as long as we do not switch variant          */
1595             if(!blackPlaysFirst) {
1596                 startedFromPositionFile = TRUE;
1597                 CopyBoard(filePosition, boards[0]);
1598             }
1599         }
1600         if (initialMode == AnalyzeMode) {
1601           if (appData.noChessProgram) {
1602             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1603             return;
1604           }
1605           if (appData.icsActive) {
1606             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1607             return;
1608           }
1609           AnalyzeModeEvent();
1610         } else if (initialMode == AnalyzeFile) {
1611           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1612           ShowThinkingEvent();
1613           AnalyzeFileEvent();
1614           AnalysisPeriodicEvent(1);
1615         } else if (initialMode == MachinePlaysWhite) {
1616           if (appData.noChessProgram) {
1617             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1618                               0, 2);
1619             return;
1620           }
1621           if (appData.icsActive) {
1622             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1623                               0, 2);
1624             return;
1625           }
1626           MachineWhiteEvent();
1627         } else if (initialMode == MachinePlaysBlack) {
1628           if (appData.noChessProgram) {
1629             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1630                               0, 2);
1631             return;
1632           }
1633           if (appData.icsActive) {
1634             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1635                               0, 2);
1636             return;
1637           }
1638           MachineBlackEvent();
1639         } else if (initialMode == TwoMachinesPlay) {
1640           if (appData.noChessProgram) {
1641             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1642                               0, 2);
1643             return;
1644           }
1645           if (appData.icsActive) {
1646             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1647                               0, 2);
1648             return;
1649           }
1650           TwoMachinesEvent();
1651         } else if (initialMode == EditGame) {
1652           EditGameEvent();
1653         } else if (initialMode == EditPosition) {
1654           EditPositionEvent();
1655         } else if (initialMode == Training) {
1656           if (*appData.loadGameFile == NULLCHAR) {
1657             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1658             return;
1659           }
1660           TrainingEvent();
1661         }
1662     }
1663 }
1664
1665 /*
1666  * Establish will establish a contact to a remote host.port.
1667  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1668  *  used to talk to the host.
1669  * Returns 0 if okay, error code if not.
1670  */
1671 int
1672 establish()
1673 {
1674     char buf[MSG_SIZ];
1675
1676     if (*appData.icsCommPort != NULLCHAR) {
1677         /* Talk to the host through a serial comm port */
1678         return OpenCommPort(appData.icsCommPort, &icsPR);
1679
1680     } else if (*appData.gateway != NULLCHAR) {
1681         if (*appData.remoteShell == NULLCHAR) {
1682             /* Use the rcmd protocol to run telnet program on a gateway host */
1683             snprintf(buf, sizeof(buf), "%s %s %s",
1684                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1685             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1686
1687         } else {
1688             /* Use the rsh program to run telnet program on a gateway host */
1689             if (*appData.remoteUser == NULLCHAR) {
1690                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1691                         appData.gateway, appData.telnetProgram,
1692                         appData.icsHost, appData.icsPort);
1693             } else {
1694                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1695                         appData.remoteShell, appData.gateway,
1696                         appData.remoteUser, appData.telnetProgram,
1697                         appData.icsHost, appData.icsPort);
1698             }
1699             return StartChildProcess(buf, "", &icsPR);
1700
1701         }
1702     } else if (appData.useTelnet) {
1703         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1704
1705     } else {
1706         /* TCP socket interface differs somewhat between
1707            Unix and NT; handle details in the front end.
1708            */
1709         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1710     }
1711 }
1712
1713 void EscapeExpand(char *p, char *q)
1714 {       // [HGM] initstring: routine to shape up string arguments
1715         while(*p++ = *q++) if(p[-1] == '\\')
1716             switch(*q++) {
1717                 case 'n': p[-1] = '\n'; break;
1718                 case 'r': p[-1] = '\r'; break;
1719                 case 't': p[-1] = '\t'; break;
1720                 case '\\': p[-1] = '\\'; break;
1721                 case 0: *p = 0; return;
1722                 default: p[-1] = q[-1]; break;
1723             }
1724 }
1725
1726 void
1727 show_bytes(fp, buf, count)
1728      FILE *fp;
1729      char *buf;
1730      int count;
1731 {
1732     while (count--) {
1733         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1734             fprintf(fp, "\\%03o", *buf & 0xff);
1735         } else {
1736             putc(*buf, fp);
1737         }
1738         buf++;
1739     }
1740     fflush(fp);
1741 }
1742
1743 /* Returns an errno value */
1744 int
1745 OutputMaybeTelnet(pr, message, count, outError)
1746      ProcRef pr;
1747      char *message;
1748      int count;
1749      int *outError;
1750 {
1751     char buf[8192], *p, *q, *buflim;
1752     int left, newcount, outcount;
1753
1754     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1755         *appData.gateway != NULLCHAR) {
1756         if (appData.debugMode) {
1757             fprintf(debugFP, ">ICS: ");
1758             show_bytes(debugFP, message, count);
1759             fprintf(debugFP, "\n");
1760         }
1761         return OutputToProcess(pr, message, count, outError);
1762     }
1763
1764     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1765     p = message;
1766     q = buf;
1767     left = count;
1768     newcount = 0;
1769     while (left) {
1770         if (q >= buflim) {
1771             if (appData.debugMode) {
1772                 fprintf(debugFP, ">ICS: ");
1773                 show_bytes(debugFP, buf, newcount);
1774                 fprintf(debugFP, "\n");
1775             }
1776             outcount = OutputToProcess(pr, buf, newcount, outError);
1777             if (outcount < newcount) return -1; /* to be sure */
1778             q = buf;
1779             newcount = 0;
1780         }
1781         if (*p == '\n') {
1782             *q++ = '\r';
1783             newcount++;
1784         } else if (((unsigned char) *p) == TN_IAC) {
1785             *q++ = (char) TN_IAC;
1786             newcount ++;
1787         }
1788         *q++ = *p++;
1789         newcount++;
1790         left--;
1791     }
1792     if (appData.debugMode) {
1793         fprintf(debugFP, ">ICS: ");
1794         show_bytes(debugFP, buf, newcount);
1795         fprintf(debugFP, "\n");
1796     }
1797     outcount = OutputToProcess(pr, buf, newcount, outError);
1798     if (outcount < newcount) return -1; /* to be sure */
1799     return count;
1800 }
1801
1802 void
1803 read_from_player(isr, closure, message, count, error)
1804      InputSourceRef isr;
1805      VOIDSTAR closure;
1806      char *message;
1807      int count;
1808      int error;
1809 {
1810     int outError, outCount;
1811     static int gotEof = 0;
1812
1813     /* Pass data read from player on to ICS */
1814     if (count > 0) {
1815         gotEof = 0;
1816         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1817         if (outCount < count) {
1818             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1819         }
1820     } else if (count < 0) {
1821         RemoveInputSource(isr);
1822         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1823     } else if (gotEof++ > 0) {
1824         RemoveInputSource(isr);
1825         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1826     }
1827 }
1828
1829 void
1830 KeepAlive()
1831 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1832     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1833     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1834     SendToICS("date\n");
1835     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1836 }
1837
1838 /* added routine for printf style output to ics */
1839 void ics_printf(char *format, ...)
1840 {
1841     char buffer[MSG_SIZ];
1842     va_list args;
1843
1844     va_start(args, format);
1845     vsnprintf(buffer, sizeof(buffer), format, args);
1846     buffer[sizeof(buffer)-1] = '\0';
1847     SendToICS(buffer);
1848     va_end(args);
1849 }
1850
1851 void
1852 SendToICS(s)
1853      char *s;
1854 {
1855     int count, outCount, outError;
1856
1857     if (icsPR == NULL) return;
1858
1859     count = strlen(s);
1860     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1861     if (outCount < count) {
1862         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1863     }
1864 }
1865
1866 /* This is used for sending logon scripts to the ICS. Sending
1867    without a delay causes problems when using timestamp on ICC
1868    (at least on my machine). */
1869 void
1870 SendToICSDelayed(s,msdelay)
1871      char *s;
1872      long msdelay;
1873 {
1874     int count, outCount, outError;
1875
1876     if (icsPR == NULL) return;
1877
1878     count = strlen(s);
1879     if (appData.debugMode) {
1880         fprintf(debugFP, ">ICS: ");
1881         show_bytes(debugFP, s, count);
1882         fprintf(debugFP, "\n");
1883     }
1884     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1885                                       msdelay);
1886     if (outCount < count) {
1887         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1888     }
1889 }
1890
1891
1892 /* Remove all highlighting escape sequences in s
1893    Also deletes any suffix starting with '('
1894    */
1895 char *
1896 StripHighlightAndTitle(s)
1897      char *s;
1898 {
1899     static char retbuf[MSG_SIZ];
1900     char *p = retbuf;
1901
1902     while (*s != NULLCHAR) {
1903         while (*s == '\033') {
1904             while (*s != NULLCHAR && !isalpha(*s)) s++;
1905             if (*s != NULLCHAR) s++;
1906         }
1907         while (*s != NULLCHAR && *s != '\033') {
1908             if (*s == '(' || *s == '[') {
1909                 *p = NULLCHAR;
1910                 return retbuf;
1911             }
1912             *p++ = *s++;
1913         }
1914     }
1915     *p = NULLCHAR;
1916     return retbuf;
1917 }
1918
1919 /* Remove all highlighting escape sequences in s */
1920 char *
1921 StripHighlight(s)
1922      char *s;
1923 {
1924     static char retbuf[MSG_SIZ];
1925     char *p = retbuf;
1926
1927     while (*s != NULLCHAR) {
1928         while (*s == '\033') {
1929             while (*s != NULLCHAR && !isalpha(*s)) s++;
1930             if (*s != NULLCHAR) s++;
1931         }
1932         while (*s != NULLCHAR && *s != '\033') {
1933             *p++ = *s++;
1934         }
1935     }
1936     *p = NULLCHAR;
1937     return retbuf;
1938 }
1939
1940 char *variantNames[] = VARIANT_NAMES;
1941 char *
1942 VariantName(v)
1943      VariantClass v;
1944 {
1945     return variantNames[v];
1946 }
1947
1948
1949 /* Identify a variant from the strings the chess servers use or the
1950    PGN Variant tag names we use. */
1951 VariantClass
1952 StringToVariant(e)
1953      char *e;
1954 {
1955     char *p;
1956     int wnum = -1;
1957     VariantClass v = VariantNormal;
1958     int i, found = FALSE;
1959     char buf[MSG_SIZ];
1960     int len;
1961
1962     if (!e) return v;
1963
1964     /* [HGM] skip over optional board-size prefixes */
1965     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1966         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1967         while( *e++ != '_');
1968     }
1969
1970     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1971         v = VariantNormal;
1972         found = TRUE;
1973     } else
1974     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1975       if (StrCaseStr(e, variantNames[i])) {
1976         v = (VariantClass) i;
1977         found = TRUE;
1978         break;
1979       }
1980     }
1981
1982     if (!found) {
1983       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1984           || StrCaseStr(e, "wild/fr")
1985           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1986         v = VariantFischeRandom;
1987       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1988                  (i = 1, p = StrCaseStr(e, "w"))) {
1989         p += i;
1990         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1991         if (isdigit(*p)) {
1992           wnum = atoi(p);
1993         } else {
1994           wnum = -1;
1995         }
1996         switch (wnum) {
1997         case 0: /* FICS only, actually */
1998         case 1:
1999           /* Castling legal even if K starts on d-file */
2000           v = VariantWildCastle;
2001           break;
2002         case 2:
2003         case 3:
2004         case 4:
2005           /* Castling illegal even if K & R happen to start in
2006              normal positions. */
2007           v = VariantNoCastle;
2008           break;
2009         case 5:
2010         case 7:
2011         case 8:
2012         case 10:
2013         case 11:
2014         case 12:
2015         case 13:
2016         case 14:
2017         case 15:
2018         case 18:
2019         case 19:
2020           /* Castling legal iff K & R start in normal positions */
2021           v = VariantNormal;
2022           break;
2023         case 6:
2024         case 20:
2025         case 21:
2026           /* Special wilds for position setup; unclear what to do here */
2027           v = VariantLoadable;
2028           break;
2029         case 9:
2030           /* Bizarre ICC game */
2031           v = VariantTwoKings;
2032           break;
2033         case 16:
2034           v = VariantKriegspiel;
2035           break;
2036         case 17:
2037           v = VariantLosers;
2038           break;
2039         case 22:
2040           v = VariantFischeRandom;
2041           break;
2042         case 23:
2043           v = VariantCrazyhouse;
2044           break;
2045         case 24:
2046           v = VariantBughouse;
2047           break;
2048         case 25:
2049           v = Variant3Check;
2050           break;
2051         case 26:
2052           /* Not quite the same as FICS suicide! */
2053           v = VariantGiveaway;
2054           break;
2055         case 27:
2056           v = VariantAtomic;
2057           break;
2058         case 28:
2059           v = VariantShatranj;
2060           break;
2061
2062         /* Temporary names for future ICC types.  The name *will* change in
2063            the next xboard/WinBoard release after ICC defines it. */
2064         case 29:
2065           v = Variant29;
2066           break;
2067         case 30:
2068           v = Variant30;
2069           break;
2070         case 31:
2071           v = Variant31;
2072           break;
2073         case 32:
2074           v = Variant32;
2075           break;
2076         case 33:
2077           v = Variant33;
2078           break;
2079         case 34:
2080           v = Variant34;
2081           break;
2082         case 35:
2083           v = Variant35;
2084           break;
2085         case 36:
2086           v = Variant36;
2087           break;
2088         case 37:
2089           v = VariantShogi;
2090           break;
2091         case 38:
2092           v = VariantXiangqi;
2093           break;
2094         case 39:
2095           v = VariantCourier;
2096           break;
2097         case 40:
2098           v = VariantGothic;
2099           break;
2100         case 41:
2101           v = VariantCapablanca;
2102           break;
2103         case 42:
2104           v = VariantKnightmate;
2105           break;
2106         case 43:
2107           v = VariantFairy;
2108           break;
2109         case 44:
2110           v = VariantCylinder;
2111           break;
2112         case 45:
2113           v = VariantFalcon;
2114           break;
2115         case 46:
2116           v = VariantCapaRandom;
2117           break;
2118         case 47:
2119           v = VariantBerolina;
2120           break;
2121         case 48:
2122           v = VariantJanus;
2123           break;
2124         case 49:
2125           v = VariantSuper;
2126           break;
2127         case 50:
2128           v = VariantGreat;
2129           break;
2130         case -1:
2131           /* Found "wild" or "w" in the string but no number;
2132              must assume it's normal chess. */
2133           v = VariantNormal;
2134           break;
2135         default:
2136           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2137           if( (len > MSG_SIZ) && appData.debugMode )
2138             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2139
2140           DisplayError(buf, 0);
2141           v = VariantUnknown;
2142           break;
2143         }
2144       }
2145     }
2146     if (appData.debugMode) {
2147       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2148               e, wnum, VariantName(v));
2149     }
2150     return v;
2151 }
2152
2153 static int leftover_start = 0, leftover_len = 0;
2154 char star_match[STAR_MATCH_N][MSG_SIZ];
2155
2156 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2157    advance *index beyond it, and set leftover_start to the new value of
2158    *index; else return FALSE.  If pattern contains the character '*', it
2159    matches any sequence of characters not containing '\r', '\n', or the
2160    character following the '*' (if any), and the matched sequence(s) are
2161    copied into star_match.
2162    */
2163 int
2164 looking_at(buf, index, pattern)
2165      char *buf;
2166      int *index;
2167      char *pattern;
2168 {
2169     char *bufp = &buf[*index], *patternp = pattern;
2170     int star_count = 0;
2171     char *matchp = star_match[0];
2172
2173     for (;;) {
2174         if (*patternp == NULLCHAR) {
2175             *index = leftover_start = bufp - buf;
2176             *matchp = NULLCHAR;
2177             return TRUE;
2178         }
2179         if (*bufp == NULLCHAR) return FALSE;
2180         if (*patternp == '*') {
2181             if (*bufp == *(patternp + 1)) {
2182                 *matchp = NULLCHAR;
2183                 matchp = star_match[++star_count];
2184                 patternp += 2;
2185                 bufp++;
2186                 continue;
2187             } else if (*bufp == '\n' || *bufp == '\r') {
2188                 patternp++;
2189                 if (*patternp == NULLCHAR)
2190                   continue;
2191                 else
2192                   return FALSE;
2193             } else {
2194                 *matchp++ = *bufp++;
2195                 continue;
2196             }
2197         }
2198         if (*patternp != *bufp) return FALSE;
2199         patternp++;
2200         bufp++;
2201     }
2202 }
2203
2204 void
2205 SendToPlayer(data, length)
2206      char *data;
2207      int length;
2208 {
2209     int error, outCount;
2210     outCount = OutputToProcess(NoProc, data, length, &error);
2211     if (outCount < length) {
2212         DisplayFatalError(_("Error writing to display"), error, 1);
2213     }
2214 }
2215
2216 void
2217 PackHolding(packed, holding)
2218      char packed[];
2219      char *holding;
2220 {
2221     char *p = holding;
2222     char *q = packed;
2223     int runlength = 0;
2224     int curr = 9999;
2225     do {
2226         if (*p == curr) {
2227             runlength++;
2228         } else {
2229             switch (runlength) {
2230               case 0:
2231                 break;
2232               case 1:
2233                 *q++ = curr;
2234                 break;
2235               case 2:
2236                 *q++ = curr;
2237                 *q++ = curr;
2238                 break;
2239               default:
2240                 sprintf(q, "%d", runlength);
2241                 while (*q) q++;
2242                 *q++ = curr;
2243                 break;
2244             }
2245             runlength = 1;
2246             curr = *p;
2247         }
2248     } while (*p++);
2249     *q = NULLCHAR;
2250 }
2251
2252 /* Telnet protocol requests from the front end */
2253 void
2254 TelnetRequest(ddww, option)
2255      unsigned char ddww, option;
2256 {
2257     unsigned char msg[3];
2258     int outCount, outError;
2259
2260     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2261
2262     if (appData.debugMode) {
2263         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2264         switch (ddww) {
2265           case TN_DO:
2266             ddwwStr = "DO";
2267             break;
2268           case TN_DONT:
2269             ddwwStr = "DONT";
2270             break;
2271           case TN_WILL:
2272             ddwwStr = "WILL";
2273             break;
2274           case TN_WONT:
2275             ddwwStr = "WONT";
2276             break;
2277           default:
2278             ddwwStr = buf1;
2279             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2280             break;
2281         }
2282         switch (option) {
2283           case TN_ECHO:
2284             optionStr = "ECHO";
2285             break;
2286           default:
2287             optionStr = buf2;
2288             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2289             break;
2290         }
2291         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2292     }
2293     msg[0] = TN_IAC;
2294     msg[1] = ddww;
2295     msg[2] = option;
2296     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2297     if (outCount < 3) {
2298         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2299     }
2300 }
2301
2302 void
2303 DoEcho()
2304 {
2305     if (!appData.icsActive) return;
2306     TelnetRequest(TN_DO, TN_ECHO);
2307 }
2308
2309 void
2310 DontEcho()
2311 {
2312     if (!appData.icsActive) return;
2313     TelnetRequest(TN_DONT, TN_ECHO);
2314 }
2315
2316 void
2317 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2318 {
2319     /* put the holdings sent to us by the server on the board holdings area */
2320     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2321     char p;
2322     ChessSquare piece;
2323
2324     if(gameInfo.holdingsWidth < 2)  return;
2325     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2326         return; // prevent overwriting by pre-board holdings
2327
2328     if( (int)lowestPiece >= BlackPawn ) {
2329         holdingsColumn = 0;
2330         countsColumn = 1;
2331         holdingsStartRow = BOARD_HEIGHT-1;
2332         direction = -1;
2333     } else {
2334         holdingsColumn = BOARD_WIDTH-1;
2335         countsColumn = BOARD_WIDTH-2;
2336         holdingsStartRow = 0;
2337         direction = 1;
2338     }
2339
2340     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2341         board[i][holdingsColumn] = EmptySquare;
2342         board[i][countsColumn]   = (ChessSquare) 0;
2343     }
2344     while( (p=*holdings++) != NULLCHAR ) {
2345         piece = CharToPiece( ToUpper(p) );
2346         if(piece == EmptySquare) continue;
2347         /*j = (int) piece - (int) WhitePawn;*/
2348         j = PieceToNumber(piece);
2349         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2350         if(j < 0) continue;               /* should not happen */
2351         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2352         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2353         board[holdingsStartRow+j*direction][countsColumn]++;
2354     }
2355 }
2356
2357
2358 void
2359 VariantSwitch(Board board, VariantClass newVariant)
2360 {
2361    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2362    static Board oldBoard;
2363
2364    startedFromPositionFile = FALSE;
2365    if(gameInfo.variant == newVariant) return;
2366
2367    /* [HGM] This routine is called each time an assignment is made to
2368     * gameInfo.variant during a game, to make sure the board sizes
2369     * are set to match the new variant. If that means adding or deleting
2370     * holdings, we shift the playing board accordingly
2371     * This kludge is needed because in ICS observe mode, we get boards
2372     * of an ongoing game without knowing the variant, and learn about the
2373     * latter only later. This can be because of the move list we requested,
2374     * in which case the game history is refilled from the beginning anyway,
2375     * but also when receiving holdings of a crazyhouse game. In the latter
2376     * case we want to add those holdings to the already received position.
2377     */
2378
2379
2380    if (appData.debugMode) {
2381      fprintf(debugFP, "Switch board from %s to %s\n",
2382              VariantName(gameInfo.variant), VariantName(newVariant));
2383      setbuf(debugFP, NULL);
2384    }
2385    shuffleOpenings = 0;       /* [HGM] shuffle */
2386    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2387    switch(newVariant)
2388      {
2389      case VariantShogi:
2390        newWidth = 9;  newHeight = 9;
2391        gameInfo.holdingsSize = 7;
2392      case VariantBughouse:
2393      case VariantCrazyhouse:
2394        newHoldingsWidth = 2; break;
2395      case VariantGreat:
2396        newWidth = 10;
2397      case VariantSuper:
2398        newHoldingsWidth = 2;
2399        gameInfo.holdingsSize = 8;
2400        break;
2401      case VariantGothic:
2402      case VariantCapablanca:
2403      case VariantCapaRandom:
2404        newWidth = 10;
2405      default:
2406        newHoldingsWidth = gameInfo.holdingsSize = 0;
2407      };
2408
2409    if(newWidth  != gameInfo.boardWidth  ||
2410       newHeight != gameInfo.boardHeight ||
2411       newHoldingsWidth != gameInfo.holdingsWidth ) {
2412
2413      /* shift position to new playing area, if needed */
2414      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2415        for(i=0; i<BOARD_HEIGHT; i++)
2416          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2417            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2418              board[i][j];
2419        for(i=0; i<newHeight; i++) {
2420          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2421          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2422        }
2423      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2424        for(i=0; i<BOARD_HEIGHT; i++)
2425          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2426            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2427              board[i][j];
2428      }
2429      gameInfo.boardWidth  = newWidth;
2430      gameInfo.boardHeight = newHeight;
2431      gameInfo.holdingsWidth = newHoldingsWidth;
2432      gameInfo.variant = newVariant;
2433      InitDrawingSizes(-2, 0);
2434    } else gameInfo.variant = newVariant;
2435    CopyBoard(oldBoard, board);   // remember correctly formatted board
2436      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2437    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2438 }
2439
2440 static int loggedOn = FALSE;
2441
2442 /*-- Game start info cache: --*/
2443 int gs_gamenum;
2444 char gs_kind[MSG_SIZ];
2445 static char player1Name[128] = "";
2446 static char player2Name[128] = "";
2447 static char cont_seq[] = "\n\\   ";
2448 static int player1Rating = -1;
2449 static int player2Rating = -1;
2450 /*----------------------------*/
2451
2452 ColorClass curColor = ColorNormal;
2453 int suppressKibitz = 0;
2454
2455 // [HGM] seekgraph
2456 Boolean soughtPending = FALSE;
2457 Boolean seekGraphUp;
2458 #define MAX_SEEK_ADS 200
2459 #define SQUARE 0x80
2460 char *seekAdList[MAX_SEEK_ADS];
2461 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2462 float tcList[MAX_SEEK_ADS];
2463 char colorList[MAX_SEEK_ADS];
2464 int nrOfSeekAds = 0;
2465 int minRating = 1010, maxRating = 2800;
2466 int hMargin = 10, vMargin = 20, h, w;
2467 extern int squareSize, lineGap;
2468
2469 void
2470 PlotSeekAd(int i)
2471 {
2472         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2473         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2474         if(r < minRating+100 && r >=0 ) r = minRating+100;
2475         if(r > maxRating) r = maxRating;
2476         if(tc < 1.) tc = 1.;
2477         if(tc > 95.) tc = 95.;
2478         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2479         y = ((double)r - minRating)/(maxRating - minRating)
2480             * (h-vMargin-squareSize/8-1) + vMargin;
2481         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2482         if(strstr(seekAdList[i], " u ")) color = 1;
2483         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2484            !strstr(seekAdList[i], "bullet") &&
2485            !strstr(seekAdList[i], "blitz") &&
2486            !strstr(seekAdList[i], "standard") ) color = 2;
2487         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2488         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2489 }
2490
2491 void
2492 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2493 {
2494         char buf[MSG_SIZ], *ext = "";
2495         VariantClass v = StringToVariant(type);
2496         if(strstr(type, "wild")) {
2497             ext = type + 4; // append wild number
2498             if(v == VariantFischeRandom) type = "chess960"; else
2499             if(v == VariantLoadable) type = "setup"; else
2500             type = VariantName(v);
2501         }
2502         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2503         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2504             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2505             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2506             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2507             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2508             seekNrList[nrOfSeekAds] = nr;
2509             zList[nrOfSeekAds] = 0;
2510             seekAdList[nrOfSeekAds++] = StrSave(buf);
2511             if(plot) PlotSeekAd(nrOfSeekAds-1);
2512         }
2513 }
2514
2515 void
2516 EraseSeekDot(int i)
2517 {
2518     int x = xList[i], y = yList[i], d=squareSize/4, k;
2519     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2520     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2521     // now replot every dot that overlapped
2522     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2523         int xx = xList[k], yy = yList[k];
2524         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2525             DrawSeekDot(xx, yy, colorList[k]);
2526     }
2527 }
2528
2529 void
2530 RemoveSeekAd(int nr)
2531 {
2532         int i;
2533         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2534             EraseSeekDot(i);
2535             if(seekAdList[i]) free(seekAdList[i]);
2536             seekAdList[i] = seekAdList[--nrOfSeekAds];
2537             seekNrList[i] = seekNrList[nrOfSeekAds];
2538             ratingList[i] = ratingList[nrOfSeekAds];
2539             colorList[i]  = colorList[nrOfSeekAds];
2540             tcList[i] = tcList[nrOfSeekAds];
2541             xList[i]  = xList[nrOfSeekAds];
2542             yList[i]  = yList[nrOfSeekAds];
2543             zList[i]  = zList[nrOfSeekAds];
2544             seekAdList[nrOfSeekAds] = NULL;
2545             break;
2546         }
2547 }
2548
2549 Boolean
2550 MatchSoughtLine(char *line)
2551 {
2552     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2553     int nr, base, inc, u=0; char dummy;
2554
2555     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2556        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2557        (u=1) &&
2558        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2559         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2560         // match: compact and save the line
2561         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2562         return TRUE;
2563     }
2564     return FALSE;
2565 }
2566
2567 int
2568 DrawSeekGraph()
2569 {
2570     int i;
2571     if(!seekGraphUp) return FALSE;
2572     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2573     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2574
2575     DrawSeekBackground(0, 0, w, h);
2576     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2577     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2578     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2579         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2580         yy = h-1-yy;
2581         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2582         if(i%500 == 0) {
2583             char buf[MSG_SIZ];
2584             snprintf(buf, MSG_SIZ, "%d", i);
2585             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2586         }
2587     }
2588     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2589     for(i=1; i<100; i+=(i<10?1:5)) {
2590         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2591         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2592         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2593             char buf[MSG_SIZ];
2594             snprintf(buf, MSG_SIZ, "%d", i);
2595             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2596         }
2597     }
2598     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2599     return TRUE;
2600 }
2601
2602 int SeekGraphClick(ClickType click, int x, int y, int moving)
2603 {
2604     static int lastDown = 0, displayed = 0, lastSecond;
2605     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2606         if(click == Release || moving) return FALSE;
2607         nrOfSeekAds = 0;
2608         soughtPending = TRUE;
2609         SendToICS(ics_prefix);
2610         SendToICS("sought\n"); // should this be "sought all"?
2611     } else { // issue challenge based on clicked ad
2612         int dist = 10000; int i, closest = 0, second = 0;
2613         for(i=0; i<nrOfSeekAds; i++) {
2614             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2615             if(d < dist) { dist = d; closest = i; }
2616             second += (d - zList[i] < 120); // count in-range ads
2617             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2618         }
2619         if(dist < 120) {
2620             char buf[MSG_SIZ];
2621             second = (second > 1);
2622             if(displayed != closest || second != lastSecond) {
2623                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2624                 lastSecond = second; displayed = closest;
2625             }
2626             if(click == Press) {
2627                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2628                 lastDown = closest;
2629                 return TRUE;
2630             } // on press 'hit', only show info
2631             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2632             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2633             SendToICS(ics_prefix);
2634             SendToICS(buf);
2635             return TRUE; // let incoming board of started game pop down the graph
2636         } else if(click == Release) { // release 'miss' is ignored
2637             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2638             if(moving == 2) { // right up-click
2639                 nrOfSeekAds = 0; // refresh graph
2640                 soughtPending = TRUE;
2641                 SendToICS(ics_prefix);
2642                 SendToICS("sought\n"); // should this be "sought all"?
2643             }
2644             return TRUE;
2645         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2646         // press miss or release hit 'pop down' seek graph
2647         seekGraphUp = FALSE;
2648         DrawPosition(TRUE, NULL);
2649     }
2650     return TRUE;
2651 }
2652
2653 void
2654 read_from_ics(isr, closure, data, count, error)
2655      InputSourceRef isr;
2656      VOIDSTAR closure;
2657      char *data;
2658      int count;
2659      int error;
2660 {
2661 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2662 #define STARTED_NONE 0
2663 #define STARTED_MOVES 1
2664 #define STARTED_BOARD 2
2665 #define STARTED_OBSERVE 3
2666 #define STARTED_HOLDINGS 4
2667 #define STARTED_CHATTER 5
2668 #define STARTED_COMMENT 6
2669 #define STARTED_MOVES_NOHIDE 7
2670
2671     static int started = STARTED_NONE;
2672     static char parse[20000];
2673     static int parse_pos = 0;
2674     static char buf[BUF_SIZE + 1];
2675     static int firstTime = TRUE, intfSet = FALSE;
2676     static ColorClass prevColor = ColorNormal;
2677     static int savingComment = FALSE;
2678     static int cmatch = 0; // continuation sequence match
2679     char *bp;
2680     char str[MSG_SIZ];
2681     int i, oldi;
2682     int buf_len;
2683     int next_out;
2684     int tkind;
2685     int backup;    /* [DM] For zippy color lines */
2686     char *p;
2687     char talker[MSG_SIZ]; // [HGM] chat
2688     int channel;
2689
2690     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2691
2692     if (appData.debugMode) {
2693       if (!error) {
2694         fprintf(debugFP, "<ICS: ");
2695         show_bytes(debugFP, data, count);
2696         fprintf(debugFP, "\n");
2697       }
2698     }
2699
2700     if (appData.debugMode) { int f = forwardMostMove;
2701         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2702                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2703                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2704     }
2705     if (count > 0) {
2706         /* If last read ended with a partial line that we couldn't parse,
2707            prepend it to the new read and try again. */
2708         if (leftover_len > 0) {
2709             for (i=0; i<leftover_len; i++)
2710               buf[i] = buf[leftover_start + i];
2711         }
2712
2713     /* copy new characters into the buffer */
2714     bp = buf + leftover_len;
2715     buf_len=leftover_len;
2716     for (i=0; i<count; i++)
2717     {
2718         // ignore these
2719         if (data[i] == '\r')
2720             continue;
2721
2722         // join lines split by ICS?
2723         if (!appData.noJoin)
2724         {
2725             /*
2726                 Joining just consists of finding matches against the
2727                 continuation sequence, and discarding that sequence
2728                 if found instead of copying it.  So, until a match
2729                 fails, there's nothing to do since it might be the
2730                 complete sequence, and thus, something we don't want
2731                 copied.
2732             */
2733             if (data[i] == cont_seq[cmatch])
2734             {
2735                 cmatch++;
2736                 if (cmatch == strlen(cont_seq))
2737                 {
2738                     cmatch = 0; // complete match.  just reset the counter
2739
2740                     /*
2741                         it's possible for the ICS to not include the space
2742                         at the end of the last word, making our [correct]
2743                         join operation fuse two separate words.  the server
2744                         does this when the space occurs at the width setting.
2745                     */
2746                     if (!buf_len || buf[buf_len-1] != ' ')
2747                     {
2748                         *bp++ = ' ';
2749                         buf_len++;
2750                     }
2751                 }
2752                 continue;
2753             }
2754             else if (cmatch)
2755             {
2756                 /*
2757                     match failed, so we have to copy what matched before
2758                     falling through and copying this character.  In reality,
2759                     this will only ever be just the newline character, but
2760                     it doesn't hurt to be precise.
2761                 */
2762                 strncpy(bp, cont_seq, cmatch);
2763                 bp += cmatch;
2764                 buf_len += cmatch;
2765                 cmatch = 0;
2766             }
2767         }
2768
2769         // copy this char
2770         *bp++ = data[i];
2771         buf_len++;
2772     }
2773
2774         buf[buf_len] = NULLCHAR;
2775 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2776         next_out = 0;
2777         leftover_start = 0;
2778
2779         i = 0;
2780         while (i < buf_len) {
2781             /* Deal with part of the TELNET option negotiation
2782                protocol.  We refuse to do anything beyond the
2783                defaults, except that we allow the WILL ECHO option,
2784                which ICS uses to turn off password echoing when we are
2785                directly connected to it.  We reject this option
2786                if localLineEditing mode is on (always on in xboard)
2787                and we are talking to port 23, which might be a real
2788                telnet server that will try to keep WILL ECHO on permanently.
2789              */
2790             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2791                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2792                 unsigned char option;
2793                 oldi = i;
2794                 switch ((unsigned char) buf[++i]) {
2795                   case TN_WILL:
2796                     if (appData.debugMode)
2797                       fprintf(debugFP, "\n<WILL ");
2798                     switch (option = (unsigned char) buf[++i]) {
2799                       case TN_ECHO:
2800                         if (appData.debugMode)
2801                           fprintf(debugFP, "ECHO ");
2802                         /* Reply only if this is a change, according
2803                            to the protocol rules. */
2804                         if (remoteEchoOption) break;
2805                         if (appData.localLineEditing &&
2806                             atoi(appData.icsPort) == TN_PORT) {
2807                             TelnetRequest(TN_DONT, TN_ECHO);
2808                         } else {
2809                             EchoOff();
2810                             TelnetRequest(TN_DO, TN_ECHO);
2811                             remoteEchoOption = TRUE;
2812                         }
2813                         break;
2814                       default:
2815                         if (appData.debugMode)
2816                           fprintf(debugFP, "%d ", option);
2817                         /* Whatever this is, we don't want it. */
2818                         TelnetRequest(TN_DONT, option);
2819                         break;
2820                     }
2821                     break;
2822                   case TN_WONT:
2823                     if (appData.debugMode)
2824                       fprintf(debugFP, "\n<WONT ");
2825                     switch (option = (unsigned char) buf[++i]) {
2826                       case TN_ECHO:
2827                         if (appData.debugMode)
2828                           fprintf(debugFP, "ECHO ");
2829                         /* Reply only if this is a change, according
2830                            to the protocol rules. */
2831                         if (!remoteEchoOption) break;
2832                         EchoOn();
2833                         TelnetRequest(TN_DONT, TN_ECHO);
2834                         remoteEchoOption = FALSE;
2835                         break;
2836                       default:
2837                         if (appData.debugMode)
2838                           fprintf(debugFP, "%d ", (unsigned char) option);
2839                         /* Whatever this is, it must already be turned
2840                            off, because we never agree to turn on
2841                            anything non-default, so according to the
2842                            protocol rules, we don't reply. */
2843                         break;
2844                     }
2845                     break;
2846                   case TN_DO:
2847                     if (appData.debugMode)
2848                       fprintf(debugFP, "\n<DO ");
2849                     switch (option = (unsigned char) buf[++i]) {
2850                       default:
2851                         /* Whatever this is, we refuse to do it. */
2852                         if (appData.debugMode)
2853                           fprintf(debugFP, "%d ", option);
2854                         TelnetRequest(TN_WONT, option);
2855                         break;
2856                     }
2857                     break;
2858                   case TN_DONT:
2859                     if (appData.debugMode)
2860                       fprintf(debugFP, "\n<DONT ");
2861                     switch (option = (unsigned char) buf[++i]) {
2862                       default:
2863                         if (appData.debugMode)
2864                           fprintf(debugFP, "%d ", option);
2865                         /* Whatever this is, we are already not doing
2866                            it, because we never agree to do anything
2867                            non-default, so according to the protocol
2868                            rules, we don't reply. */
2869                         break;
2870                     }
2871                     break;
2872                   case TN_IAC:
2873                     if (appData.debugMode)
2874                       fprintf(debugFP, "\n<IAC ");
2875                     /* Doubled IAC; pass it through */
2876                     i--;
2877                     break;
2878                   default:
2879                     if (appData.debugMode)
2880                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2881                     /* Drop all other telnet commands on the floor */
2882                     break;
2883                 }
2884                 if (oldi > next_out)
2885                   SendToPlayer(&buf[next_out], oldi - next_out);
2886                 if (++i > next_out)
2887                   next_out = i;
2888                 continue;
2889             }
2890
2891             /* OK, this at least will *usually* work */
2892             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2893                 loggedOn = TRUE;
2894             }
2895
2896             if (loggedOn && !intfSet) {
2897                 if (ics_type == ICS_ICC) {
2898                   snprintf(str, MSG_SIZ,
2899                           "/set-quietly interface %s\n/set-quietly style 12\n",
2900                           programVersion);
2901                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2902                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2903                 } else if (ics_type == ICS_CHESSNET) {
2904                   snprintf(str, MSG_SIZ, "/style 12\n");
2905                 } else {
2906                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2907                   strcat(str, programVersion);
2908                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2909                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2910                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2911 #ifdef WIN32
2912                   strcat(str, "$iset nohighlight 1\n");
2913 #endif
2914                   strcat(str, "$iset lock 1\n$style 12\n");
2915                 }
2916                 SendToICS(str);
2917                 NotifyFrontendLogin();
2918                 intfSet = TRUE;
2919             }
2920
2921             if (started == STARTED_COMMENT) {
2922                 /* Accumulate characters in comment */
2923                 parse[parse_pos++] = buf[i];
2924                 if (buf[i] == '\n') {
2925                     parse[parse_pos] = NULLCHAR;
2926                     if(chattingPartner>=0) {
2927                         char mess[MSG_SIZ];
2928                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2929                         OutputChatMessage(chattingPartner, mess);
2930                         chattingPartner = -1;
2931                         next_out = i+1; // [HGM] suppress printing in ICS window
2932                     } else
2933                     if(!suppressKibitz) // [HGM] kibitz
2934                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2935                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2936                         int nrDigit = 0, nrAlph = 0, j;
2937                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2938                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2939                         parse[parse_pos] = NULLCHAR;
2940                         // try to be smart: if it does not look like search info, it should go to
2941                         // ICS interaction window after all, not to engine-output window.
2942                         for(j=0; j<parse_pos; j++) { // count letters and digits
2943                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2944                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2945                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2946                         }
2947                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2948                             int depth=0; float score;
2949                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2950                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2951                                 pvInfoList[forwardMostMove-1].depth = depth;
2952                                 pvInfoList[forwardMostMove-1].score = 100*score;
2953                             }
2954                             OutputKibitz(suppressKibitz, parse);
2955                         } else {
2956                             char tmp[MSG_SIZ];
2957                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2958                             SendToPlayer(tmp, strlen(tmp));
2959                         }
2960                         next_out = i+1; // [HGM] suppress printing in ICS window
2961                     }
2962                     started = STARTED_NONE;
2963                 } else {
2964                     /* Don't match patterns against characters in comment */
2965                     i++;
2966                     continue;
2967                 }
2968             }
2969             if (started == STARTED_CHATTER) {
2970                 if (buf[i] != '\n') {
2971                     /* Don't match patterns against characters in chatter */
2972                     i++;
2973                     continue;
2974                 }
2975                 started = STARTED_NONE;
2976                 if(suppressKibitz) next_out = i+1;
2977             }
2978
2979             /* Kludge to deal with rcmd protocol */
2980             if (firstTime && looking_at(buf, &i, "\001*")) {
2981                 DisplayFatalError(&buf[1], 0, 1);
2982                 continue;
2983             } else {
2984                 firstTime = FALSE;
2985             }
2986
2987             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2988                 ics_type = ICS_ICC;
2989                 ics_prefix = "/";
2990                 if (appData.debugMode)
2991                   fprintf(debugFP, "ics_type %d\n", ics_type);
2992                 continue;
2993             }
2994             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2995                 ics_type = ICS_FICS;
2996                 ics_prefix = "$";
2997                 if (appData.debugMode)
2998                   fprintf(debugFP, "ics_type %d\n", ics_type);
2999                 continue;
3000             }
3001             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3002                 ics_type = ICS_CHESSNET;
3003                 ics_prefix = "/";
3004                 if (appData.debugMode)
3005                   fprintf(debugFP, "ics_type %d\n", ics_type);
3006                 continue;
3007             }
3008
3009             if (!loggedOn &&
3010                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3011                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3012                  looking_at(buf, &i, "will be \"*\""))) {
3013               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3014               continue;
3015             }
3016
3017             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3018               char buf[MSG_SIZ];
3019               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3020               DisplayIcsInteractionTitle(buf);
3021               have_set_title = TRUE;
3022             }
3023
3024             /* skip finger notes */
3025             if (started == STARTED_NONE &&
3026                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3027                  (buf[i] == '1' && buf[i+1] == '0')) &&
3028                 buf[i+2] == ':' && buf[i+3] == ' ') {
3029               started = STARTED_CHATTER;
3030               i += 3;
3031               continue;
3032             }
3033
3034             oldi = i;
3035             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3036             if(appData.seekGraph) {
3037                 if(soughtPending && MatchSoughtLine(buf+i)) {
3038                     i = strstr(buf+i, "rated") - buf;
3039                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3040                     next_out = leftover_start = i;
3041                     started = STARTED_CHATTER;
3042                     suppressKibitz = TRUE;
3043                     continue;
3044                 }
3045                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3046                         && looking_at(buf, &i, "* ads displayed")) {
3047                     soughtPending = FALSE;
3048                     seekGraphUp = TRUE;
3049                     DrawSeekGraph();
3050                     continue;
3051                 }
3052                 if(appData.autoRefresh) {
3053                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3054                         int s = (ics_type == ICS_ICC); // ICC format differs
3055                         if(seekGraphUp)
3056                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3057                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3058                         looking_at(buf, &i, "*% "); // eat prompt
3059                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3060                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3061                         next_out = i; // suppress
3062                         continue;
3063                     }
3064                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3065                         char *p = star_match[0];
3066                         while(*p) {
3067                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3068                             while(*p && *p++ != ' '); // next
3069                         }
3070                         looking_at(buf, &i, "*% "); // eat prompt
3071                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3072                         next_out = i;
3073                         continue;
3074                     }
3075                 }
3076             }
3077
3078             /* skip formula vars */
3079             if (started == STARTED_NONE &&
3080                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3081               started = STARTED_CHATTER;
3082               i += 3;
3083               continue;
3084             }
3085
3086             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3087             if (appData.autoKibitz && started == STARTED_NONE &&
3088                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3089                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3090                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3091                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3092                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3093                         suppressKibitz = TRUE;
3094                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3095                         next_out = i;
3096                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3097                                 && (gameMode == IcsPlayingWhite)) ||
3098                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3099                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3100                             started = STARTED_CHATTER; // own kibitz we simply discard
3101                         else {
3102                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3103                             parse_pos = 0; parse[0] = NULLCHAR;
3104                             savingComment = TRUE;
3105                             suppressKibitz = gameMode != IcsObserving ? 2 :
3106                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3107                         }
3108                         continue;
3109                 } else
3110                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3111                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3112                          && atoi(star_match[0])) {
3113                     // suppress the acknowledgements of our own autoKibitz
3114                     char *p;
3115                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3116                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3117                     SendToPlayer(star_match[0], strlen(star_match[0]));
3118                     if(looking_at(buf, &i, "*% ")) // eat prompt
3119                         suppressKibitz = FALSE;
3120                     next_out = i;
3121                     continue;
3122                 }
3123             } // [HGM] kibitz: end of patch
3124
3125             // [HGM] chat: intercept tells by users for which we have an open chat window
3126             channel = -1;
3127             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3128                                            looking_at(buf, &i, "* whispers:") ||
3129                                            looking_at(buf, &i, "* kibitzes:") ||
3130                                            looking_at(buf, &i, "* shouts:") ||
3131                                            looking_at(buf, &i, "* c-shouts:") ||
3132                                            looking_at(buf, &i, "--> * ") ||
3133                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3134                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3135                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3136                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3137                 int p;
3138                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3139                 chattingPartner = -1;
3140
3141                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3142                 for(p=0; p<MAX_CHAT; p++) {
3143                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3144                     talker[0] = '['; strcat(talker, "] ");
3145                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3146                     chattingPartner = p; break;
3147                     }
3148                 } else
3149                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3150                 for(p=0; p<MAX_CHAT; p++) {
3151                     if(!strcmp("kibitzes", chatPartner[p])) {
3152                         talker[0] = '['; strcat(talker, "] ");
3153                         chattingPartner = p; break;
3154                     }
3155                 } else
3156                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3157                 for(p=0; p<MAX_CHAT; p++) {
3158                     if(!strcmp("whispers", chatPartner[p])) {
3159                         talker[0] = '['; strcat(talker, "] ");
3160                         chattingPartner = p; break;
3161                     }
3162                 } else
3163                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3164                   if(buf[i-8] == '-' && buf[i-3] == 't')
3165                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3166                     if(!strcmp("c-shouts", chatPartner[p])) {
3167                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3168                         chattingPartner = p; break;
3169                     }
3170                   }
3171                   if(chattingPartner < 0)
3172                   for(p=0; p<MAX_CHAT; p++) {
3173                     if(!strcmp("shouts", chatPartner[p])) {
3174                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3175                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3176                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3177                         chattingPartner = p; break;
3178                     }
3179                   }
3180                 }
3181                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3182                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3183                     talker[0] = 0; Colorize(ColorTell, FALSE);
3184                     chattingPartner = p; break;
3185                 }
3186                 if(chattingPartner<0) i = oldi; else {
3187                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3188                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3189                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3190                     started = STARTED_COMMENT;
3191                     parse_pos = 0; parse[0] = NULLCHAR;
3192                     savingComment = 3 + chattingPartner; // counts as TRUE
3193                     suppressKibitz = TRUE;
3194                     continue;
3195                 }
3196             } // [HGM] chat: end of patch
3197
3198           backup = i;
3199             if (appData.zippyTalk || appData.zippyPlay) {
3200                 /* [DM] Backup address for color zippy lines */
3201 #if ZIPPY
3202                if (loggedOn == TRUE)
3203                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3204                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3205 #endif
3206             } // [DM] 'else { ' deleted
3207                 if (
3208                     /* Regular tells and says */
3209                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3210                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3211                     looking_at(buf, &i, "* says: ") ||
3212                     /* Don't color "message" or "messages" output */
3213                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3214                     looking_at(buf, &i, "*. * at *:*: ") ||
3215                     looking_at(buf, &i, "--* (*:*): ") ||
3216                     /* Message notifications (same color as tells) */
3217                     looking_at(buf, &i, "* has left a message ") ||
3218                     looking_at(buf, &i, "* just sent you a message:\n") ||
3219                     /* Whispers and kibitzes */
3220                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3221                     looking_at(buf, &i, "* kibitzes: ") ||
3222                     /* Channel tells */
3223                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3224
3225                   if (tkind == 1 && strchr(star_match[0], ':')) {
3226                       /* Avoid "tells you:" spoofs in channels */
3227                      tkind = 3;
3228                   }
3229                   if (star_match[0][0] == NULLCHAR ||
3230                       strchr(star_match[0], ' ') ||
3231                       (tkind == 3 && strchr(star_match[1], ' '))) {
3232                     /* Reject bogus matches */
3233                     i = oldi;
3234                   } else {
3235                     if (appData.colorize) {
3236                       if (oldi > next_out) {
3237                         SendToPlayer(&buf[next_out], oldi - next_out);
3238                         next_out = oldi;
3239                       }
3240                       switch (tkind) {
3241                       case 1:
3242                         Colorize(ColorTell, FALSE);
3243                         curColor = ColorTell;
3244                         break;
3245                       case 2:
3246                         Colorize(ColorKibitz, FALSE);
3247                         curColor = ColorKibitz;
3248                         break;
3249                       case 3:
3250                         p = strrchr(star_match[1], '(');
3251                         if (p == NULL) {
3252                           p = star_match[1];
3253                         } else {
3254                           p++;
3255                         }
3256                         if (atoi(p) == 1) {
3257                           Colorize(ColorChannel1, FALSE);
3258                           curColor = ColorChannel1;
3259                         } else {
3260                           Colorize(ColorChannel, FALSE);
3261                           curColor = ColorChannel;
3262                         }
3263                         break;
3264                       case 5:
3265                         curColor = ColorNormal;
3266                         break;
3267                       }
3268                     }
3269                     if (started == STARTED_NONE && appData.autoComment &&
3270                         (gameMode == IcsObserving ||
3271                          gameMode == IcsPlayingWhite ||
3272                          gameMode == IcsPlayingBlack)) {
3273                       parse_pos = i - oldi;
3274                       memcpy(parse, &buf[oldi], parse_pos);
3275                       parse[parse_pos] = NULLCHAR;
3276                       started = STARTED_COMMENT;
3277                       savingComment = TRUE;
3278                     } else {
3279                       started = STARTED_CHATTER;
3280                       savingComment = FALSE;
3281                     }
3282                     loggedOn = TRUE;
3283                     continue;
3284                   }
3285                 }
3286
3287                 if (looking_at(buf, &i, "* s-shouts: ") ||
3288                     looking_at(buf, &i, "* c-shouts: ")) {
3289                     if (appData.colorize) {
3290                         if (oldi > next_out) {
3291                             SendToPlayer(&buf[next_out], oldi - next_out);
3292                             next_out = oldi;
3293                         }
3294                         Colorize(ColorSShout, FALSE);
3295                         curColor = ColorSShout;
3296                     }
3297                     loggedOn = TRUE;
3298                     started = STARTED_CHATTER;
3299                     continue;
3300                 }
3301
3302                 if (looking_at(buf, &i, "--->")) {
3303                     loggedOn = TRUE;
3304                     continue;
3305                 }
3306
3307                 if (looking_at(buf, &i, "* shouts: ") ||
3308                     looking_at(buf, &i, "--> ")) {
3309                     if (appData.colorize) {
3310                         if (oldi > next_out) {
3311                             SendToPlayer(&buf[next_out], oldi - next_out);
3312                             next_out = oldi;
3313                         }
3314                         Colorize(ColorShout, FALSE);
3315                         curColor = ColorShout;
3316                     }
3317                     loggedOn = TRUE;
3318                     started = STARTED_CHATTER;
3319                     continue;
3320                 }
3321
3322                 if (looking_at( buf, &i, "Challenge:")) {
3323                     if (appData.colorize) {
3324                         if (oldi > next_out) {
3325                             SendToPlayer(&buf[next_out], oldi - next_out);
3326                             next_out = oldi;
3327                         }
3328                         Colorize(ColorChallenge, FALSE);
3329                         curColor = ColorChallenge;
3330                     }
3331                     loggedOn = TRUE;
3332                     continue;
3333                 }
3334
3335                 if (looking_at(buf, &i, "* offers you") ||
3336                     looking_at(buf, &i, "* offers to be") ||
3337                     looking_at(buf, &i, "* would like to") ||
3338                     looking_at(buf, &i, "* requests to") ||
3339                     looking_at(buf, &i, "Your opponent offers") ||
3340                     looking_at(buf, &i, "Your opponent requests")) {
3341
3342                     if (appData.colorize) {
3343                         if (oldi > next_out) {
3344                             SendToPlayer(&buf[next_out], oldi - next_out);
3345                             next_out = oldi;
3346                         }
3347                         Colorize(ColorRequest, FALSE);
3348                         curColor = ColorRequest;
3349                     }
3350                     continue;
3351                 }
3352
3353                 if (looking_at(buf, &i, "* (*) seeking")) {
3354                     if (appData.colorize) {
3355                         if (oldi > next_out) {
3356                             SendToPlayer(&buf[next_out], oldi - next_out);
3357                             next_out = oldi;
3358                         }
3359                         Colorize(ColorSeek, FALSE);
3360                         curColor = ColorSeek;
3361                     }
3362                     continue;
3363             }
3364
3365           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3366
3367             if (looking_at(buf, &i, "\\   ")) {
3368                 if (prevColor != ColorNormal) {
3369                     if (oldi > next_out) {
3370                         SendToPlayer(&buf[next_out], oldi - next_out);
3371                         next_out = oldi;
3372                     }
3373                     Colorize(prevColor, TRUE);
3374                     curColor = prevColor;
3375                 }
3376                 if (savingComment) {
3377                     parse_pos = i - oldi;
3378                     memcpy(parse, &buf[oldi], parse_pos);
3379                     parse[parse_pos] = NULLCHAR;
3380                     started = STARTED_COMMENT;
3381                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3382                         chattingPartner = savingComment - 3; // kludge to remember the box
3383                 } else {
3384                     started = STARTED_CHATTER;
3385                 }
3386                 continue;
3387             }
3388
3389             if (looking_at(buf, &i, "Black Strength :") ||
3390                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3391                 looking_at(buf, &i, "<10>") ||
3392                 looking_at(buf, &i, "#@#")) {
3393                 /* Wrong board style */
3394                 loggedOn = TRUE;
3395                 SendToICS(ics_prefix);
3396                 SendToICS("set style 12\n");
3397                 SendToICS(ics_prefix);
3398                 SendToICS("refresh\n");
3399                 continue;
3400             }
3401
3402             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3403                 ICSInitScript();
3404                 have_sent_ICS_logon = 1;
3405                 continue;
3406             }
3407
3408             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3409                 (looking_at(buf, &i, "\n<12> ") ||
3410                  looking_at(buf, &i, "<12> "))) {
3411                 loggedOn = TRUE;
3412                 if (oldi > next_out) {
3413                     SendToPlayer(&buf[next_out], oldi - next_out);
3414                 }
3415                 next_out = i;
3416                 started = STARTED_BOARD;
3417                 parse_pos = 0;
3418                 continue;
3419             }
3420
3421             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3422                 looking_at(buf, &i, "<b1> ")) {
3423                 if (oldi > next_out) {
3424                     SendToPlayer(&buf[next_out], oldi - next_out);
3425                 }
3426                 next_out = i;
3427                 started = STARTED_HOLDINGS;
3428                 parse_pos = 0;
3429                 continue;
3430             }
3431
3432             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3433                 loggedOn = TRUE;
3434                 /* Header for a move list -- first line */
3435
3436                 switch (ics_getting_history) {
3437                   case H_FALSE:
3438                     switch (gameMode) {
3439                       case IcsIdle:
3440                       case BeginningOfGame:
3441                         /* User typed "moves" or "oldmoves" while we
3442                            were idle.  Pretend we asked for these
3443                            moves and soak them up so user can step
3444                            through them and/or save them.
3445                            */
3446                         Reset(FALSE, TRUE);
3447                         gameMode = IcsObserving;
3448                         ModeHighlight();
3449                         ics_gamenum = -1;
3450                         ics_getting_history = H_GOT_UNREQ_HEADER;
3451                         break;
3452                       case EditGame: /*?*/
3453                       case EditPosition: /*?*/
3454                         /* Should above feature work in these modes too? */
3455                         /* For now it doesn't */
3456                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3457                         break;
3458                       default:
3459                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3460                         break;
3461                     }
3462                     break;
3463                   case H_REQUESTED:
3464                     /* Is this the right one? */
3465                     if (gameInfo.white && gameInfo.black &&
3466                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3467                         strcmp(gameInfo.black, star_match[2]) == 0) {
3468                         /* All is well */
3469                         ics_getting_history = H_GOT_REQ_HEADER;
3470                     }
3471                     break;
3472                   case H_GOT_REQ_HEADER:
3473                   case H_GOT_UNREQ_HEADER:
3474                   case H_GOT_UNWANTED_HEADER:
3475                   case H_GETTING_MOVES:
3476                     /* Should not happen */
3477                     DisplayError(_("Error gathering move list: two headers"), 0);
3478                     ics_getting_history = H_FALSE;
3479                     break;
3480                 }
3481
3482                 /* Save player ratings into gameInfo if needed */
3483                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3484                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3485                     (gameInfo.whiteRating == -1 ||
3486                      gameInfo.blackRating == -1)) {
3487
3488                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3489                     gameInfo.blackRating = string_to_rating(star_match[3]);
3490                     if (appData.debugMode)
3491                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3492                               gameInfo.whiteRating, gameInfo.blackRating);
3493                 }
3494                 continue;
3495             }
3496
3497             if (looking_at(buf, &i,
3498               "* * match, initial time: * minute*, increment: * second")) {
3499                 /* Header for a move list -- second line */
3500                 /* Initial board will follow if this is a wild game */
3501                 if (gameInfo.event != NULL) free(gameInfo.event);
3502                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3503                 gameInfo.event = StrSave(str);
3504                 /* [HGM] we switched variant. Translate boards if needed. */
3505                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3506                 continue;
3507             }
3508
3509             if (looking_at(buf, &i, "Move  ")) {
3510                 /* Beginning of a move list */
3511                 switch (ics_getting_history) {
3512                   case H_FALSE:
3513                     /* Normally should not happen */
3514                     /* Maybe user hit reset while we were parsing */
3515                     break;
3516                   case H_REQUESTED:
3517                     /* Happens if we are ignoring a move list that is not
3518                      * the one we just requested.  Common if the user
3519                      * tries to observe two games without turning off
3520                      * getMoveList */
3521                     break;
3522                   case H_GETTING_MOVES:
3523                     /* Should not happen */
3524                     DisplayError(_("Error gathering move list: nested"), 0);
3525                     ics_getting_history = H_FALSE;
3526                     break;
3527                   case H_GOT_REQ_HEADER:
3528                     ics_getting_history = H_GETTING_MOVES;
3529                     started = STARTED_MOVES;
3530                     parse_pos = 0;
3531                     if (oldi > next_out) {
3532                         SendToPlayer(&buf[next_out], oldi - next_out);
3533                     }
3534                     break;
3535                   case H_GOT_UNREQ_HEADER:
3536                     ics_getting_history = H_GETTING_MOVES;
3537                     started = STARTED_MOVES_NOHIDE;
3538                     parse_pos = 0;
3539                     break;
3540                   case H_GOT_UNWANTED_HEADER:
3541                     ics_getting_history = H_FALSE;
3542                     break;
3543                 }
3544                 continue;
3545             }
3546
3547             if (looking_at(buf, &i, "% ") ||
3548                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3549                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3550                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3551                     soughtPending = FALSE;
3552                     seekGraphUp = TRUE;
3553                     DrawSeekGraph();
3554                 }
3555                 if(suppressKibitz) next_out = i;
3556                 savingComment = FALSE;
3557                 suppressKibitz = 0;
3558                 switch (started) {
3559                   case STARTED_MOVES:
3560                   case STARTED_MOVES_NOHIDE:
3561                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3562                     parse[parse_pos + i - oldi] = NULLCHAR;
3563                     ParseGameHistory(parse);
3564 #if ZIPPY
3565                     if (appData.zippyPlay && first.initDone) {
3566                         FeedMovesToProgram(&first, forwardMostMove);
3567                         if (gameMode == IcsPlayingWhite) {
3568                             if (WhiteOnMove(forwardMostMove)) {
3569                                 if (first.sendTime) {
3570                                   if (first.useColors) {
3571                                     SendToProgram("black\n", &first);
3572                                   }
3573                                   SendTimeRemaining(&first, TRUE);
3574                                 }
3575                                 if (first.useColors) {
3576                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3577                                 }
3578                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3579                                 first.maybeThinking = TRUE;
3580                             } else {
3581                                 if (first.usePlayother) {
3582                                   if (first.sendTime) {
3583                                     SendTimeRemaining(&first, TRUE);
3584                                   }
3585                                   SendToProgram("playother\n", &first);
3586                                   firstMove = FALSE;
3587                                 } else {
3588                                   firstMove = TRUE;
3589                                 }
3590                             }
3591                         } else if (gameMode == IcsPlayingBlack) {
3592                             if (!WhiteOnMove(forwardMostMove)) {
3593                                 if (first.sendTime) {
3594                                   if (first.useColors) {
3595                                     SendToProgram("white\n", &first);
3596                                   }
3597                                   SendTimeRemaining(&first, FALSE);
3598                                 }
3599                                 if (first.useColors) {
3600                                   SendToProgram("black\n", &first);
3601                                 }
3602                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3603                                 first.maybeThinking = TRUE;
3604                             } else {
3605                                 if (first.usePlayother) {
3606                                   if (first.sendTime) {
3607                                     SendTimeRemaining(&first, FALSE);
3608                                   }
3609                                   SendToProgram("playother\n", &first);
3610                                   firstMove = FALSE;
3611                                 } else {
3612                                   firstMove = TRUE;
3613                                 }
3614                             }
3615                         }
3616                     }
3617 #endif
3618                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3619                         /* Moves came from oldmoves or moves command
3620                            while we weren't doing anything else.
3621                            */
3622                         currentMove = forwardMostMove;
3623                         ClearHighlights();/*!!could figure this out*/
3624                         flipView = appData.flipView;
3625                         DrawPosition(TRUE, boards[currentMove]);
3626                         DisplayBothClocks();
3627                         snprintf(str, MSG_SIZ, "%s vs. %s",
3628                                 gameInfo.white, gameInfo.black);
3629                         DisplayTitle(str);
3630                         gameMode = IcsIdle;
3631                     } else {
3632                         /* Moves were history of an active game */
3633                         if (gameInfo.resultDetails != NULL) {
3634                             free(gameInfo.resultDetails);
3635                             gameInfo.resultDetails = NULL;
3636                         }
3637                     }
3638                     HistorySet(parseList, backwardMostMove,
3639                                forwardMostMove, currentMove-1);
3640                     DisplayMove(currentMove - 1);
3641                     if (started == STARTED_MOVES) next_out = i;
3642                     started = STARTED_NONE;
3643                     ics_getting_history = H_FALSE;
3644                     break;
3645
3646                   case STARTED_OBSERVE:
3647                     started = STARTED_NONE;
3648                     SendToICS(ics_prefix);
3649                     SendToICS("refresh\n");
3650                     break;
3651
3652                   default:
3653                     break;
3654                 }
3655                 if(bookHit) { // [HGM] book: simulate book reply
3656                     static char bookMove[MSG_SIZ]; // a bit generous?
3657
3658                     programStats.nodes = programStats.depth = programStats.time =
3659                     programStats.score = programStats.got_only_move = 0;
3660                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3661
3662                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3663                     strcat(bookMove, bookHit);
3664                     HandleMachineMove(bookMove, &first);
3665                 }
3666                 continue;
3667             }
3668
3669             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3670                  started == STARTED_HOLDINGS ||
3671                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3672                 /* Accumulate characters in move list or board */
3673                 parse[parse_pos++] = buf[i];
3674             }
3675
3676             /* Start of game messages.  Mostly we detect start of game
3677                when the first board image arrives.  On some versions
3678                of the ICS, though, we need to do a "refresh" after starting
3679                to observe in order to get the current board right away. */
3680             if (looking_at(buf, &i, "Adding game * to observation list")) {
3681                 started = STARTED_OBSERVE;
3682                 continue;
3683             }
3684
3685             /* Handle auto-observe */
3686             if (appData.autoObserve &&
3687                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3688                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3689                 char *player;
3690                 /* Choose the player that was highlighted, if any. */
3691                 if (star_match[0][0] == '\033' ||
3692                     star_match[1][0] != '\033') {
3693                     player = star_match[0];
3694                 } else {
3695                     player = star_match[2];
3696                 }
3697                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3698                         ics_prefix, StripHighlightAndTitle(player));
3699                 SendToICS(str);
3700
3701                 /* Save ratings from notify string */
3702                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3703                 player1Rating = string_to_rating(star_match[1]);
3704                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3705                 player2Rating = string_to_rating(star_match[3]);
3706
3707                 if (appData.debugMode)
3708                   fprintf(debugFP,
3709                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3710                           player1Name, player1Rating,
3711                           player2Name, player2Rating);
3712
3713                 continue;
3714             }
3715
3716             /* Deal with automatic examine mode after a game,
3717                and with IcsObserving -> IcsExamining transition */
3718             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3719                 looking_at(buf, &i, "has made you an examiner of game *")) {
3720
3721                 int gamenum = atoi(star_match[0]);
3722                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3723                     gamenum == ics_gamenum) {
3724                     /* We were already playing or observing this game;
3725                        no need to refetch history */
3726                     gameMode = IcsExamining;
3727                     if (pausing) {
3728                         pauseExamForwardMostMove = forwardMostMove;
3729                     } else if (currentMove < forwardMostMove) {
3730                         ForwardInner(forwardMostMove);
3731                     }
3732                 } else {
3733                     /* I don't think this case really can happen */
3734                     SendToICS(ics_prefix);
3735                     SendToICS("refresh\n");
3736                 }
3737                 continue;
3738             }
3739
3740             /* Error messages */
3741 //          if (ics_user_moved) {
3742             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3743                 if (looking_at(buf, &i, "Illegal move") ||
3744                     looking_at(buf, &i, "Not a legal move") ||
3745                     looking_at(buf, &i, "Your king is in check") ||
3746                     looking_at(buf, &i, "It isn't your turn") ||
3747                     looking_at(buf, &i, "It is not your move")) {
3748                     /* Illegal move */
3749                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3750                         currentMove = forwardMostMove-1;
3751                         DisplayMove(currentMove - 1); /* before DMError */
3752                         DrawPosition(FALSE, boards[currentMove]);
3753                         SwitchClocks(forwardMostMove-1); // [HGM] race
3754                         DisplayBothClocks();
3755                     }
3756                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3757                     ics_user_moved = 0;
3758                     continue;
3759                 }
3760             }
3761
3762             if (looking_at(buf, &i, "still have time") ||
3763                 looking_at(buf, &i, "not out of time") ||
3764                 looking_at(buf, &i, "either player is out of time") ||
3765                 looking_at(buf, &i, "has timeseal; checking")) {
3766                 /* We must have called his flag a little too soon */
3767                 whiteFlag = blackFlag = FALSE;
3768                 continue;
3769             }
3770
3771             if (looking_at(buf, &i, "added * seconds to") ||
3772                 looking_at(buf, &i, "seconds were added to")) {
3773                 /* Update the clocks */
3774                 SendToICS(ics_prefix);
3775                 SendToICS("refresh\n");
3776                 continue;
3777             }
3778
3779             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3780                 ics_clock_paused = TRUE;
3781                 StopClocks();
3782                 continue;
3783             }
3784
3785             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3786                 ics_clock_paused = FALSE;
3787                 StartClocks();
3788                 continue;
3789             }
3790
3791             /* Grab player ratings from the Creating: message.
3792                Note we have to check for the special case when
3793                the ICS inserts things like [white] or [black]. */
3794             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3795                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3796                 /* star_matches:
3797                    0    player 1 name (not necessarily white)
3798                    1    player 1 rating
3799                    2    empty, white, or black (IGNORED)
3800                    3    player 2 name (not necessarily black)
3801                    4    player 2 rating
3802
3803                    The names/ratings are sorted out when the game
3804                    actually starts (below).
3805                 */
3806                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3807                 player1Rating = string_to_rating(star_match[1]);
3808                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3809                 player2Rating = string_to_rating(star_match[4]);
3810
3811                 if (appData.debugMode)
3812                   fprintf(debugFP,
3813                           "Ratings from 'Creating:' %s %d, %s %d\n",
3814                           player1Name, player1Rating,
3815                           player2Name, player2Rating);
3816
3817                 continue;
3818             }
3819
3820             /* Improved generic start/end-of-game messages */
3821             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3822                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3823                 /* If tkind == 0: */
3824                 /* star_match[0] is the game number */
3825                 /*           [1] is the white player's name */
3826                 /*           [2] is the black player's name */
3827                 /* For end-of-game: */
3828                 /*           [3] is the reason for the game end */
3829                 /*           [4] is a PGN end game-token, preceded by " " */
3830                 /* For start-of-game: */
3831                 /*           [3] begins with "Creating" or "Continuing" */
3832                 /*           [4] is " *" or empty (don't care). */
3833                 int gamenum = atoi(star_match[0]);
3834                 char *whitename, *blackname, *why, *endtoken;
3835                 ChessMove endtype = EndOfFile;
3836
3837                 if (tkind == 0) {
3838                   whitename = star_match[1];
3839                   blackname = star_match[2];
3840                   why = star_match[3];
3841                   endtoken = star_match[4];
3842                 } else {
3843                   whitename = star_match[1];
3844                   blackname = star_match[3];
3845                   why = star_match[5];
3846                   endtoken = star_match[6];
3847                 }
3848
3849                 /* Game start messages */
3850                 if (strncmp(why, "Creating ", 9) == 0 ||
3851                     strncmp(why, "Continuing ", 11) == 0) {
3852                     gs_gamenum = gamenum;
3853                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3854                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3855 #if ZIPPY
3856                     if (appData.zippyPlay) {
3857                         ZippyGameStart(whitename, blackname);
3858                     }
3859 #endif /*ZIPPY*/
3860                     partnerBoardValid = FALSE; // [HGM] bughouse
3861                     continue;
3862                 }
3863
3864                 /* Game end messages */
3865                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3866                     ics_gamenum != gamenum) {
3867                     continue;
3868                 }
3869                 while (endtoken[0] == ' ') endtoken++;
3870                 switch (endtoken[0]) {
3871                   case '*':
3872                   default:
3873                     endtype = GameUnfinished;
3874                     break;
3875                   case '0':
3876                     endtype = BlackWins;
3877                     break;
3878                   case '1':
3879                     if (endtoken[1] == '/')
3880                       endtype = GameIsDrawn;
3881                     else
3882                       endtype = WhiteWins;
3883                     break;
3884                 }
3885                 GameEnds(endtype, why, GE_ICS);
3886 #if ZIPPY
3887                 if (appData.zippyPlay && first.initDone) {
3888                     ZippyGameEnd(endtype, why);
3889                     if (first.pr == NULL) {
3890                       /* Start the next process early so that we'll
3891                          be ready for the next challenge */
3892                       StartChessProgram(&first);
3893                     }
3894                     /* Send "new" early, in case this command takes
3895                        a long time to finish, so that we'll be ready
3896                        for the next challenge. */
3897                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3898                     Reset(TRUE, TRUE);
3899                 }
3900 #endif /*ZIPPY*/
3901                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3902                 continue;
3903             }
3904
3905             if (looking_at(buf, &i, "Removing game * from observation") ||
3906                 looking_at(buf, &i, "no longer observing game *") ||
3907                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3908                 if (gameMode == IcsObserving &&
3909                     atoi(star_match[0]) == ics_gamenum)
3910                   {
3911                       /* icsEngineAnalyze */
3912                       if (appData.icsEngineAnalyze) {
3913                             ExitAnalyzeMode();
3914                             ModeHighlight();
3915                       }
3916                       StopClocks();
3917                       gameMode = IcsIdle;
3918                       ics_gamenum = -1;
3919                       ics_user_moved = FALSE;
3920                   }
3921                 continue;
3922             }
3923
3924             if (looking_at(buf, &i, "no longer examining game *")) {
3925                 if (gameMode == IcsExamining &&
3926                     atoi(star_match[0]) == ics_gamenum)
3927                   {
3928                       gameMode = IcsIdle;
3929                       ics_gamenum = -1;
3930                       ics_user_moved = FALSE;
3931                   }
3932                 continue;
3933             }
3934
3935             /* Advance leftover_start past any newlines we find,
3936                so only partial lines can get reparsed */
3937             if (looking_at(buf, &i, "\n")) {
3938                 prevColor = curColor;
3939                 if (curColor != ColorNormal) {
3940                     if (oldi > next_out) {
3941                         SendToPlayer(&buf[next_out], oldi - next_out);
3942                         next_out = oldi;
3943                     }
3944                     Colorize(ColorNormal, FALSE);
3945                     curColor = ColorNormal;
3946                 }
3947                 if (started == STARTED_BOARD) {
3948                     started = STARTED_NONE;
3949                     parse[parse_pos] = NULLCHAR;
3950                     ParseBoard12(parse);
3951                     ics_user_moved = 0;
3952
3953                     /* Send premove here */
3954                     if (appData.premove) {
3955                       char str[MSG_SIZ];
3956                       if (currentMove == 0 &&
3957                           gameMode == IcsPlayingWhite &&
3958                           appData.premoveWhite) {
3959                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3960                         if (appData.debugMode)
3961                           fprintf(debugFP, "Sending premove:\n");
3962                         SendToICS(str);
3963                       } else if (currentMove == 1 &&
3964                                  gameMode == IcsPlayingBlack &&
3965                                  appData.premoveBlack) {
3966                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3967                         if (appData.debugMode)
3968                           fprintf(debugFP, "Sending premove:\n");
3969                         SendToICS(str);
3970                       } else if (gotPremove) {
3971                         gotPremove = 0;
3972                         ClearPremoveHighlights();
3973                         if (appData.debugMode)
3974                           fprintf(debugFP, "Sending premove:\n");
3975                           UserMoveEvent(premoveFromX, premoveFromY,
3976                                         premoveToX, premoveToY,
3977                                         premovePromoChar);
3978                       }
3979                     }
3980
3981                     /* Usually suppress following prompt */
3982                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3983                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3984                         if (looking_at(buf, &i, "*% ")) {
3985                             savingComment = FALSE;
3986                             suppressKibitz = 0;
3987                         }
3988                     }
3989                     next_out = i;
3990                 } else if (started == STARTED_HOLDINGS) {
3991                     int gamenum;
3992                     char new_piece[MSG_SIZ];
3993                     started = STARTED_NONE;
3994                     parse[parse_pos] = NULLCHAR;
3995                     if (appData.debugMode)
3996                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3997                                                         parse, currentMove);
3998                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3999                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4000                         if (gameInfo.variant == VariantNormal) {
4001                           /* [HGM] We seem to switch variant during a game!
4002                            * Presumably no holdings were displayed, so we have
4003                            * to move the position two files to the right to
4004                            * create room for them!
4005                            */
4006                           VariantClass newVariant;
4007                           switch(gameInfo.boardWidth) { // base guess on board width
4008                                 case 9:  newVariant = VariantShogi; break;
4009                                 case 10: newVariant = VariantGreat; break;
4010                                 default: newVariant = VariantCrazyhouse; break;
4011                           }
4012                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4013                           /* Get a move list just to see the header, which
4014                              will tell us whether this is really bug or zh */
4015                           if (ics_getting_history == H_FALSE) {
4016                             ics_getting_history = H_REQUESTED;
4017                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4018                             SendToICS(str);
4019                           }
4020                         }
4021                         new_piece[0] = NULLCHAR;
4022                         sscanf(parse, "game %d white [%s black [%s <- %s",
4023                                &gamenum, white_holding, black_holding,
4024                                new_piece);
4025                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4026                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4027                         /* [HGM] copy holdings to board holdings area */
4028                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4029                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4030                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4031 #if ZIPPY
4032                         if (appData.zippyPlay && first.initDone) {
4033                             ZippyHoldings(white_holding, black_holding,
4034                                           new_piece);
4035                         }
4036 #endif /*ZIPPY*/
4037                         if (tinyLayout || smallLayout) {
4038                             char wh[16], bh[16];
4039                             PackHolding(wh, white_holding);
4040                             PackHolding(bh, black_holding);
4041                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4042                                     gameInfo.white, gameInfo.black);
4043                         } else {
4044                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4045                                     gameInfo.white, white_holding,
4046                                     gameInfo.black, black_holding);
4047                         }
4048                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4049                         DrawPosition(FALSE, boards[currentMove]);
4050                         DisplayTitle(str);
4051                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4052                         sscanf(parse, "game %d white [%s black [%s <- %s",
4053                                &gamenum, white_holding, black_holding,
4054                                new_piece);
4055                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4056                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4057                         /* [HGM] copy holdings to partner-board holdings area */
4058                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4059                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4060                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4061                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4062                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4063                       }
4064                     }
4065                     /* Suppress following prompt */
4066                     if (looking_at(buf, &i, "*% ")) {
4067                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4068                         savingComment = FALSE;
4069                         suppressKibitz = 0;
4070                     }
4071                     next_out = i;
4072                 }
4073                 continue;
4074             }
4075
4076             i++;                /* skip unparsed character and loop back */
4077         }
4078
4079         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4080 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4081 //          SendToPlayer(&buf[next_out], i - next_out);
4082             started != STARTED_HOLDINGS && leftover_start > next_out) {
4083             SendToPlayer(&buf[next_out], leftover_start - next_out);
4084             next_out = i;
4085         }
4086
4087         leftover_len = buf_len - leftover_start;
4088         /* if buffer ends with something we couldn't parse,
4089            reparse it after appending the next read */
4090
4091     } else if (count == 0) {
4092         RemoveInputSource(isr);
4093         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4094     } else {
4095         DisplayFatalError(_("Error reading from ICS"), error, 1);
4096     }
4097 }
4098
4099
4100 /* Board style 12 looks like this:
4101
4102    <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
4103
4104  * The "<12> " is stripped before it gets to this routine.  The two
4105  * trailing 0's (flip state and clock ticking) are later addition, and
4106  * some chess servers may not have them, or may have only the first.
4107  * Additional trailing fields may be added in the future.
4108  */
4109
4110 #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"
4111
4112 #define RELATION_OBSERVING_PLAYED    0
4113 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4114 #define RELATION_PLAYING_MYMOVE      1
4115 #define RELATION_PLAYING_NOTMYMOVE  -1
4116 #define RELATION_EXAMINING           2
4117 #define RELATION_ISOLATED_BOARD     -3
4118 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4119
4120 void
4121 ParseBoard12(string)
4122      char *string;
4123 {
4124     GameMode newGameMode;
4125     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4126     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4127     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4128     char to_play, board_chars[200];
4129     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4130     char black[32], white[32];
4131     Board board;
4132     int prevMove = currentMove;
4133     int ticking = 2;
4134     ChessMove moveType;
4135     int fromX, fromY, toX, toY;
4136     char promoChar;
4137     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4138     char *bookHit = NULL; // [HGM] book
4139     Boolean weird = FALSE, reqFlag = FALSE;
4140
4141     fromX = fromY = toX = toY = -1;
4142
4143     newGame = FALSE;
4144
4145     if (appData.debugMode)
4146       fprintf(debugFP, _("Parsing board: %s\n"), string);
4147
4148     move_str[0] = NULLCHAR;
4149     elapsed_time[0] = NULLCHAR;
4150     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4151         int  i = 0, j;
4152         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4153             if(string[i] == ' ') { ranks++; files = 0; }
4154             else files++;
4155             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4156             i++;
4157         }
4158         for(j = 0; j <i; j++) board_chars[j] = string[j];
4159         board_chars[i] = '\0';
4160         string += i + 1;
4161     }
4162     n = sscanf(string, PATTERN, &to_play, &double_push,
4163                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4164                &gamenum, white, black, &relation, &basetime, &increment,
4165                &white_stren, &black_stren, &white_time, &black_time,
4166                &moveNum, str, elapsed_time, move_str, &ics_flip,
4167                &ticking);
4168
4169     if (n < 21) {
4170         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4171         DisplayError(str, 0);
4172         return;
4173     }
4174
4175     /* Convert the move number to internal form */
4176     moveNum = (moveNum - 1) * 2;
4177     if (to_play == 'B') moveNum++;
4178     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4179       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4180                         0, 1);
4181       return;
4182     }
4183
4184     switch (relation) {
4185       case RELATION_OBSERVING_PLAYED:
4186       case RELATION_OBSERVING_STATIC:
4187         if (gamenum == -1) {
4188             /* Old ICC buglet */
4189             relation = RELATION_OBSERVING_STATIC;
4190         }
4191         newGameMode = IcsObserving;
4192         break;
4193       case RELATION_PLAYING_MYMOVE:
4194       case RELATION_PLAYING_NOTMYMOVE:
4195         newGameMode =
4196           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4197             IcsPlayingWhite : IcsPlayingBlack;
4198         break;
4199       case RELATION_EXAMINING:
4200         newGameMode = IcsExamining;
4201         break;
4202       case RELATION_ISOLATED_BOARD:
4203       default:
4204         /* Just display this board.  If user was doing something else,
4205            we will forget about it until the next board comes. */
4206         newGameMode = IcsIdle;
4207         break;
4208       case RELATION_STARTING_POSITION:
4209         newGameMode = gameMode;
4210         break;
4211     }
4212
4213     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4214          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4215       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4216       char *toSqr;
4217       for (k = 0; k < ranks; k++) {
4218         for (j = 0; j < files; j++)
4219           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4220         if(gameInfo.holdingsWidth > 1) {
4221              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4222              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4223         }
4224       }
4225       CopyBoard(partnerBoard, board);
4226       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4227         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4228         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4229       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4230       if(toSqr = strchr(str, '-')) {
4231         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4232         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4233       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4234       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4235       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4236       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4237       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4238       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4239                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4240       DisplayMessage(partnerStatus, "");
4241         partnerBoardValid = TRUE;
4242       return;
4243     }
4244
4245     /* Modify behavior for initial board display on move listing
4246        of wild games.
4247        */
4248     switch (ics_getting_history) {
4249       case H_FALSE:
4250       case H_REQUESTED:
4251         break;
4252       case H_GOT_REQ_HEADER:
4253       case H_GOT_UNREQ_HEADER:
4254         /* This is the initial position of the current game */
4255         gamenum = ics_gamenum;
4256         moveNum = 0;            /* old ICS bug workaround */
4257         if (to_play == 'B') {
4258           startedFromSetupPosition = TRUE;
4259           blackPlaysFirst = TRUE;
4260           moveNum = 1;
4261           if (forwardMostMove == 0) forwardMostMove = 1;
4262           if (backwardMostMove == 0) backwardMostMove = 1;
4263           if (currentMove == 0) currentMove = 1;
4264         }
4265         newGameMode = gameMode;
4266         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4267         break;
4268       case H_GOT_UNWANTED_HEADER:
4269         /* This is an initial board that we don't want */
4270         return;
4271       case H_GETTING_MOVES:
4272         /* Should not happen */
4273         DisplayError(_("Error gathering move list: extra board"), 0);
4274         ics_getting_history = H_FALSE;
4275         return;
4276     }
4277
4278    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4279                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4280      /* [HGM] We seem to have switched variant unexpectedly
4281       * Try to guess new variant from board size
4282       */
4283           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4284           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4285           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4286           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4287           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4288           if(!weird) newVariant = VariantNormal;
4289           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4290           /* Get a move list just to see the header, which
4291              will tell us whether this is really bug or zh */
4292           if (ics_getting_history == H_FALSE) {
4293             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4294             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4295             SendToICS(str);
4296           }
4297     }
4298
4299     /* Take action if this is the first board of a new game, or of a
4300        different game than is currently being displayed.  */
4301     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4302         relation == RELATION_ISOLATED_BOARD) {
4303
4304         /* Forget the old game and get the history (if any) of the new one */
4305         if (gameMode != BeginningOfGame) {
4306           Reset(TRUE, TRUE);
4307         }
4308         newGame = TRUE;
4309         if (appData.autoRaiseBoard) BoardToTop();
4310         prevMove = -3;
4311         if (gamenum == -1) {
4312             newGameMode = IcsIdle;
4313         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4314                    appData.getMoveList && !reqFlag) {
4315             /* Need to get game history */
4316             ics_getting_history = H_REQUESTED;
4317             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4318             SendToICS(str);
4319         }
4320
4321         /* Initially flip the board to have black on the bottom if playing
4322            black or if the ICS flip flag is set, but let the user change
4323            it with the Flip View button. */
4324         flipView = appData.autoFlipView ?
4325           (newGameMode == IcsPlayingBlack) || ics_flip :
4326           appData.flipView;
4327
4328         /* Done with values from previous mode; copy in new ones */
4329         gameMode = newGameMode;
4330         ModeHighlight();
4331         ics_gamenum = gamenum;
4332         if (gamenum == gs_gamenum) {
4333             int klen = strlen(gs_kind);
4334             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4335             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4336             gameInfo.event = StrSave(str);
4337         } else {
4338             gameInfo.event = StrSave("ICS game");
4339         }
4340         gameInfo.site = StrSave(appData.icsHost);
4341         gameInfo.date = PGNDate();
4342         gameInfo.round = StrSave("-");
4343         gameInfo.white = StrSave(white);
4344         gameInfo.black = StrSave(black);
4345         timeControl = basetime * 60 * 1000;
4346         timeControl_2 = 0;
4347         timeIncrement = increment * 1000;
4348         movesPerSession = 0;
4349         gameInfo.timeControl = TimeControlTagValue();
4350         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4351   if (appData.debugMode) {
4352     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4353     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4354     setbuf(debugFP, NULL);
4355   }
4356
4357         gameInfo.outOfBook = NULL;
4358
4359         /* Do we have the ratings? */
4360         if (strcmp(player1Name, white) == 0 &&
4361             strcmp(player2Name, black) == 0) {
4362             if (appData.debugMode)
4363               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4364                       player1Rating, player2Rating);
4365             gameInfo.whiteRating = player1Rating;
4366             gameInfo.blackRating = player2Rating;
4367         } else if (strcmp(player2Name, white) == 0 &&
4368                    strcmp(player1Name, black) == 0) {
4369             if (appData.debugMode)
4370               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4371                       player2Rating, player1Rating);
4372             gameInfo.whiteRating = player2Rating;
4373             gameInfo.blackRating = player1Rating;
4374         }
4375         player1Name[0] = player2Name[0] = NULLCHAR;
4376
4377         /* Silence shouts if requested */
4378         if (appData.quietPlay &&
4379             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4380             SendToICS(ics_prefix);
4381             SendToICS("set shout 0\n");
4382         }
4383     }
4384
4385     /* Deal with midgame name changes */
4386     if (!newGame) {
4387         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4388             if (gameInfo.white) free(gameInfo.white);
4389             gameInfo.white = StrSave(white);
4390         }
4391         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4392             if (gameInfo.black) free(gameInfo.black);
4393             gameInfo.black = StrSave(black);
4394         }
4395     }
4396
4397     /* Throw away game result if anything actually changes in examine mode */
4398     if (gameMode == IcsExamining && !newGame) {
4399         gameInfo.result = GameUnfinished;
4400         if (gameInfo.resultDetails != NULL) {
4401             free(gameInfo.resultDetails);
4402             gameInfo.resultDetails = NULL;
4403         }
4404     }
4405
4406     /* In pausing && IcsExamining mode, we ignore boards coming
4407        in if they are in a different variation than we are. */
4408     if (pauseExamInvalid) return;
4409     if (pausing && gameMode == IcsExamining) {
4410         if (moveNum <= pauseExamForwardMostMove) {
4411             pauseExamInvalid = TRUE;
4412             forwardMostMove = pauseExamForwardMostMove;
4413             return;
4414         }
4415     }
4416
4417   if (appData.debugMode) {
4418     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4419   }
4420     /* Parse the board */
4421     for (k = 0; k < ranks; k++) {
4422       for (j = 0; j < files; j++)
4423         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4424       if(gameInfo.holdingsWidth > 1) {
4425            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4426            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4427       }
4428     }
4429     CopyBoard(boards[moveNum], board);
4430     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4431     if (moveNum == 0) {
4432         startedFromSetupPosition =
4433           !CompareBoards(board, initialPosition);
4434         if(startedFromSetupPosition)
4435             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4436     }
4437
4438     /* [HGM] Set castling rights. Take the outermost Rooks,
4439        to make it also work for FRC opening positions. Note that board12
4440        is really defective for later FRC positions, as it has no way to
4441        indicate which Rook can castle if they are on the same side of King.
4442        For the initial position we grant rights to the outermost Rooks,
4443        and remember thos rights, and we then copy them on positions
4444        later in an FRC game. This means WB might not recognize castlings with
4445        Rooks that have moved back to their original position as illegal,
4446        but in ICS mode that is not its job anyway.
4447     */
4448     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4449     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4450
4451         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4452             if(board[0][i] == WhiteRook) j = i;
4453         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4454         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4455             if(board[0][i] == WhiteRook) j = i;
4456         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4457         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4458             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4459         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4460         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4461             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4462         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4463
4464         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4465         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4466             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4467         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4468             if(board[BOARD_HEIGHT-1][k] == bKing)
4469                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4470         if(gameInfo.variant == VariantTwoKings) {
4471             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4472             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4473             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4474         }
4475     } else { int r;
4476         r = boards[moveNum][CASTLING][0] = initialRights[0];
4477         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4478         r = boards[moveNum][CASTLING][1] = initialRights[1];
4479         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4480         r = boards[moveNum][CASTLING][3] = initialRights[3];
4481         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4482         r = boards[moveNum][CASTLING][4] = initialRights[4];
4483         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4484         /* wildcastle kludge: always assume King has rights */
4485         r = boards[moveNum][CASTLING][2] = initialRights[2];
4486         r = boards[moveNum][CASTLING][5] = initialRights[5];
4487     }
4488     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4489     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4490
4491
4492     if (ics_getting_history == H_GOT_REQ_HEADER ||
4493         ics_getting_history == H_GOT_UNREQ_HEADER) {
4494         /* This was an initial position from a move list, not
4495            the current position */
4496         return;
4497     }
4498
4499     /* Update currentMove and known move number limits */
4500     newMove = newGame || moveNum > forwardMostMove;
4501
4502     if (newGame) {
4503         forwardMostMove = backwardMostMove = currentMove = moveNum;
4504         if (gameMode == IcsExamining && moveNum == 0) {
4505           /* Workaround for ICS limitation: we are not told the wild
4506              type when starting to examine a game.  But if we ask for
4507              the move list, the move list header will tell us */
4508             ics_getting_history = H_REQUESTED;
4509             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4510             SendToICS(str);
4511         }
4512     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4513                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4514 #if ZIPPY
4515         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4516         /* [HGM] applied this also to an engine that is silently watching        */
4517         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4518             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4519             gameInfo.variant == currentlyInitializedVariant) {
4520           takeback = forwardMostMove - moveNum;
4521           for (i = 0; i < takeback; i++) {
4522             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4523             SendToProgram("undo\n", &first);
4524           }
4525         }
4526 #endif
4527
4528         forwardMostMove = moveNum;
4529         if (!pausing || currentMove > forwardMostMove)
4530           currentMove = forwardMostMove;
4531     } else {
4532         /* New part of history that is not contiguous with old part */
4533         if (pausing && gameMode == IcsExamining) {
4534             pauseExamInvalid = TRUE;
4535             forwardMostMove = pauseExamForwardMostMove;
4536             return;
4537         }
4538         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4539 #if ZIPPY
4540             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4541                 // [HGM] when we will receive the move list we now request, it will be
4542                 // fed to the engine from the first move on. So if the engine is not
4543                 // in the initial position now, bring it there.
4544                 InitChessProgram(&first, 0);
4545             }
4546 #endif
4547             ics_getting_history = H_REQUESTED;
4548             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4549             SendToICS(str);
4550         }
4551         forwardMostMove = backwardMostMove = currentMove = moveNum;
4552     }
4553
4554     /* Update the clocks */
4555     if (strchr(elapsed_time, '.')) {
4556       /* Time is in ms */
4557       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4558       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4559     } else {
4560       /* Time is in seconds */
4561       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4562       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4563     }
4564
4565
4566 #if ZIPPY
4567     if (appData.zippyPlay && newGame &&
4568         gameMode != IcsObserving && gameMode != IcsIdle &&
4569         gameMode != IcsExamining)
4570       ZippyFirstBoard(moveNum, basetime, increment);
4571 #endif
4572
4573     /* Put the move on the move list, first converting
4574        to canonical algebraic form. */
4575     if (moveNum > 0) {
4576   if (appData.debugMode) {
4577     if (appData.debugMode) { int f = forwardMostMove;
4578         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4579                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4580                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4581     }
4582     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4583     fprintf(debugFP, "moveNum = %d\n", moveNum);
4584     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4585     setbuf(debugFP, NULL);
4586   }
4587         if (moveNum <= backwardMostMove) {
4588             /* We don't know what the board looked like before
4589                this move.  Punt. */
4590           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4591             strcat(parseList[moveNum - 1], " ");
4592             strcat(parseList[moveNum - 1], elapsed_time);
4593             moveList[moveNum - 1][0] = NULLCHAR;
4594         } else if (strcmp(move_str, "none") == 0) {
4595             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4596             /* Again, we don't know what the board looked like;
4597                this is really the start of the game. */
4598             parseList[moveNum - 1][0] = NULLCHAR;
4599             moveList[moveNum - 1][0] = NULLCHAR;
4600             backwardMostMove = moveNum;
4601             startedFromSetupPosition = TRUE;
4602             fromX = fromY = toX = toY = -1;
4603         } else {
4604           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4605           //                 So we parse the long-algebraic move string in stead of the SAN move
4606           int valid; char buf[MSG_SIZ], *prom;
4607
4608           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4609                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4610           // str looks something like "Q/a1-a2"; kill the slash
4611           if(str[1] == '/')
4612             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4613           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4614           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4615                 strcat(buf, prom); // long move lacks promo specification!
4616           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4617                 if(appData.debugMode)
4618                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4619                 safeStrCpy(move_str, buf, MSG_SIZ);
4620           }
4621           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4622                                 &fromX, &fromY, &toX, &toY, &promoChar)
4623                || ParseOneMove(buf, moveNum - 1, &moveType,
4624                                 &fromX, &fromY, &toX, &toY, &promoChar);
4625           // end of long SAN patch
4626           if (valid) {
4627             (void) CoordsToAlgebraic(boards[moveNum - 1],
4628                                      PosFlags(moveNum - 1),
4629                                      fromY, fromX, toY, toX, promoChar,
4630                                      parseList[moveNum-1]);
4631             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4632               case MT_NONE:
4633               case MT_STALEMATE:
4634               default:
4635                 break;
4636               case MT_CHECK:
4637                 if(gameInfo.variant != VariantShogi)
4638                     strcat(parseList[moveNum - 1], "+");
4639                 break;
4640               case MT_CHECKMATE:
4641               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4642                 strcat(parseList[moveNum - 1], "#");
4643                 break;
4644             }
4645             strcat(parseList[moveNum - 1], " ");
4646             strcat(parseList[moveNum - 1], elapsed_time);
4647             /* currentMoveString is set as a side-effect of ParseOneMove */
4648             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4649             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4650             strcat(moveList[moveNum - 1], "\n");
4651
4652             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4653                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4654               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4655                 ChessSquare old, new = boards[moveNum][k][j];
4656                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4657                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4658                   if(old == new) continue;
4659                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4660                   else if(new == WhiteWazir || new == BlackWazir) {
4661                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4662                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4663                       else boards[moveNum][k][j] = old; // preserve type of Gold
4664                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4665                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4666               }
4667           } else {
4668             /* Move from ICS was illegal!?  Punt. */
4669             if (appData.debugMode) {
4670               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4671               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4672             }
4673             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4674             strcat(parseList[moveNum - 1], " ");
4675             strcat(parseList[moveNum - 1], elapsed_time);
4676             moveList[moveNum - 1][0] = NULLCHAR;
4677             fromX = fromY = toX = toY = -1;
4678           }
4679         }
4680   if (appData.debugMode) {
4681     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4682     setbuf(debugFP, NULL);
4683   }
4684
4685 #if ZIPPY
4686         /* Send move to chess program (BEFORE animating it). */
4687         if (appData.zippyPlay && !newGame && newMove &&
4688            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4689
4690             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4691                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4692                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4693                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4694                             move_str);
4695                     DisplayError(str, 0);
4696                 } else {
4697                     if (first.sendTime) {
4698                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4699                     }
4700                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4701                     if (firstMove && !bookHit) {
4702                         firstMove = FALSE;
4703                         if (first.useColors) {
4704                           SendToProgram(gameMode == IcsPlayingWhite ?
4705                                         "white\ngo\n" :
4706                                         "black\ngo\n", &first);
4707                         } else {
4708                           SendToProgram("go\n", &first);
4709                         }
4710                         first.maybeThinking = TRUE;
4711                     }
4712                 }
4713             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4714               if (moveList[moveNum - 1][0] == NULLCHAR) {
4715                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4716                 DisplayError(str, 0);
4717               } else {
4718                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4719                 SendMoveToProgram(moveNum - 1, &first);
4720               }
4721             }
4722         }
4723 #endif
4724     }
4725
4726     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4727         /* If move comes from a remote source, animate it.  If it
4728            isn't remote, it will have already been animated. */
4729         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4730             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4731         }
4732         if (!pausing && appData.highlightLastMove) {
4733             SetHighlights(fromX, fromY, toX, toY);
4734         }
4735     }
4736
4737     /* Start the clocks */
4738     whiteFlag = blackFlag = FALSE;
4739     appData.clockMode = !(basetime == 0 && increment == 0);
4740     if (ticking == 0) {
4741       ics_clock_paused = TRUE;
4742       StopClocks();
4743     } else if (ticking == 1) {
4744       ics_clock_paused = FALSE;
4745     }
4746     if (gameMode == IcsIdle ||
4747         relation == RELATION_OBSERVING_STATIC ||
4748         relation == RELATION_EXAMINING ||
4749         ics_clock_paused)
4750       DisplayBothClocks();
4751     else
4752       StartClocks();
4753
4754     /* Display opponents and material strengths */
4755     if (gameInfo.variant != VariantBughouse &&
4756         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4757         if (tinyLayout || smallLayout) {
4758             if(gameInfo.variant == VariantNormal)
4759               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4760                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4761                     basetime, increment);
4762             else
4763               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4764                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4765                     basetime, increment, (int) gameInfo.variant);
4766         } else {
4767             if(gameInfo.variant == VariantNormal)
4768               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4769                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4770                     basetime, increment);
4771             else
4772               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4773                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4774                     basetime, increment, VariantName(gameInfo.variant));
4775         }
4776         DisplayTitle(str);
4777   if (appData.debugMode) {
4778     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4779   }
4780     }
4781
4782
4783     /* Display the board */
4784     if (!pausing && !appData.noGUI) {
4785
4786       if (appData.premove)
4787           if (!gotPremove ||
4788              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4789              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4790               ClearPremoveHighlights();
4791
4792       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4793         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4794       DrawPosition(j, boards[currentMove]);
4795
4796       DisplayMove(moveNum - 1);
4797       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4798             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4799               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4800         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4801       }
4802     }
4803
4804     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4805 #if ZIPPY
4806     if(bookHit) { // [HGM] book: simulate book reply
4807         static char bookMove[MSG_SIZ]; // a bit generous?
4808
4809         programStats.nodes = programStats.depth = programStats.time =
4810         programStats.score = programStats.got_only_move = 0;
4811         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4812
4813         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4814         strcat(bookMove, bookHit);
4815         HandleMachineMove(bookMove, &first);
4816     }
4817 #endif
4818 }
4819
4820 void
4821 GetMoveListEvent()
4822 {
4823     char buf[MSG_SIZ];
4824     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4825         ics_getting_history = H_REQUESTED;
4826         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4827         SendToICS(buf);
4828     }
4829 }
4830
4831 void
4832 AnalysisPeriodicEvent(force)
4833      int force;
4834 {
4835     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4836          && !force) || !appData.periodicUpdates)
4837       return;
4838
4839     /* Send . command to Crafty to collect stats */
4840     SendToProgram(".\n", &first);
4841
4842     /* Don't send another until we get a response (this makes
4843        us stop sending to old Crafty's which don't understand
4844        the "." command (sending illegal cmds resets node count & time,
4845        which looks bad)) */
4846     programStats.ok_to_send = 0;
4847 }
4848
4849 void ics_update_width(new_width)
4850         int new_width;
4851 {
4852         ics_printf("set width %d\n", new_width);
4853 }
4854
4855 void
4856 SendMoveToProgram(moveNum, cps)
4857      int moveNum;
4858      ChessProgramState *cps;
4859 {
4860     char buf[MSG_SIZ];
4861
4862     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4863         // null move in variant where engine does not understand it (for analysis purposes)
4864         SendBoard(cps, moveNum + 1); // send position after move in stead.
4865         return;
4866     }
4867     if (cps->useUsermove) {
4868       SendToProgram("usermove ", cps);
4869     }
4870     if (cps->useSAN) {
4871       char *space;
4872       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4873         int len = space - parseList[moveNum];
4874         memcpy(buf, parseList[moveNum], len);
4875         buf[len++] = '\n';
4876         buf[len] = NULLCHAR;
4877       } else {
4878         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4879       }
4880       SendToProgram(buf, cps);
4881     } else {
4882       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4883         AlphaRank(moveList[moveNum], 4);
4884         SendToProgram(moveList[moveNum], cps);
4885         AlphaRank(moveList[moveNum], 4); // and back
4886       } else
4887       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4888        * the engine. It would be nice to have a better way to identify castle
4889        * moves here. */
4890       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4891                                                                          && cps->useOOCastle) {
4892         int fromX = moveList[moveNum][0] - AAA;
4893         int fromY = moveList[moveNum][1] - ONE;
4894         int toX = moveList[moveNum][2] - AAA;
4895         int toY = moveList[moveNum][3] - ONE;
4896         if((boards[moveNum][fromY][fromX] == WhiteKing
4897             && boards[moveNum][toY][toX] == WhiteRook)
4898            || (boards[moveNum][fromY][fromX] == BlackKing
4899                && boards[moveNum][toY][toX] == BlackRook)) {
4900           if(toX > fromX) SendToProgram("O-O\n", cps);
4901           else SendToProgram("O-O-O\n", cps);
4902         }
4903         else SendToProgram(moveList[moveNum], cps);
4904       } else
4905       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4906         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4907           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4908           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4909                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4910         } else
4911           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4912                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4913         SendToProgram(buf, cps);
4914       }
4915       else SendToProgram(moveList[moveNum], cps);
4916       /* End of additions by Tord */
4917     }
4918
4919     /* [HGM] setting up the opening has brought engine in force mode! */
4920     /*       Send 'go' if we are in a mode where machine should play. */
4921     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4922         (gameMode == TwoMachinesPlay   ||
4923 #if ZIPPY
4924          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4925 #endif
4926          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4927         SendToProgram("go\n", cps);
4928   if (appData.debugMode) {
4929     fprintf(debugFP, "(extra)\n");
4930   }
4931     }
4932     setboardSpoiledMachineBlack = 0;
4933 }
4934
4935 void
4936 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4937      ChessMove moveType;
4938      int fromX, fromY, toX, toY;
4939      char promoChar;
4940 {
4941     char user_move[MSG_SIZ];
4942
4943     switch (moveType) {
4944       default:
4945         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4946                 (int)moveType, fromX, fromY, toX, toY);
4947         DisplayError(user_move + strlen("say "), 0);
4948         break;
4949       case WhiteKingSideCastle:
4950       case BlackKingSideCastle:
4951       case WhiteQueenSideCastleWild:
4952       case BlackQueenSideCastleWild:
4953       /* PUSH Fabien */
4954       case WhiteHSideCastleFR:
4955       case BlackHSideCastleFR:
4956       /* POP Fabien */
4957         snprintf(user_move, MSG_SIZ, "o-o\n");
4958         break;
4959       case WhiteQueenSideCastle:
4960       case BlackQueenSideCastle:
4961       case WhiteKingSideCastleWild:
4962       case BlackKingSideCastleWild:
4963       /* PUSH Fabien */
4964       case WhiteASideCastleFR:
4965       case BlackASideCastleFR:
4966       /* POP Fabien */
4967         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4968         break;
4969       case WhiteNonPromotion:
4970       case BlackNonPromotion:
4971         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4972         break;
4973       case WhitePromotion:
4974       case BlackPromotion:
4975         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4976           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4977                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4978                 PieceToChar(WhiteFerz));
4979         else if(gameInfo.variant == VariantGreat)
4980           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4981                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4982                 PieceToChar(WhiteMan));
4983         else
4984           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4985                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4986                 promoChar);
4987         break;
4988       case WhiteDrop:
4989       case BlackDrop:
4990       drop:
4991         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4992                  ToUpper(PieceToChar((ChessSquare) fromX)),
4993                  AAA + toX, ONE + toY);
4994         break;
4995       case IllegalMove:  /* could be a variant we don't quite understand */
4996         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4997       case NormalMove:
4998       case WhiteCapturesEnPassant:
4999       case BlackCapturesEnPassant:
5000         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5001                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5002         break;
5003     }
5004     SendToICS(user_move);
5005     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5006         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5007 }
5008
5009 void
5010 UploadGameEvent()
5011 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5012     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5013     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5014     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5015         DisplayError("You cannot do this while you are playing or observing", 0);
5016         return;
5017     }
5018     if(gameMode != IcsExamining) { // is this ever not the case?
5019         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5020
5021         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5022           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5023         } else { // on FICS we must first go to general examine mode
5024           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5025         }
5026         if(gameInfo.variant != VariantNormal) {
5027             // try figure out wild number, as xboard names are not always valid on ICS
5028             for(i=1; i<=36; i++) {
5029               snprintf(buf, MSG_SIZ, "wild/%d", i);
5030                 if(StringToVariant(buf) == gameInfo.variant) break;
5031             }
5032             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5033             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5034             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5035         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5036         SendToICS(ics_prefix);
5037         SendToICS(buf);
5038         if(startedFromSetupPosition || backwardMostMove != 0) {
5039           fen = PositionToFEN(backwardMostMove, NULL);
5040           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5041             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5042             SendToICS(buf);
5043           } else { // FICS: everything has to set by separate bsetup commands
5044             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5045             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5046             SendToICS(buf);
5047             if(!WhiteOnMove(backwardMostMove)) {
5048                 SendToICS("bsetup tomove black\n");
5049             }
5050             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5051             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5052             SendToICS(buf);
5053             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5054             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5055             SendToICS(buf);
5056             i = boards[backwardMostMove][EP_STATUS];
5057             if(i >= 0) { // set e.p.
5058               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5059                 SendToICS(buf);
5060             }
5061             bsetup++;
5062           }
5063         }
5064       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5065             SendToICS("bsetup done\n"); // switch to normal examining.
5066     }
5067     for(i = backwardMostMove; i<last; i++) {
5068         char buf[20];
5069         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5070         SendToICS(buf);
5071     }
5072     SendToICS(ics_prefix);
5073     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5074 }
5075
5076 void
5077 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5078      int rf, ff, rt, ft;
5079      char promoChar;
5080      char move[7];
5081 {
5082     if (rf == DROP_RANK) {
5083       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5084       sprintf(move, "%c@%c%c\n",
5085                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5086     } else {
5087         if (promoChar == 'x' || promoChar == NULLCHAR) {
5088           sprintf(move, "%c%c%c%c\n",
5089                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5090         } else {
5091             sprintf(move, "%c%c%c%c%c\n",
5092                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5093         }
5094     }
5095 }
5096
5097 void
5098 ProcessICSInitScript(f)
5099      FILE *f;
5100 {
5101     char buf[MSG_SIZ];
5102
5103     while (fgets(buf, MSG_SIZ, f)) {
5104         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5105     }
5106
5107     fclose(f);
5108 }
5109
5110
5111 static int lastX, lastY, selectFlag, dragging;
5112
5113 void
5114 Sweep(int step)
5115 {
5116     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5117     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5118     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5119     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5120     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5121     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5122     do {
5123         promoSweep -= step;
5124         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5125         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5126         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5127         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5128         if(!step) step = 1;
5129     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5130             appData.testLegality && (promoSweep == king ||
5131             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5132     ChangeDragPiece(promoSweep);
5133 }
5134
5135 int PromoScroll(int x, int y)
5136 {
5137   int step = 0;
5138
5139   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5140   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5141   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5142   if(!step) return FALSE;
5143   lastX = x; lastY = y;
5144   if((promoSweep < BlackPawn) == flipView) step = -step;
5145   if(step > 0) selectFlag = 1;
5146   if(!selectFlag) Sweep(step);
5147   return FALSE;
5148 }
5149
5150 void
5151 NextPiece(int step)
5152 {
5153     ChessSquare piece = boards[currentMove][toY][toX];
5154     do {
5155         pieceSweep -= step;
5156         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5157         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5158         if(!step) step = -1;
5159     } while(PieceToChar(pieceSweep) == '.');
5160     boards[currentMove][toY][toX] = pieceSweep;
5161     DrawPosition(FALSE, boards[currentMove]);
5162     boards[currentMove][toY][toX] = piece;
5163 }
5164 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5165 void
5166 AlphaRank(char *move, int n)
5167 {
5168 //    char *p = move, c; int x, y;
5169
5170     if (appData.debugMode) {
5171         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5172     }
5173
5174     if(move[1]=='*' &&
5175        move[2]>='0' && move[2]<='9' &&
5176        move[3]>='a' && move[3]<='x'    ) {
5177         move[1] = '@';
5178         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5179         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5180     } else
5181     if(move[0]>='0' && move[0]<='9' &&
5182        move[1]>='a' && move[1]<='x' &&
5183        move[2]>='0' && move[2]<='9' &&
5184        move[3]>='a' && move[3]<='x'    ) {
5185         /* input move, Shogi -> normal */
5186         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5187         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5188         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5189         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5190     } else
5191     if(move[1]=='@' &&
5192        move[3]>='0' && move[3]<='9' &&
5193        move[2]>='a' && move[2]<='x'    ) {
5194         move[1] = '*';
5195         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5196         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5197     } else
5198     if(
5199        move[0]>='a' && move[0]<='x' &&
5200        move[3]>='0' && move[3]<='9' &&
5201        move[2]>='a' && move[2]<='x'    ) {
5202          /* output move, normal -> Shogi */
5203         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5204         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5205         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5206         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5207         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5208     }
5209     if (appData.debugMode) {
5210         fprintf(debugFP, "   out = '%s'\n", move);
5211     }
5212 }
5213
5214 char yy_textstr[8000];
5215
5216 /* Parser for moves from gnuchess, ICS, or user typein box */
5217 Boolean
5218 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5219      char *move;
5220      int moveNum;
5221      ChessMove *moveType;
5222      int *fromX, *fromY, *toX, *toY;
5223      char *promoChar;
5224 {
5225     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5226
5227     switch (*moveType) {
5228       case WhitePromotion:
5229       case BlackPromotion:
5230       case WhiteNonPromotion:
5231       case BlackNonPromotion:
5232       case NormalMove:
5233       case WhiteCapturesEnPassant:
5234       case BlackCapturesEnPassant:
5235       case WhiteKingSideCastle:
5236       case WhiteQueenSideCastle:
5237       case BlackKingSideCastle:
5238       case BlackQueenSideCastle:
5239       case WhiteKingSideCastleWild:
5240       case WhiteQueenSideCastleWild:
5241       case BlackKingSideCastleWild:
5242       case BlackQueenSideCastleWild:
5243       /* Code added by Tord: */
5244       case WhiteHSideCastleFR:
5245       case WhiteASideCastleFR:
5246       case BlackHSideCastleFR:
5247       case BlackASideCastleFR:
5248       /* End of code added by Tord */
5249       case IllegalMove:         /* bug or odd chess variant */
5250         *fromX = currentMoveString[0] - AAA;
5251         *fromY = currentMoveString[1] - ONE;
5252         *toX = currentMoveString[2] - AAA;
5253         *toY = currentMoveString[3] - ONE;
5254         *promoChar = currentMoveString[4];
5255         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5256             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5257     if (appData.debugMode) {
5258         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5259     }
5260             *fromX = *fromY = *toX = *toY = 0;
5261             return FALSE;
5262         }
5263         if (appData.testLegality) {
5264           return (*moveType != IllegalMove);
5265         } else {
5266           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5267                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5268         }
5269
5270       case WhiteDrop:
5271       case BlackDrop:
5272         *fromX = *moveType == WhiteDrop ?
5273           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5274           (int) CharToPiece(ToLower(currentMoveString[0]));
5275         *fromY = DROP_RANK;
5276         *toX = currentMoveString[2] - AAA;
5277         *toY = currentMoveString[3] - ONE;
5278         *promoChar = NULLCHAR;
5279         return TRUE;
5280
5281       case AmbiguousMove:
5282       case ImpossibleMove:
5283       case EndOfFile:
5284       case ElapsedTime:
5285       case Comment:
5286       case PGNTag:
5287       case NAG:
5288       case WhiteWins:
5289       case BlackWins:
5290       case GameIsDrawn:
5291       default:
5292     if (appData.debugMode) {
5293         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5294     }
5295         /* bug? */
5296         *fromX = *fromY = *toX = *toY = 0;
5297         *promoChar = NULLCHAR;
5298         return FALSE;
5299     }
5300 }
5301
5302 Boolean pushed = FALSE;
5303 char *lastParseAttempt;
5304
5305 void
5306 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5307 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5308   int fromX, fromY, toX, toY; char promoChar;
5309   ChessMove moveType;
5310   Boolean valid;
5311   int nr = 0;
5312
5313   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5314     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5315     pushed = TRUE;
5316   }
5317   endPV = forwardMostMove;
5318   do {
5319     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5320     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5321     lastParseAttempt = pv;
5322     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5323 if(appData.debugMode){
5324 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);
5325 }
5326     if(!valid && nr == 0 &&
5327        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5328         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5329         // Hande case where played move is different from leading PV move
5330         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5331         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5332         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5333         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5334           endPV += 2; // if position different, keep this
5335           moveList[endPV-1][0] = fromX + AAA;
5336           moveList[endPV-1][1] = fromY + ONE;
5337           moveList[endPV-1][2] = toX + AAA;
5338           moveList[endPV-1][3] = toY + ONE;
5339           parseList[endPV-1][0] = NULLCHAR;
5340           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5341         }
5342       }
5343     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5344     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5345     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5346     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5347         valid++; // allow comments in PV
5348         continue;
5349     }
5350     nr++;
5351     if(endPV+1 > framePtr) break; // no space, truncate
5352     if(!valid) break;
5353     endPV++;
5354     CopyBoard(boards[endPV], boards[endPV-1]);
5355     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5356     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5357     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5358     CoordsToAlgebraic(boards[endPV - 1],
5359                              PosFlags(endPV - 1),
5360                              fromY, fromX, toY, toX, promoChar,
5361                              parseList[endPV - 1]);
5362   } while(valid);
5363   if(atEnd == 2) return; // used hidden, for PV conversion
5364   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5365   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5366   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5367                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5368   DrawPosition(TRUE, boards[currentMove]);
5369 }
5370
5371 int
5372 MultiPV(ChessProgramState *cps)
5373 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5374         int i;
5375         for(i=0; i<cps->nrOptions; i++)
5376             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5377                 return i;
5378         return -1;
5379 }
5380
5381 Boolean
5382 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5383 {
5384         int startPV, multi, lineStart, origIndex = index;
5385         char *p, buf2[MSG_SIZ];
5386
5387         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5388         lastX = x; lastY = y;
5389         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5390         lineStart = startPV = index;
5391         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5392         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5393         index = startPV;
5394         do{ while(buf[index] && buf[index] != '\n') index++;
5395         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5396         buf[index] = 0;
5397         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5398                 int n = first.option[multi].value;
5399                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5400                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5401                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5402                 first.option[multi].value = n;
5403                 *start = *end = 0;
5404                 return FALSE;
5405         }
5406         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5407         *start = startPV; *end = index-1;
5408         return TRUE;
5409 }
5410
5411 char *
5412 PvToSAN(char *pv)
5413 {
5414         static char buf[10*MSG_SIZ];
5415         int i, k=0, savedEnd=endPV;
5416         *buf = NULLCHAR;
5417         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5418         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5419         for(i = forwardMostMove; i<endPV; i++){
5420             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5421             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5422             k += strlen(buf+k);
5423         }
5424         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5425         if(forwardMostMove < savedEnd) PopInner(0);
5426         endPV = savedEnd;
5427         return buf;
5428 }
5429
5430 Boolean
5431 LoadPV(int x, int y)
5432 { // called on right mouse click to load PV
5433   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5434   lastX = x; lastY = y;
5435   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5436   return TRUE;
5437 }
5438
5439 void
5440 UnLoadPV()
5441 {
5442   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5443   if(endPV < 0) return;
5444   endPV = -1;
5445   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5446         Boolean saveAnimate = appData.animate;
5447         if(pushed) {
5448             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5449                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5450             } else storedGames--; // abandon shelved tail of original game
5451         }
5452         pushed = FALSE;
5453         forwardMostMove = currentMove;
5454         currentMove = oldFMM;
5455         appData.animate = FALSE;
5456         ToNrEvent(forwardMostMove);
5457         appData.animate = saveAnimate;
5458   }
5459   currentMove = forwardMostMove;
5460   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5461   ClearPremoveHighlights();
5462   DrawPosition(TRUE, boards[currentMove]);
5463 }
5464
5465 void
5466 MovePV(int x, int y, int h)
5467 { // step through PV based on mouse coordinates (called on mouse move)
5468   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5469
5470   // we must somehow check if right button is still down (might be released off board!)
5471   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5472   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5473   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5474   if(!step) return;
5475   lastX = x; lastY = y;
5476
5477   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5478   if(endPV < 0) return;
5479   if(y < margin) step = 1; else
5480   if(y > h - margin) step = -1;
5481   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5482   currentMove += step;
5483   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5484   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5485                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5486   DrawPosition(FALSE, boards[currentMove]);
5487 }
5488
5489
5490 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5491 // All positions will have equal probability, but the current method will not provide a unique
5492 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5493 #define DARK 1
5494 #define LITE 2
5495 #define ANY 3
5496
5497 int squaresLeft[4];
5498 int piecesLeft[(int)BlackPawn];
5499 int seed, nrOfShuffles;
5500
5501 void GetPositionNumber()
5502 {       // sets global variable seed
5503         int i;
5504
5505         seed = appData.defaultFrcPosition;
5506         if(seed < 0) { // randomize based on time for negative FRC position numbers
5507                 for(i=0; i<50; i++) seed += random();
5508                 seed = random() ^ random() >> 8 ^ random() << 8;
5509                 if(seed<0) seed = -seed;
5510         }
5511 }
5512
5513 int put(Board board, int pieceType, int rank, int n, int shade)
5514 // put the piece on the (n-1)-th empty squares of the given shade
5515 {
5516         int i;
5517
5518         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5519                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5520                         board[rank][i] = (ChessSquare) pieceType;
5521                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5522                         squaresLeft[ANY]--;
5523                         piecesLeft[pieceType]--;
5524                         return i;
5525                 }
5526         }
5527         return -1;
5528 }
5529
5530
5531 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5532 // calculate where the next piece goes, (any empty square), and put it there
5533 {
5534         int i;
5535
5536         i = seed % squaresLeft[shade];
5537         nrOfShuffles *= squaresLeft[shade];
5538         seed /= squaresLeft[shade];
5539         put(board, pieceType, rank, i, shade);
5540 }
5541
5542 void AddTwoPieces(Board board, int pieceType, int rank)
5543 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5544 {
5545         int i, n=squaresLeft[ANY], j=n-1, k;
5546
5547         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5548         i = seed % k;  // pick one
5549         nrOfShuffles *= k;
5550         seed /= k;
5551         while(i >= j) i -= j--;
5552         j = n - 1 - j; i += j;
5553         put(board, pieceType, rank, j, ANY);
5554         put(board, pieceType, rank, i, ANY);
5555 }
5556
5557 void SetUpShuffle(Board board, int number)
5558 {
5559         int i, p, first=1;
5560
5561         GetPositionNumber(); nrOfShuffles = 1;
5562
5563         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5564         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5565         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5566
5567         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5568
5569         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5570             p = (int) board[0][i];
5571             if(p < (int) BlackPawn) piecesLeft[p] ++;
5572             board[0][i] = EmptySquare;
5573         }
5574
5575         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5576             // shuffles restricted to allow normal castling put KRR first
5577             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5578                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5579             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5580                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5581             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5582                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5583             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5584                 put(board, WhiteRook, 0, 0, ANY);
5585             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5586         }
5587
5588         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5589             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5590             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5591                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5592                 while(piecesLeft[p] >= 2) {
5593                     AddOnePiece(board, p, 0, LITE);
5594                     AddOnePiece(board, p, 0, DARK);
5595                 }
5596                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5597             }
5598
5599         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5600             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5601             // but we leave King and Rooks for last, to possibly obey FRC restriction
5602             if(p == (int)WhiteRook) continue;
5603             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5604             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5605         }
5606
5607         // now everything is placed, except perhaps King (Unicorn) and Rooks
5608
5609         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5610             // Last King gets castling rights
5611             while(piecesLeft[(int)WhiteUnicorn]) {
5612                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5613                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5614             }
5615
5616             while(piecesLeft[(int)WhiteKing]) {
5617                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5618                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5619             }
5620
5621
5622         } else {
5623             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5624             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5625         }
5626
5627         // Only Rooks can be left; simply place them all
5628         while(piecesLeft[(int)WhiteRook]) {
5629                 i = put(board, WhiteRook, 0, 0, ANY);
5630                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5631                         if(first) {
5632                                 first=0;
5633                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5634                         }
5635                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5636                 }
5637         }
5638         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5639             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5640         }
5641
5642         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5643 }
5644
5645 int SetCharTable( char *table, const char * map )
5646 /* [HGM] moved here from winboard.c because of its general usefulness */
5647 /*       Basically a safe strcpy that uses the last character as King */
5648 {
5649     int result = FALSE; int NrPieces;
5650
5651     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5652                     && NrPieces >= 12 && !(NrPieces&1)) {
5653         int i; /* [HGM] Accept even length from 12 to 34 */
5654
5655         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5656         for( i=0; i<NrPieces/2-1; i++ ) {
5657             table[i] = map[i];
5658             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5659         }
5660         table[(int) WhiteKing]  = map[NrPieces/2-1];
5661         table[(int) BlackKing]  = map[NrPieces-1];
5662
5663         result = TRUE;
5664     }
5665
5666     return result;
5667 }
5668
5669 void Prelude(Board board)
5670 {       // [HGM] superchess: random selection of exo-pieces
5671         int i, j, k; ChessSquare p;
5672         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5673
5674         GetPositionNumber(); // use FRC position number
5675
5676         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5677             SetCharTable(pieceToChar, appData.pieceToCharTable);
5678             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5679                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5680         }
5681
5682         j = seed%4;                 seed /= 4;
5683         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5684         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5685         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5686         j = seed%3 + (seed%3 >= j); seed /= 3;
5687         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5688         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5689         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5690         j = seed%3;                 seed /= 3;
5691         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5692         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5693         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5694         j = seed%2 + (seed%2 >= j); seed /= 2;
5695         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5696         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5697         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5698         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5699         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5700         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5701         put(board, exoPieces[0],    0, 0, ANY);
5702         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5703 }
5704
5705 void
5706 InitPosition(redraw)
5707      int redraw;
5708 {
5709     ChessSquare (* pieces)[BOARD_FILES];
5710     int i, j, pawnRow, overrule,
5711     oldx = gameInfo.boardWidth,
5712     oldy = gameInfo.boardHeight,
5713     oldh = gameInfo.holdingsWidth;
5714     static int oldv;
5715
5716     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5717
5718     /* [AS] Initialize pv info list [HGM] and game status */
5719     {
5720         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5721             pvInfoList[i].depth = 0;
5722             boards[i][EP_STATUS] = EP_NONE;
5723             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5724         }
5725
5726         initialRulePlies = 0; /* 50-move counter start */
5727
5728         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5729         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5730     }
5731
5732
5733     /* [HGM] logic here is completely changed. In stead of full positions */
5734     /* the initialized data only consist of the two backranks. The switch */
5735     /* selects which one we will use, which is than copied to the Board   */
5736     /* initialPosition, which for the rest is initialized by Pawns and    */
5737     /* empty squares. This initial position is then copied to boards[0],  */
5738     /* possibly after shuffling, so that it remains available.            */
5739
5740     gameInfo.holdingsWidth = 0; /* default board sizes */
5741     gameInfo.boardWidth    = 8;
5742     gameInfo.boardHeight   = 8;
5743     gameInfo.holdingsSize  = 0;
5744     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5745     for(i=0; i<BOARD_FILES-2; i++)
5746       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5747     initialPosition[EP_STATUS] = EP_NONE;
5748     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5749     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5750          SetCharTable(pieceNickName, appData.pieceNickNames);
5751     else SetCharTable(pieceNickName, "............");
5752     pieces = FIDEArray;
5753
5754     switch (gameInfo.variant) {
5755     case VariantFischeRandom:
5756       shuffleOpenings = TRUE;
5757     default:
5758       break;
5759     case VariantShatranj:
5760       pieces = ShatranjArray;
5761       nrCastlingRights = 0;
5762       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5763       break;
5764     case VariantMakruk:
5765       pieces = makrukArray;
5766       nrCastlingRights = 0;
5767       startedFromSetupPosition = TRUE;
5768       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5769       break;
5770     case VariantTwoKings:
5771       pieces = twoKingsArray;
5772       break;
5773     case VariantGrand:
5774       pieces = GrandArray;
5775       nrCastlingRights = 0;
5776       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5777       gameInfo.boardWidth = 10;
5778       gameInfo.boardHeight = 10;
5779       gameInfo.holdingsSize = 7;
5780       break;
5781     case VariantCapaRandom:
5782       shuffleOpenings = TRUE;
5783     case VariantCapablanca:
5784       pieces = CapablancaArray;
5785       gameInfo.boardWidth = 10;
5786       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5787       break;
5788     case VariantGothic:
5789       pieces = GothicArray;
5790       gameInfo.boardWidth = 10;
5791       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5792       break;
5793     case VariantSChess:
5794       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5795       gameInfo.holdingsSize = 7;
5796       break;
5797     case VariantJanus:
5798       pieces = JanusArray;
5799       gameInfo.boardWidth = 10;
5800       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5801       nrCastlingRights = 6;
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)>>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)>>1;
5808       break;
5809     case VariantFalcon:
5810       pieces = FalconArray;
5811       gameInfo.boardWidth = 10;
5812       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5813       break;
5814     case VariantXiangqi:
5815       pieces = XiangqiArray;
5816       gameInfo.boardWidth  = 9;
5817       gameInfo.boardHeight = 10;
5818       nrCastlingRights = 0;
5819       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5820       break;
5821     case VariantShogi:
5822       pieces = ShogiArray;
5823       gameInfo.boardWidth  = 9;
5824       gameInfo.boardHeight = 9;
5825       gameInfo.holdingsSize = 7;
5826       nrCastlingRights = 0;
5827       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5828       break;
5829     case VariantCourier:
5830       pieces = CourierArray;
5831       gameInfo.boardWidth  = 12;
5832       nrCastlingRights = 0;
5833       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5834       break;
5835     case VariantKnightmate:
5836       pieces = KnightmateArray;
5837       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5838       break;
5839     case VariantSpartan:
5840       pieces = SpartanArray;
5841       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5842       break;
5843     case VariantFairy:
5844       pieces = fairyArray;
5845       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5846       break;
5847     case VariantGreat:
5848       pieces = GreatArray;
5849       gameInfo.boardWidth = 10;
5850       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5851       gameInfo.holdingsSize = 8;
5852       break;
5853     case VariantSuper:
5854       pieces = FIDEArray;
5855       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5856       gameInfo.holdingsSize = 8;
5857       startedFromSetupPosition = TRUE;
5858       break;
5859     case VariantCrazyhouse:
5860     case VariantBughouse:
5861       pieces = FIDEArray;
5862       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5863       gameInfo.holdingsSize = 5;
5864       break;
5865     case VariantWildCastle:
5866       pieces = FIDEArray;
5867       /* !!?shuffle with kings guaranteed to be on d or e file */
5868       shuffleOpenings = 1;
5869       break;
5870     case VariantNoCastle:
5871       pieces = FIDEArray;
5872       nrCastlingRights = 0;
5873       /* !!?unconstrained back-rank shuffle */
5874       shuffleOpenings = 1;
5875       break;
5876     }
5877
5878     overrule = 0;
5879     if(appData.NrFiles >= 0) {
5880         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5881         gameInfo.boardWidth = appData.NrFiles;
5882     }
5883     if(appData.NrRanks >= 0) {
5884         gameInfo.boardHeight = appData.NrRanks;
5885     }
5886     if(appData.holdingsSize >= 0) {
5887         i = appData.holdingsSize;
5888         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5889         gameInfo.holdingsSize = i;
5890     }
5891     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5892     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5893         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5894
5895     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5896     if(pawnRow < 1) pawnRow = 1;
5897     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5898
5899     /* User pieceToChar list overrules defaults */
5900     if(appData.pieceToCharTable != NULL)
5901         SetCharTable(pieceToChar, appData.pieceToCharTable);
5902
5903     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5904
5905         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5906             s = (ChessSquare) 0; /* account holding counts in guard band */
5907         for( i=0; i<BOARD_HEIGHT; i++ )
5908             initialPosition[i][j] = s;
5909
5910         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5911         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5912         initialPosition[pawnRow][j] = WhitePawn;
5913         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5914         if(gameInfo.variant == VariantXiangqi) {
5915             if(j&1) {
5916                 initialPosition[pawnRow][j] =
5917                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5918                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5919                    initialPosition[2][j] = WhiteCannon;
5920                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5921                 }
5922             }
5923         }
5924         if(gameInfo.variant == VariantGrand) {
5925             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5926                initialPosition[0][j] = WhiteRook;
5927                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5928             }
5929         }
5930         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5931     }
5932     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5933
5934             j=BOARD_LEFT+1;
5935             initialPosition[1][j] = WhiteBishop;
5936             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5937             j=BOARD_RGHT-2;
5938             initialPosition[1][j] = WhiteRook;
5939             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5940     }
5941
5942     if( nrCastlingRights == -1) {
5943         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5944         /*       This sets default castling rights from none to normal corners   */
5945         /* Variants with other castling rights must set them themselves above    */
5946         nrCastlingRights = 6;
5947
5948         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5949         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5950         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5951         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5952         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5953         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5954      }
5955
5956      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5957      if(gameInfo.variant == VariantGreat) { // promotion commoners
5958         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5959         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5960         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5961         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5962      }
5963      if( gameInfo.variant == VariantSChess ) {
5964       initialPosition[1][0] = BlackMarshall;
5965       initialPosition[2][0] = BlackAngel;
5966       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5967       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5968       initialPosition[1][1] = initialPosition[2][1] = 
5969       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5970      }
5971   if (appData.debugMode) {
5972     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5973   }
5974     if(shuffleOpenings) {
5975         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5976         startedFromSetupPosition = TRUE;
5977     }
5978     if(startedFromPositionFile) {
5979       /* [HGM] loadPos: use PositionFile for every new game */
5980       CopyBoard(initialPosition, filePosition);
5981       for(i=0; i<nrCastlingRights; i++)
5982           initialRights[i] = filePosition[CASTLING][i];
5983       startedFromSetupPosition = TRUE;
5984     }
5985
5986     CopyBoard(boards[0], initialPosition);
5987
5988     if(oldx != gameInfo.boardWidth ||
5989        oldy != gameInfo.boardHeight ||
5990        oldv != gameInfo.variant ||
5991        oldh != gameInfo.holdingsWidth
5992                                          )
5993             InitDrawingSizes(-2 ,0);
5994
5995     oldv = gameInfo.variant;
5996     if (redraw)
5997       DrawPosition(TRUE, boards[currentMove]);
5998 }
5999
6000 void
6001 SendBoard(cps, moveNum)
6002      ChessProgramState *cps;
6003      int moveNum;
6004 {
6005     char message[MSG_SIZ];
6006
6007     if (cps->useSetboard) {
6008       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6009       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6010       SendToProgram(message, cps);
6011       free(fen);
6012
6013     } else {
6014       ChessSquare *bp;
6015       int i, j;
6016       /* Kludge to set black to move, avoiding the troublesome and now
6017        * deprecated "black" command.
6018        */
6019       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6020         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6021
6022       SendToProgram("edit\n", cps);
6023       SendToProgram("#\n", cps);
6024       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6025         bp = &boards[moveNum][i][BOARD_LEFT];
6026         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6027           if ((int) *bp < (int) BlackPawn) {
6028             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6029                     AAA + j, ONE + i);
6030             if(message[0] == '+' || message[0] == '~') {
6031               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6032                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6033                         AAA + j, ONE + i);
6034             }
6035             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6036                 message[1] = BOARD_RGHT   - 1 - j + '1';
6037                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6038             }
6039             SendToProgram(message, cps);
6040           }
6041         }
6042       }
6043
6044       SendToProgram("c\n", cps);
6045       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6046         bp = &boards[moveNum][i][BOARD_LEFT];
6047         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6048           if (((int) *bp != (int) EmptySquare)
6049               && ((int) *bp >= (int) BlackPawn)) {
6050             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6051                     AAA + j, ONE + i);
6052             if(message[0] == '+' || message[0] == '~') {
6053               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6054                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6055                         AAA + j, ONE + i);
6056             }
6057             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6058                 message[1] = BOARD_RGHT   - 1 - j + '1';
6059                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6060             }
6061             SendToProgram(message, cps);
6062           }
6063         }
6064       }
6065
6066       SendToProgram(".\n", cps);
6067     }
6068     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6069 }
6070
6071 ChessSquare
6072 DefaultPromoChoice(int white)
6073 {
6074     ChessSquare result;
6075     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6076         result = WhiteFerz; // no choice
6077     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6078         result= WhiteKing; // in Suicide Q is the last thing we want
6079     else if(gameInfo.variant == VariantSpartan)
6080         result = white ? WhiteQueen : WhiteAngel;
6081     else result = WhiteQueen;
6082     if(!white) result = WHITE_TO_BLACK result;
6083     return result;
6084 }
6085
6086 static int autoQueen; // [HGM] oneclick
6087
6088 int
6089 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6090 {
6091     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6092     /* [HGM] add Shogi promotions */
6093     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6094     ChessSquare piece;
6095     ChessMove moveType;
6096     Boolean premove;
6097
6098     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6099     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6100
6101     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6102       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6103         return FALSE;
6104
6105     piece = boards[currentMove][fromY][fromX];
6106     if(gameInfo.variant == VariantShogi) {
6107         promotionZoneSize = BOARD_HEIGHT/3;
6108         highestPromotingPiece = (int)WhiteFerz;
6109     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6110         promotionZoneSize = 3;
6111     }
6112
6113     // Treat Lance as Pawn when it is not representing Amazon
6114     if(gameInfo.variant != VariantSuper) {
6115         if(piece == WhiteLance) piece = WhitePawn; else
6116         if(piece == BlackLance) piece = BlackPawn;
6117     }
6118
6119     // next weed out all moves that do not touch the promotion zone at all
6120     if((int)piece >= BlackPawn) {
6121         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6122              return FALSE;
6123         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6124     } else {
6125         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6126            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6127     }
6128
6129     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6130
6131     // weed out mandatory Shogi promotions
6132     if(gameInfo.variant == VariantShogi) {
6133         if(piece >= BlackPawn) {
6134             if(toY == 0 && piece == BlackPawn ||
6135                toY == 0 && piece == BlackQueen ||
6136                toY <= 1 && piece == BlackKnight) {
6137                 *promoChoice = '+';
6138                 return FALSE;
6139             }
6140         } else {
6141             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6142                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6143                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6144                 *promoChoice = '+';
6145                 return FALSE;
6146             }
6147         }
6148     }
6149
6150     // weed out obviously illegal Pawn moves
6151     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6152         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6153         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6154         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6155         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6156         // note we are not allowed to test for valid (non-)capture, due to premove
6157     }
6158
6159     // we either have a choice what to promote to, or (in Shogi) whether to promote
6160     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6161         *promoChoice = PieceToChar(BlackFerz);  // no choice
6162         return FALSE;
6163     }
6164     // no sense asking what we must promote to if it is going to explode...
6165     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6166         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6167         return FALSE;
6168     }
6169     // give caller the default choice even if we will not make it
6170     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6171     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6172     if(        sweepSelect && gameInfo.variant != VariantGreat
6173                            && gameInfo.variant != VariantGrand
6174                            && gameInfo.variant != VariantSuper) return FALSE;
6175     if(autoQueen) return FALSE; // predetermined
6176
6177     // suppress promotion popup on illegal moves that are not premoves
6178     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6179               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6180     if(appData.testLegality && !premove) {
6181         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6182                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6183         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6184             return FALSE;
6185     }
6186
6187     return TRUE;
6188 }
6189
6190 int
6191 InPalace(row, column)
6192      int row, column;
6193 {   /* [HGM] for Xiangqi */
6194     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6195          column < (BOARD_WIDTH + 4)/2 &&
6196          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6197     return FALSE;
6198 }
6199
6200 int
6201 PieceForSquare (x, y)
6202      int x;
6203      int y;
6204 {
6205   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6206      return -1;
6207   else
6208      return boards[currentMove][y][x];
6209 }
6210
6211 int
6212 OKToStartUserMove(x, y)
6213      int x, y;
6214 {
6215     ChessSquare from_piece;
6216     int white_piece;
6217
6218     if (matchMode) return FALSE;
6219     if (gameMode == EditPosition) return TRUE;
6220
6221     if (x >= 0 && y >= 0)
6222       from_piece = boards[currentMove][y][x];
6223     else
6224       from_piece = EmptySquare;
6225
6226     if (from_piece == EmptySquare) return FALSE;
6227
6228     white_piece = (int)from_piece >= (int)WhitePawn &&
6229       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6230
6231     switch (gameMode) {
6232       case AnalyzeFile:
6233       case TwoMachinesPlay:
6234       case EndOfGame:
6235         return FALSE;
6236
6237       case IcsObserving:
6238       case IcsIdle:
6239         return FALSE;
6240
6241       case MachinePlaysWhite:
6242       case IcsPlayingBlack:
6243         if (appData.zippyPlay) return FALSE;
6244         if (white_piece) {
6245             DisplayMoveError(_("You are playing Black"));
6246             return FALSE;
6247         }
6248         break;
6249
6250       case MachinePlaysBlack:
6251       case IcsPlayingWhite:
6252         if (appData.zippyPlay) return FALSE;
6253         if (!white_piece) {
6254             DisplayMoveError(_("You are playing White"));
6255             return FALSE;
6256         }
6257         break;
6258
6259       case PlayFromGameFile:
6260             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6261       case EditGame:
6262         if (!white_piece && WhiteOnMove(currentMove)) {
6263             DisplayMoveError(_("It is White's turn"));
6264             return FALSE;
6265         }
6266         if (white_piece && !WhiteOnMove(currentMove)) {
6267             DisplayMoveError(_("It is Black's turn"));
6268             return FALSE;
6269         }
6270         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6271             /* Editing correspondence game history */
6272             /* Could disallow this or prompt for confirmation */
6273             cmailOldMove = -1;
6274         }
6275         break;
6276
6277       case BeginningOfGame:
6278         if (appData.icsActive) return FALSE;
6279         if (!appData.noChessProgram) {
6280             if (!white_piece) {
6281                 DisplayMoveError(_("You are playing White"));
6282                 return FALSE;
6283             }
6284         }
6285         break;
6286
6287       case Training:
6288         if (!white_piece && WhiteOnMove(currentMove)) {
6289             DisplayMoveError(_("It is White's turn"));
6290             return FALSE;
6291         }
6292         if (white_piece && !WhiteOnMove(currentMove)) {
6293             DisplayMoveError(_("It is Black's turn"));
6294             return FALSE;
6295         }
6296         break;
6297
6298       default:
6299       case IcsExamining:
6300         break;
6301     }
6302     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6303         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6304         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6305         && gameMode != AnalyzeFile && gameMode != Training) {
6306         DisplayMoveError(_("Displayed position is not current"));
6307         return FALSE;
6308     }
6309     return TRUE;
6310 }
6311
6312 Boolean
6313 OnlyMove(int *x, int *y, Boolean captures) {
6314     DisambiguateClosure cl;
6315     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6316     switch(gameMode) {
6317       case MachinePlaysBlack:
6318       case IcsPlayingWhite:
6319       case BeginningOfGame:
6320         if(!WhiteOnMove(currentMove)) return FALSE;
6321         break;
6322       case MachinePlaysWhite:
6323       case IcsPlayingBlack:
6324         if(WhiteOnMove(currentMove)) return FALSE;
6325         break;
6326       case EditGame:
6327         break;
6328       default:
6329         return FALSE;
6330     }
6331     cl.pieceIn = EmptySquare;
6332     cl.rfIn = *y;
6333     cl.ffIn = *x;
6334     cl.rtIn = -1;
6335     cl.ftIn = -1;
6336     cl.promoCharIn = NULLCHAR;
6337     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6338     if( cl.kind == NormalMove ||
6339         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6340         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6341         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6342       fromX = cl.ff;
6343       fromY = cl.rf;
6344       *x = cl.ft;
6345       *y = cl.rt;
6346       return TRUE;
6347     }
6348     if(cl.kind != ImpossibleMove) return FALSE;
6349     cl.pieceIn = EmptySquare;
6350     cl.rfIn = -1;
6351     cl.ffIn = -1;
6352     cl.rtIn = *y;
6353     cl.ftIn = *x;
6354     cl.promoCharIn = NULLCHAR;
6355     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6356     if( cl.kind == NormalMove ||
6357         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6358         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6359         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6360       fromX = cl.ff;
6361       fromY = cl.rf;
6362       *x = cl.ft;
6363       *y = cl.rt;
6364       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6365       return TRUE;
6366     }
6367     return FALSE;
6368 }
6369
6370 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6371 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6372 int lastLoadGameUseList = FALSE;
6373 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6374 ChessMove lastLoadGameStart = EndOfFile;
6375
6376 void
6377 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6378      int fromX, fromY, toX, toY;
6379      int promoChar;
6380 {
6381     ChessMove moveType;
6382     ChessSquare pdown, pup;
6383
6384     /* Check if the user is playing in turn.  This is complicated because we
6385        let the user "pick up" a piece before it is his turn.  So the piece he
6386        tried to pick up may have been captured by the time he puts it down!
6387        Therefore we use the color the user is supposed to be playing in this
6388        test, not the color of the piece that is currently on the starting
6389        square---except in EditGame mode, where the user is playing both
6390        sides; fortunately there the capture race can't happen.  (It can
6391        now happen in IcsExamining mode, but that's just too bad.  The user
6392        will get a somewhat confusing message in that case.)
6393        */
6394
6395     switch (gameMode) {
6396       case AnalyzeFile:
6397       case TwoMachinesPlay:
6398       case EndOfGame:
6399       case IcsObserving:
6400       case IcsIdle:
6401         /* We switched into a game mode where moves are not accepted,
6402            perhaps while the mouse button was down. */
6403         return;
6404
6405       case MachinePlaysWhite:
6406         /* User is moving for Black */
6407         if (WhiteOnMove(currentMove)) {
6408             DisplayMoveError(_("It is White's turn"));
6409             return;
6410         }
6411         break;
6412
6413       case MachinePlaysBlack:
6414         /* User is moving for White */
6415         if (!WhiteOnMove(currentMove)) {
6416             DisplayMoveError(_("It is Black's turn"));
6417             return;
6418         }
6419         break;
6420
6421       case PlayFromGameFile:
6422             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6423       case EditGame:
6424       case IcsExamining:
6425       case BeginningOfGame:
6426       case AnalyzeMode:
6427       case Training:
6428         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6429         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6430             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6431             /* User is moving for Black */
6432             if (WhiteOnMove(currentMove)) {
6433                 DisplayMoveError(_("It is White's turn"));
6434                 return;
6435             }
6436         } else {
6437             /* User is moving for White */
6438             if (!WhiteOnMove(currentMove)) {
6439                 DisplayMoveError(_("It is Black's turn"));
6440                 return;
6441             }
6442         }
6443         break;
6444
6445       case IcsPlayingBlack:
6446         /* User is moving for Black */
6447         if (WhiteOnMove(currentMove)) {
6448             if (!appData.premove) {
6449                 DisplayMoveError(_("It is White's turn"));
6450             } else if (toX >= 0 && toY >= 0) {
6451                 premoveToX = toX;
6452                 premoveToY = toY;
6453                 premoveFromX = fromX;
6454                 premoveFromY = fromY;
6455                 premovePromoChar = promoChar;
6456                 gotPremove = 1;
6457                 if (appData.debugMode)
6458                     fprintf(debugFP, "Got premove: fromX %d,"
6459                             "fromY %d, toX %d, toY %d\n",
6460                             fromX, fromY, toX, toY);
6461             }
6462             return;
6463         }
6464         break;
6465
6466       case IcsPlayingWhite:
6467         /* User is moving for White */
6468         if (!WhiteOnMove(currentMove)) {
6469             if (!appData.premove) {
6470                 DisplayMoveError(_("It is Black's turn"));
6471             } else if (toX >= 0 && toY >= 0) {
6472                 premoveToX = toX;
6473                 premoveToY = toY;
6474                 premoveFromX = fromX;
6475                 premoveFromY = fromY;
6476                 premovePromoChar = promoChar;
6477                 gotPremove = 1;
6478                 if (appData.debugMode)
6479                     fprintf(debugFP, "Got premove: fromX %d,"
6480                             "fromY %d, toX %d, toY %d\n",
6481                             fromX, fromY, toX, toY);
6482             }
6483             return;
6484         }
6485         break;
6486
6487       default:
6488         break;
6489
6490       case EditPosition:
6491         /* EditPosition, empty square, or different color piece;
6492            click-click move is possible */
6493         if (toX == -2 || toY == -2) {
6494             boards[0][fromY][fromX] = EmptySquare;
6495             DrawPosition(FALSE, boards[currentMove]);
6496             return;
6497         } else if (toX >= 0 && toY >= 0) {
6498             boards[0][toY][toX] = boards[0][fromY][fromX];
6499             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6500                 if(boards[0][fromY][0] != EmptySquare) {
6501                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6502                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6503                 }
6504             } else
6505             if(fromX == BOARD_RGHT+1) {
6506                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6507                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6508                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6509                 }
6510             } else
6511             boards[0][fromY][fromX] = EmptySquare;
6512             DrawPosition(FALSE, boards[currentMove]);
6513             return;
6514         }
6515         return;
6516     }
6517
6518     if(toX < 0 || toY < 0) return;
6519     pdown = boards[currentMove][fromY][fromX];
6520     pup = boards[currentMove][toY][toX];
6521
6522     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6523     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6524          if( pup != EmptySquare ) return;
6525          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6526            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6527                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6528            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6529            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6530            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6531            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6532          fromY = DROP_RANK;
6533     }
6534
6535     /* [HGM] always test for legality, to get promotion info */
6536     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6537                                          fromY, fromX, toY, toX, promoChar);
6538
6539     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6540
6541     /* [HGM] but possibly ignore an IllegalMove result */
6542     if (appData.testLegality) {
6543         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6544             DisplayMoveError(_("Illegal move"));
6545             return;
6546         }
6547     }
6548
6549     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6550 }
6551
6552 /* Common tail of UserMoveEvent and DropMenuEvent */
6553 int
6554 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6555      ChessMove moveType;
6556      int fromX, fromY, toX, toY;
6557      /*char*/int promoChar;
6558 {
6559     char *bookHit = 0;
6560
6561     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6562         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6563         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6564         if(WhiteOnMove(currentMove)) {
6565             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6566         } else {
6567             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6568         }
6569     }
6570
6571     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6572        move type in caller when we know the move is a legal promotion */
6573     if(moveType == NormalMove && promoChar)
6574         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6575
6576     /* [HGM] <popupFix> The following if has been moved here from
6577        UserMoveEvent(). Because it seemed to belong here (why not allow
6578        piece drops in training games?), and because it can only be
6579        performed after it is known to what we promote. */
6580     if (gameMode == Training) {
6581       /* compare the move played on the board to the next move in the
6582        * game. If they match, display the move and the opponent's response.
6583        * If they don't match, display an error message.
6584        */
6585       int saveAnimate;
6586       Board testBoard;
6587       CopyBoard(testBoard, boards[currentMove]);
6588       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6589
6590       if (CompareBoards(testBoard, boards[currentMove+1])) {
6591         ForwardInner(currentMove+1);
6592
6593         /* Autoplay the opponent's response.
6594          * if appData.animate was TRUE when Training mode was entered,
6595          * the response will be animated.
6596          */
6597         saveAnimate = appData.animate;
6598         appData.animate = animateTraining;
6599         ForwardInner(currentMove+1);
6600         appData.animate = saveAnimate;
6601
6602         /* check for the end of the game */
6603         if (currentMove >= forwardMostMove) {
6604           gameMode = PlayFromGameFile;
6605           ModeHighlight();
6606           SetTrainingModeOff();
6607           DisplayInformation(_("End of game"));
6608         }
6609       } else {
6610         DisplayError(_("Incorrect move"), 0);
6611       }
6612       return 1;
6613     }
6614
6615   /* Ok, now we know that the move is good, so we can kill
6616      the previous line in Analysis Mode */
6617   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6618                                 && currentMove < forwardMostMove) {
6619     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6620     else forwardMostMove = currentMove;
6621   }
6622
6623   /* If we need the chess program but it's dead, restart it */
6624   ResurrectChessProgram();
6625
6626   /* A user move restarts a paused game*/
6627   if (pausing)
6628     PauseEvent();
6629
6630   thinkOutput[0] = NULLCHAR;
6631
6632   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6633
6634   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6635     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6636     return 1;
6637   }
6638
6639   if (gameMode == BeginningOfGame) {
6640     if (appData.noChessProgram) {
6641       gameMode = EditGame;
6642       SetGameInfo();
6643     } else {
6644       char buf[MSG_SIZ];
6645       gameMode = MachinePlaysBlack;
6646       StartClocks();
6647       SetGameInfo();
6648       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6649       DisplayTitle(buf);
6650       if (first.sendName) {
6651         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6652         SendToProgram(buf, &first);
6653       }
6654       StartClocks();
6655     }
6656     ModeHighlight();
6657   }
6658
6659   /* Relay move to ICS or chess engine */
6660   if (appData.icsActive) {
6661     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6662         gameMode == IcsExamining) {
6663       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6664         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6665         SendToICS("draw ");
6666         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6667       }
6668       // also send plain move, in case ICS does not understand atomic claims
6669       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6670       ics_user_moved = 1;
6671     }
6672   } else {
6673     if (first.sendTime && (gameMode == BeginningOfGame ||
6674                            gameMode == MachinePlaysWhite ||
6675                            gameMode == MachinePlaysBlack)) {
6676       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6677     }
6678     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6679          // [HGM] book: if program might be playing, let it use book
6680         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6681         first.maybeThinking = TRUE;
6682     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6683         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6684         SendBoard(&first, currentMove+1);
6685     } else SendMoveToProgram(forwardMostMove-1, &first);
6686     if (currentMove == cmailOldMove + 1) {
6687       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6688     }
6689   }
6690
6691   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6692
6693   switch (gameMode) {
6694   case EditGame:
6695     if(appData.testLegality)
6696     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6697     case MT_NONE:
6698     case MT_CHECK:
6699       break;
6700     case MT_CHECKMATE:
6701     case MT_STAINMATE:
6702       if (WhiteOnMove(currentMove)) {
6703         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6704       } else {
6705         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6706       }
6707       break;
6708     case MT_STALEMATE:
6709       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6710       break;
6711     }
6712     break;
6713
6714   case MachinePlaysBlack:
6715   case MachinePlaysWhite:
6716     /* disable certain menu options while machine is thinking */
6717     SetMachineThinkingEnables();
6718     break;
6719
6720   default:
6721     break;
6722   }
6723
6724   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6725   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6726
6727   if(bookHit) { // [HGM] book: simulate book reply
6728         static char bookMove[MSG_SIZ]; // a bit generous?
6729
6730         programStats.nodes = programStats.depth = programStats.time =
6731         programStats.score = programStats.got_only_move = 0;
6732         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6733
6734         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6735         strcat(bookMove, bookHit);
6736         HandleMachineMove(bookMove, &first);
6737   }
6738   return 1;
6739 }
6740
6741 void
6742 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6743      Board board;
6744      int flags;
6745      ChessMove kind;
6746      int rf, ff, rt, ft;
6747      VOIDSTAR closure;
6748 {
6749     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6750     Markers *m = (Markers *) closure;
6751     if(rf == fromY && ff == fromX)
6752         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6753                          || kind == WhiteCapturesEnPassant
6754                          || kind == BlackCapturesEnPassant);
6755     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6756 }
6757
6758 void
6759 MarkTargetSquares(int clear)
6760 {
6761   int x, y;
6762   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6763      !appData.testLegality || gameMode == EditPosition) return;
6764   if(clear) {
6765     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6766   } else {
6767     int capt = 0;
6768     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6769     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6770       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6771       if(capt)
6772       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6773     }
6774   }
6775   DrawPosition(TRUE, NULL);
6776 }
6777
6778 int
6779 Explode(Board board, int fromX, int fromY, int toX, int toY)
6780 {
6781     if(gameInfo.variant == VariantAtomic &&
6782        (board[toY][toX] != EmptySquare ||                     // capture?
6783         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6784                          board[fromY][fromX] == BlackPawn   )
6785       )) {
6786         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6787         return TRUE;
6788     }
6789     return FALSE;
6790 }
6791
6792 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6793
6794 int CanPromote(ChessSquare piece, int y)
6795 {
6796         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6797         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6798         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6799            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6800            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6801                                                   gameInfo.variant == VariantMakruk) return FALSE;
6802         return (piece == BlackPawn && y == 1 ||
6803                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6804                 piece == BlackLance && y == 1 ||
6805                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6806 }
6807
6808 void LeftClick(ClickType clickType, int xPix, int yPix)
6809 {
6810     int x, y;
6811     Boolean saveAnimate;
6812     static int second = 0, promotionChoice = 0, clearFlag = 0;
6813     char promoChoice = NULLCHAR;
6814     ChessSquare piece;
6815
6816     if(appData.seekGraph && appData.icsActive && loggedOn &&
6817         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6818         SeekGraphClick(clickType, xPix, yPix, 0);
6819         return;
6820     }
6821
6822     if (clickType == Press) ErrorPopDown();
6823
6824     x = EventToSquare(xPix, BOARD_WIDTH);
6825     y = EventToSquare(yPix, BOARD_HEIGHT);
6826     if (!flipView && y >= 0) {
6827         y = BOARD_HEIGHT - 1 - y;
6828     }
6829     if (flipView && x >= 0) {
6830         x = BOARD_WIDTH - 1 - x;
6831     }
6832
6833     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6834         defaultPromoChoice = promoSweep;
6835         promoSweep = EmptySquare;   // terminate sweep
6836         promoDefaultAltered = TRUE;
6837         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6838     }
6839
6840     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6841         if(clickType == Release) return; // ignore upclick of click-click destination
6842         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6843         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6844         if(gameInfo.holdingsWidth &&
6845                 (WhiteOnMove(currentMove)
6846                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6847                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6848             // click in right holdings, for determining promotion piece
6849             ChessSquare p = boards[currentMove][y][x];
6850             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6851             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6852             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6853                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6854                 fromX = fromY = -1;
6855                 return;
6856             }
6857         }
6858         DrawPosition(FALSE, boards[currentMove]);
6859         return;
6860     }
6861
6862     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6863     if(clickType == Press
6864             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6865               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6866               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6867         return;
6868
6869     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6870         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6871
6872     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6873         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6874                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6875         defaultPromoChoice = DefaultPromoChoice(side);
6876     }
6877
6878     autoQueen = appData.alwaysPromoteToQueen;
6879
6880     if (fromX == -1) {
6881       int originalY = y;
6882       gatingPiece = EmptySquare;
6883       if (clickType != Press) {
6884         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6885             DragPieceEnd(xPix, yPix); dragging = 0;
6886             DrawPosition(FALSE, NULL);
6887         }
6888         return;
6889       }
6890       fromX = x; fromY = y; toX = toY = -1;
6891       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6892          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6893          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6894             /* First square */
6895             if (OKToStartUserMove(fromX, fromY)) {
6896                 second = 0;
6897                 MarkTargetSquares(0);
6898                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6899                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6900                     promoSweep = defaultPromoChoice;
6901                     selectFlag = 0; lastX = xPix; lastY = yPix;
6902                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6903                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6904                 }
6905                 if (appData.highlightDragging) {
6906                     SetHighlights(fromX, fromY, -1, -1);
6907                 }
6908             } else fromX = fromY = -1;
6909             return;
6910         }
6911     }
6912
6913     /* fromX != -1 */
6914     if (clickType == Press && gameMode != EditPosition) {
6915         ChessSquare fromP;
6916         ChessSquare toP;
6917         int frc;
6918
6919         // ignore off-board to clicks
6920         if(y < 0 || x < 0) return;
6921
6922         /* Check if clicking again on the same color piece */
6923         fromP = boards[currentMove][fromY][fromX];
6924         toP = boards[currentMove][y][x];
6925         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6926         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6927              WhitePawn <= toP && toP <= WhiteKing &&
6928              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6929              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6930             (BlackPawn <= fromP && fromP <= BlackKing &&
6931              BlackPawn <= toP && toP <= BlackKing &&
6932              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6933              !(fromP == BlackKing && toP == BlackRook && frc))) {
6934             /* Clicked again on same color piece -- changed his mind */
6935             second = (x == fromX && y == fromY);
6936             promoDefaultAltered = FALSE;
6937             MarkTargetSquares(1);
6938            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6939             if (appData.highlightDragging) {
6940                 SetHighlights(x, y, -1, -1);
6941             } else {
6942                 ClearHighlights();
6943             }
6944             if (OKToStartUserMove(x, y)) {
6945                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6946                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6947                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6948                  gatingPiece = boards[currentMove][fromY][fromX];
6949                 else gatingPiece = EmptySquare;
6950                 fromX = x;
6951                 fromY = y; dragging = 1;
6952                 MarkTargetSquares(0);
6953                 DragPieceBegin(xPix, yPix, FALSE);
6954                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6955                     promoSweep = defaultPromoChoice;
6956                     selectFlag = 0; lastX = xPix; lastY = yPix;
6957                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6958                 }
6959             }
6960            }
6961            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6962            second = FALSE; 
6963         }
6964         // ignore clicks on holdings
6965         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6966     }
6967
6968     if (clickType == Release && x == fromX && y == fromY) {
6969         DragPieceEnd(xPix, yPix); dragging = 0;
6970         if(clearFlag) {
6971             // a deferred attempt to click-click move an empty square on top of a piece
6972             boards[currentMove][y][x] = EmptySquare;
6973             ClearHighlights();
6974             DrawPosition(FALSE, boards[currentMove]);
6975             fromX = fromY = -1; clearFlag = 0;
6976             return;
6977         }
6978         if (appData.animateDragging) {
6979             /* Undo animation damage if any */
6980             DrawPosition(FALSE, NULL);
6981         }
6982         if (second) {
6983             /* Second up/down in same square; just abort move */
6984             second = 0;
6985             fromX = fromY = -1;
6986             gatingPiece = EmptySquare;
6987             ClearHighlights();
6988             gotPremove = 0;
6989             ClearPremoveHighlights();
6990         } else {
6991             /* First upclick in same square; start click-click mode */
6992             SetHighlights(x, y, -1, -1);
6993         }
6994         return;
6995     }
6996
6997     clearFlag = 0;
6998
6999     /* we now have a different from- and (possibly off-board) to-square */
7000     /* Completed move */
7001     toX = x;
7002     toY = y;
7003     saveAnimate = appData.animate;
7004     MarkTargetSquares(1);
7005     if (clickType == Press) {
7006         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7007             // must be Edit Position mode with empty-square selected
7008             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7009             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7010             return;
7011         }
7012         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7013             ChessSquare piece = boards[currentMove][fromY][fromX];
7014             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7015             promoSweep = defaultPromoChoice;
7016             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7017             selectFlag = 0; lastX = xPix; lastY = yPix;
7018             Sweep(0); // Pawn that is going to promote: preview promotion piece
7019             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7020             DrawPosition(FALSE, boards[currentMove]);
7021             return;
7022         }
7023         /* Finish clickclick move */
7024         if (appData.animate || appData.highlightLastMove) {
7025             SetHighlights(fromX, fromY, toX, toY);
7026         } else {
7027             ClearHighlights();
7028         }
7029     } else {
7030         /* Finish drag move */
7031         if (appData.highlightLastMove) {
7032             SetHighlights(fromX, fromY, toX, toY);
7033         } else {
7034             ClearHighlights();
7035         }
7036         DragPieceEnd(xPix, yPix); dragging = 0;
7037         /* Don't animate move and drag both */
7038         appData.animate = FALSE;
7039     }
7040
7041     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7042     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7043         ChessSquare piece = boards[currentMove][fromY][fromX];
7044         if(gameMode == EditPosition && piece != EmptySquare &&
7045            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7046             int n;
7047
7048             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7049                 n = PieceToNumber(piece - (int)BlackPawn);
7050                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7051                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7052                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7053             } else
7054             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7055                 n = PieceToNumber(piece);
7056                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7057                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7058                 boards[currentMove][n][BOARD_WIDTH-2]++;
7059             }
7060             boards[currentMove][fromY][fromX] = EmptySquare;
7061         }
7062         ClearHighlights();
7063         fromX = fromY = -1;
7064         DrawPosition(TRUE, boards[currentMove]);
7065         return;
7066     }
7067
7068     // off-board moves should not be highlighted
7069     if(x < 0 || y < 0) ClearHighlights();
7070
7071     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7072
7073     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7074         SetHighlights(fromX, fromY, toX, toY);
7075         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7076             // [HGM] super: promotion to captured piece selected from holdings
7077             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7078             promotionChoice = TRUE;
7079             // kludge follows to temporarily execute move on display, without promoting yet
7080             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7081             boards[currentMove][toY][toX] = p;
7082             DrawPosition(FALSE, boards[currentMove]);
7083             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7084             boards[currentMove][toY][toX] = q;
7085             DisplayMessage("Click in holdings to choose piece", "");
7086             return;
7087         }
7088         PromotionPopUp();
7089     } else {
7090         int oldMove = currentMove;
7091         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7092         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7093         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7094         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7095            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7096             DrawPosition(TRUE, boards[currentMove]);
7097         fromX = fromY = -1;
7098     }
7099     appData.animate = saveAnimate;
7100     if (appData.animate || appData.animateDragging) {
7101         /* Undo animation damage if needed */
7102         DrawPosition(FALSE, NULL);
7103     }
7104 }
7105
7106 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7107 {   // front-end-free part taken out of PieceMenuPopup
7108     int whichMenu; int xSqr, ySqr;
7109
7110     if(seekGraphUp) { // [HGM] seekgraph
7111         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7112         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7113         return -2;
7114     }
7115
7116     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7117          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7118         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7119         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7120         if(action == Press)   {
7121             originalFlip = flipView;
7122             flipView = !flipView; // temporarily flip board to see game from partners perspective
7123             DrawPosition(TRUE, partnerBoard);
7124             DisplayMessage(partnerStatus, "");
7125             partnerUp = TRUE;
7126         } else if(action == Release) {
7127             flipView = originalFlip;
7128             DrawPosition(TRUE, boards[currentMove]);
7129             partnerUp = FALSE;
7130         }
7131         return -2;
7132     }
7133
7134     xSqr = EventToSquare(x, BOARD_WIDTH);
7135     ySqr = EventToSquare(y, BOARD_HEIGHT);
7136     if (action == Release) {
7137         if(pieceSweep != EmptySquare) {
7138             EditPositionMenuEvent(pieceSweep, toX, toY);
7139             pieceSweep = EmptySquare;
7140         } else UnLoadPV(); // [HGM] pv
7141     }
7142     if (action != Press) return -2; // return code to be ignored
7143     switch (gameMode) {
7144       case IcsExamining:
7145         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7146       case EditPosition:
7147         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7148         if (xSqr < 0 || ySqr < 0) return -1;
7149         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7150         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7151         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7152         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7153         NextPiece(0);
7154         return 2; // grab
7155       case IcsObserving:
7156         if(!appData.icsEngineAnalyze) return -1;
7157       case IcsPlayingWhite:
7158       case IcsPlayingBlack:
7159         if(!appData.zippyPlay) goto noZip;
7160       case AnalyzeMode:
7161       case AnalyzeFile:
7162       case MachinePlaysWhite:
7163       case MachinePlaysBlack:
7164       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7165         if (!appData.dropMenu) {
7166           LoadPV(x, y);
7167           return 2; // flag front-end to grab mouse events
7168         }
7169         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7170            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7171       case EditGame:
7172       noZip:
7173         if (xSqr < 0 || ySqr < 0) return -1;
7174         if (!appData.dropMenu || appData.testLegality &&
7175             gameInfo.variant != VariantBughouse &&
7176             gameInfo.variant != VariantCrazyhouse) return -1;
7177         whichMenu = 1; // drop menu
7178         break;
7179       default:
7180         return -1;
7181     }
7182
7183     if (((*fromX = xSqr) < 0) ||
7184         ((*fromY = ySqr) < 0)) {
7185         *fromX = *fromY = -1;
7186         return -1;
7187     }
7188     if (flipView)
7189       *fromX = BOARD_WIDTH - 1 - *fromX;
7190     else
7191       *fromY = BOARD_HEIGHT - 1 - *fromY;
7192
7193     return whichMenu;
7194 }
7195
7196 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7197 {
7198 //    char * hint = lastHint;
7199     FrontEndProgramStats stats;
7200
7201     stats.which = cps == &first ? 0 : 1;
7202     stats.depth = cpstats->depth;
7203     stats.nodes = cpstats->nodes;
7204     stats.score = cpstats->score;
7205     stats.time = cpstats->time;
7206     stats.pv = cpstats->movelist;
7207     stats.hint = lastHint;
7208     stats.an_move_index = 0;
7209     stats.an_move_count = 0;
7210
7211     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7212         stats.hint = cpstats->move_name;
7213         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7214         stats.an_move_count = cpstats->nr_moves;
7215     }
7216
7217     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
7218
7219     SetProgramStats( &stats );
7220 }
7221
7222 void
7223 ClearEngineOutputPane(int which)
7224 {
7225     static FrontEndProgramStats dummyStats;
7226     dummyStats.which = which;
7227     dummyStats.pv = "#";
7228     SetProgramStats( &dummyStats );
7229 }
7230
7231 #define MAXPLAYERS 500
7232
7233 char *
7234 TourneyStandings(int display)
7235 {
7236     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7237     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7238     char result, *p, *names[MAXPLAYERS];
7239
7240     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7241         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7242     names[0] = p = strdup(appData.participants);
7243     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7244
7245     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7246
7247     while(result = appData.results[nr]) {
7248         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7249         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7250         wScore = bScore = 0;
7251         switch(result) {
7252           case '+': wScore = 2; break;
7253           case '-': bScore = 2; break;
7254           case '=': wScore = bScore = 1; break;
7255           case ' ':
7256           case '*': return strdup("busy"); // tourney not finished
7257         }
7258         score[w] += wScore;
7259         score[b] += bScore;
7260         games[w]++;
7261         games[b]++;
7262         nr++;
7263     }
7264     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7265     for(w=0; w<nPlayers; w++) {
7266         bScore = -1;
7267         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7268         ranking[w] = b; points[w] = bScore; score[b] = -2;
7269     }
7270     p = malloc(nPlayers*34+1);
7271     for(w=0; w<nPlayers && w<display; w++)
7272         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7273     free(names[0]);
7274     return p;
7275 }
7276
7277 void
7278 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7279 {       // count all piece types
7280         int p, f, r;
7281         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7282         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7283         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7284                 p = board[r][f];
7285                 pCnt[p]++;
7286                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7287                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7288                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7289                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7290                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7291                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7292         }
7293 }
7294
7295 int
7296 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7297 {
7298         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7299         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7300
7301         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7302         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7303         if(myPawns == 2 && nMine == 3) // KPP
7304             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7305         if(myPawns == 1 && nMine == 2) // KP
7306             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7307         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7308             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7309         if(myPawns) return FALSE;
7310         if(pCnt[WhiteRook+side])
7311             return pCnt[BlackRook-side] ||
7312                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7313                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7314                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7315         if(pCnt[WhiteCannon+side]) {
7316             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7317             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7318         }
7319         if(pCnt[WhiteKnight+side])
7320             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7321         return FALSE;
7322 }
7323
7324 int
7325 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7326 {
7327         VariantClass v = gameInfo.variant;
7328
7329         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7330         if(v == VariantShatranj) return TRUE; // always winnable through baring
7331         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7332         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7333
7334         if(v == VariantXiangqi) {
7335                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7336
7337                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7338                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7339                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7340                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7341                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7342                 if(stale) // we have at least one last-rank P plus perhaps C
7343                     return majors // KPKX
7344                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7345                 else // KCA*E*
7346                     return pCnt[WhiteFerz+side] // KCAK
7347                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7348                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7349                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7350
7351         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7352                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7353
7354                 if(nMine == 1) return FALSE; // bare King
7355                 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
7356                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7357                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7358                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7359                 if(pCnt[WhiteKnight+side])
7360                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7361                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7362                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7363                 if(nBishops)
7364                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7365                 if(pCnt[WhiteAlfil+side])
7366                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7367                 if(pCnt[WhiteWazir+side])
7368                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7369         }
7370
7371         return TRUE;
7372 }
7373
7374 int
7375 CompareWithRights(Board b1, Board b2)
7376 {
7377     int rights = 0;
7378     if(!CompareBoards(b1, b2)) return FALSE;
7379     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7380     /* compare castling rights */
7381     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7382            rights++; /* King lost rights, while rook still had them */
7383     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7384         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7385            rights++; /* but at least one rook lost them */
7386     }
7387     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7388            rights++;
7389     if( b1[CASTLING][5] != NoRights ) {
7390         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7391            rights++;
7392     }
7393     return rights == 0;
7394 }
7395
7396 int
7397 Adjudicate(ChessProgramState *cps)
7398 {       // [HGM] some adjudications useful with buggy engines
7399         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7400         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7401         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7402         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7403         int k, count = 0; static int bare = 1;
7404         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7405         Boolean canAdjudicate = !appData.icsActive;
7406
7407         // most tests only when we understand the game, i.e. legality-checking on
7408             if( appData.testLegality )
7409             {   /* [HGM] Some more adjudications for obstinate engines */
7410                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7411                 static int moveCount = 6;
7412                 ChessMove result;
7413                 char *reason = NULL;
7414
7415                 /* Count what is on board. */
7416                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7417
7418                 /* Some material-based adjudications that have to be made before stalemate test */
7419                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7420                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7421                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7422                      if(canAdjudicate && appData.checkMates) {
7423                          if(engineOpponent)
7424                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7425                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7426                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7427                          return 1;
7428                      }
7429                 }
7430
7431                 /* Bare King in Shatranj (loses) or Losers (wins) */
7432                 if( nrW == 1 || nrB == 1) {
7433                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7434                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7435                      if(canAdjudicate && appData.checkMates) {
7436                          if(engineOpponent)
7437                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7438                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7439                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7440                          return 1;
7441                      }
7442                   } else
7443                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7444                   {    /* bare King */
7445                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7446                         if(canAdjudicate && appData.checkMates) {
7447                             /* but only adjudicate if adjudication enabled */
7448                             if(engineOpponent)
7449                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7450                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7451                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7452                             return 1;
7453                         }
7454                   }
7455                 } else bare = 1;
7456
7457
7458             // don't wait for engine to announce game end if we can judge ourselves
7459             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7460               case MT_CHECK:
7461                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7462                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7463                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7464                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7465                             checkCnt++;
7466                         if(checkCnt >= 2) {
7467                             reason = "Xboard adjudication: 3rd check";
7468                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7469                             break;
7470                         }
7471                     }
7472                 }
7473               case MT_NONE:
7474               default:
7475                 break;
7476               case MT_STALEMATE:
7477               case MT_STAINMATE:
7478                 reason = "Xboard adjudication: Stalemate";
7479                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7480                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7481                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7482                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7483                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7484                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7485                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7486                                                                         EP_CHECKMATE : EP_WINS);
7487                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7488                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7489                 }
7490                 break;
7491               case MT_CHECKMATE:
7492                 reason = "Xboard adjudication: Checkmate";
7493                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7494                 break;
7495             }
7496
7497                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7498                     case EP_STALEMATE:
7499                         result = GameIsDrawn; break;
7500                     case EP_CHECKMATE:
7501                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7502                     case EP_WINS:
7503                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7504                     default:
7505                         result = EndOfFile;
7506                 }
7507                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7508                     if(engineOpponent)
7509                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7510                     GameEnds( result, reason, GE_XBOARD );
7511                     return 1;
7512                 }
7513
7514                 /* Next absolutely insufficient mating material. */
7515                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7516                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7517                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7518
7519                      /* always flag draws, for judging claims */
7520                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7521
7522                      if(canAdjudicate && appData.materialDraws) {
7523                          /* but only adjudicate them if adjudication enabled */
7524                          if(engineOpponent) {
7525                            SendToProgram("force\n", engineOpponent); // suppress reply
7526                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7527                          }
7528                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7529                          return 1;
7530                      }
7531                 }
7532
7533                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7534                 if(gameInfo.variant == VariantXiangqi ?
7535                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7536                  : nrW + nrB == 4 &&
7537                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7538                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7539                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7540                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7541                    ) ) {
7542                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7543                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7544                           if(engineOpponent) {
7545                             SendToProgram("force\n", engineOpponent); // suppress reply
7546                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7547                           }
7548                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7549                           return 1;
7550                      }
7551                 } else moveCount = 6;
7552             }
7553         if (appData.debugMode) { int i;
7554             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7555                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7556                     appData.drawRepeats);
7557             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7558               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7559
7560         }
7561
7562         // Repetition draws and 50-move rule can be applied independently of legality testing
7563
7564                 /* Check for rep-draws */
7565                 count = 0;
7566                 for(k = forwardMostMove-2;
7567                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7568                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7569                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7570                     k-=2)
7571                 {   int rights=0;
7572                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7573                         /* compare castling rights */
7574                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7575                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7576                                 rights++; /* King lost rights, while rook still had them */
7577                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7578                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7579                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7580                                    rights++; /* but at least one rook lost them */
7581                         }
7582                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7583                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7584                                 rights++;
7585                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7586                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7587                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7588                                    rights++;
7589                         }
7590                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7591                             && appData.drawRepeats > 1) {
7592                              /* adjudicate after user-specified nr of repeats */
7593                              int result = GameIsDrawn;
7594                              char *details = "XBoard adjudication: repetition draw";
7595                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7596                                 // [HGM] xiangqi: check for forbidden perpetuals
7597                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7598                                 for(m=forwardMostMove; m>k; m-=2) {
7599                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7600                                         ourPerpetual = 0; // the current mover did not always check
7601                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7602                                         hisPerpetual = 0; // the opponent did not always check
7603                                 }
7604                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7605                                                                         ourPerpetual, hisPerpetual);
7606                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7607                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7608                                     details = "Xboard adjudication: perpetual checking";
7609                                 } else
7610                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7611                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7612                                 } else
7613                                 // Now check for perpetual chases
7614                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7615                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7616                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7617                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7618                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7619                                         details = "Xboard adjudication: perpetual chasing";
7620                                     } else
7621                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7622                                         break; // Abort repetition-checking loop.
7623                                 }
7624                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7625                              }
7626                              if(engineOpponent) {
7627                                SendToProgram("force\n", engineOpponent); // suppress reply
7628                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7629                              }
7630                              GameEnds( result, details, GE_XBOARD );
7631                              return 1;
7632                         }
7633                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7634                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7635                     }
7636                 }
7637
7638                 /* Now we test for 50-move draws. Determine ply count */
7639                 count = forwardMostMove;
7640                 /* look for last irreversble move */
7641                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7642                     count--;
7643                 /* if we hit starting position, add initial plies */
7644                 if( count == backwardMostMove )
7645                     count -= initialRulePlies;
7646                 count = forwardMostMove - count;
7647                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7648                         // adjust reversible move counter for checks in Xiangqi
7649                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7650                         if(i < backwardMostMove) i = backwardMostMove;
7651                         while(i <= forwardMostMove) {
7652                                 lastCheck = inCheck; // check evasion does not count
7653                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7654                                 if(inCheck || lastCheck) count--; // check does not count
7655                                 i++;
7656                         }
7657                 }
7658                 if( count >= 100)
7659                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7660                          /* this is used to judge if draw claims are legal */
7661                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7662                          if(engineOpponent) {
7663                            SendToProgram("force\n", engineOpponent); // suppress reply
7664                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7665                          }
7666                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7667                          return 1;
7668                 }
7669
7670                 /* if draw offer is pending, treat it as a draw claim
7671                  * when draw condition present, to allow engines a way to
7672                  * claim draws before making their move to avoid a race
7673                  * condition occurring after their move
7674                  */
7675                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7676                          char *p = NULL;
7677                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7678                              p = "Draw claim: 50-move rule";
7679                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7680                              p = "Draw claim: 3-fold repetition";
7681                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7682                              p = "Draw claim: insufficient mating material";
7683                          if( p != NULL && canAdjudicate) {
7684                              if(engineOpponent) {
7685                                SendToProgram("force\n", engineOpponent); // suppress reply
7686                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7687                              }
7688                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7689                              return 1;
7690                          }
7691                 }
7692
7693                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7694                     if(engineOpponent) {
7695                       SendToProgram("force\n", engineOpponent); // suppress reply
7696                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7697                     }
7698                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7699                     return 1;
7700                 }
7701         return 0;
7702 }
7703
7704 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7705 {   // [HGM] book: this routine intercepts moves to simulate book replies
7706     char *bookHit = NULL;
7707
7708     //first determine if the incoming move brings opponent into his book
7709     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7710         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7711     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7712     if(bookHit != NULL && !cps->bookSuspend) {
7713         // make sure opponent is not going to reply after receiving move to book position
7714         SendToProgram("force\n", cps);
7715         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7716     }
7717     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7718     // now arrange restart after book miss
7719     if(bookHit) {
7720         // after a book hit we never send 'go', and the code after the call to this routine
7721         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7722         char buf[MSG_SIZ], *move = bookHit;
7723         if(cps->useSAN) {
7724             int fromX, fromY, toX, toY;
7725             char promoChar;
7726             ChessMove moveType;
7727             move = buf + 30;
7728             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7729                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7730                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7731                                     PosFlags(forwardMostMove),
7732                                     fromY, fromX, toY, toX, promoChar, move);
7733             } else {
7734                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7735                 bookHit = NULL;
7736             }
7737         }
7738         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7739         SendToProgram(buf, cps);
7740         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7741     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7742         SendToProgram("go\n", cps);
7743         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7744     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7745         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7746             SendToProgram("go\n", cps);
7747         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7748     }
7749     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7750 }
7751
7752 char *savedMessage;
7753 ChessProgramState *savedState;
7754 void DeferredBookMove(void)
7755 {
7756         if(savedState->lastPing != savedState->lastPong)
7757                     ScheduleDelayedEvent(DeferredBookMove, 10);
7758         else
7759         HandleMachineMove(savedMessage, savedState);
7760 }
7761
7762 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7763
7764 void
7765 HandleMachineMove(message, cps)
7766      char *message;
7767      ChessProgramState *cps;
7768 {
7769     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7770     char realname[MSG_SIZ];
7771     int fromX, fromY, toX, toY;
7772     ChessMove moveType;
7773     char promoChar;
7774     char *p, *pv=buf1;
7775     int machineWhite;
7776     char *bookHit;
7777
7778     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7779         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7780         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7781             DisplayError(_("Invalid pairing from pairing engine"), 0);
7782             return;
7783         }
7784         pairingReceived = 1;
7785         NextMatchGame();
7786         return; // Skim the pairing messages here.
7787     }
7788
7789     cps->userError = 0;
7790
7791 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7792     /*
7793      * Kludge to ignore BEL characters
7794      */
7795     while (*message == '\007') message++;
7796
7797     /*
7798      * [HGM] engine debug message: ignore lines starting with '#' character
7799      */
7800     if(cps->debug && *message == '#') return;
7801
7802     /*
7803      * Look for book output
7804      */
7805     if (cps == &first && bookRequested) {
7806         if (message[0] == '\t' || message[0] == ' ') {
7807             /* Part of the book output is here; append it */
7808             strcat(bookOutput, message);
7809             strcat(bookOutput, "  \n");
7810             return;
7811         } else if (bookOutput[0] != NULLCHAR) {
7812             /* All of book output has arrived; display it */
7813             char *p = bookOutput;
7814             while (*p != NULLCHAR) {
7815                 if (*p == '\t') *p = ' ';
7816                 p++;
7817             }
7818             DisplayInformation(bookOutput);
7819             bookRequested = FALSE;
7820             /* Fall through to parse the current output */
7821         }
7822     }
7823
7824     /*
7825      * Look for machine move.
7826      */
7827     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7828         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7829     {
7830         /* This method is only useful on engines that support ping */
7831         if (cps->lastPing != cps->lastPong) {
7832           if (gameMode == BeginningOfGame) {
7833             /* Extra move from before last new; ignore */
7834             if (appData.debugMode) {
7835                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7836             }
7837           } else {
7838             if (appData.debugMode) {
7839                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7840                         cps->which, gameMode);
7841             }
7842
7843             SendToProgram("undo\n", cps);
7844           }
7845           return;
7846         }
7847
7848         switch (gameMode) {
7849           case BeginningOfGame:
7850             /* Extra move from before last reset; ignore */
7851             if (appData.debugMode) {
7852                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7853             }
7854             return;
7855
7856           case EndOfGame:
7857           case IcsIdle:
7858           default:
7859             /* Extra move after we tried to stop.  The mode test is
7860                not a reliable way of detecting this problem, but it's
7861                the best we can do on engines that don't support ping.
7862             */
7863             if (appData.debugMode) {
7864                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7865                         cps->which, gameMode);
7866             }
7867             SendToProgram("undo\n", cps);
7868             return;
7869
7870           case MachinePlaysWhite:
7871           case IcsPlayingWhite:
7872             machineWhite = TRUE;
7873             break;
7874
7875           case MachinePlaysBlack:
7876           case IcsPlayingBlack:
7877             machineWhite = FALSE;
7878             break;
7879
7880           case TwoMachinesPlay:
7881             machineWhite = (cps->twoMachinesColor[0] == 'w');
7882             break;
7883         }
7884         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7885             if (appData.debugMode) {
7886                 fprintf(debugFP,
7887                         "Ignoring move out of turn by %s, gameMode %d"
7888                         ", forwardMost %d\n",
7889                         cps->which, gameMode, forwardMostMove);
7890             }
7891             return;
7892         }
7893
7894     if (appData.debugMode) { int f = forwardMostMove;
7895         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7896                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7897                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7898     }
7899         if(cps->alphaRank) AlphaRank(machineMove, 4);
7900         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7901                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7902             /* Machine move could not be parsed; ignore it. */
7903           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7904                     machineMove, _(cps->which));
7905             DisplayError(buf1, 0);
7906             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7907                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7908             if (gameMode == TwoMachinesPlay) {
7909               GameEnds(machineWhite ? BlackWins : WhiteWins,
7910                        buf1, GE_XBOARD);
7911             }
7912             return;
7913         }
7914
7915         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7916         /* So we have to redo legality test with true e.p. status here,  */
7917         /* to make sure an illegal e.p. capture does not slip through,   */
7918         /* to cause a forfeit on a justified illegal-move complaint      */
7919         /* of the opponent.                                              */
7920         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7921            ChessMove moveType;
7922            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7923                              fromY, fromX, toY, toX, promoChar);
7924             if (appData.debugMode) {
7925                 int i;
7926                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7927                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7928                 fprintf(debugFP, "castling rights\n");
7929             }
7930             if(moveType == IllegalMove) {
7931               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7932                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7933                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7934                            buf1, GE_XBOARD);
7935                 return;
7936            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7937            /* [HGM] Kludge to handle engines that send FRC-style castling
7938               when they shouldn't (like TSCP-Gothic) */
7939            switch(moveType) {
7940              case WhiteASideCastleFR:
7941              case BlackASideCastleFR:
7942                toX+=2;
7943                currentMoveString[2]++;
7944                break;
7945              case WhiteHSideCastleFR:
7946              case BlackHSideCastleFR:
7947                toX--;
7948                currentMoveString[2]--;
7949                break;
7950              default: ; // nothing to do, but suppresses warning of pedantic compilers
7951            }
7952         }
7953         hintRequested = FALSE;
7954         lastHint[0] = NULLCHAR;
7955         bookRequested = FALSE;
7956         /* Program may be pondering now */
7957         cps->maybeThinking = TRUE;
7958         if (cps->sendTime == 2) cps->sendTime = 1;
7959         if (cps->offeredDraw) cps->offeredDraw--;
7960
7961         /* [AS] Save move info*/
7962         pvInfoList[ forwardMostMove ].score = programStats.score;
7963         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7964         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7965
7966         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7967
7968         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7969         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7970             int count = 0;
7971
7972             while( count < adjudicateLossPlies ) {
7973                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7974
7975                 if( count & 1 ) {
7976                     score = -score; /* Flip score for winning side */
7977                 }
7978
7979                 if( score > adjudicateLossThreshold ) {
7980                     break;
7981                 }
7982
7983                 count++;
7984             }
7985
7986             if( count >= adjudicateLossPlies ) {
7987                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7988
7989                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7990                     "Xboard adjudication",
7991                     GE_XBOARD );
7992
7993                 return;
7994             }
7995         }
7996
7997         if(Adjudicate(cps)) {
7998             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7999             return; // [HGM] adjudicate: for all automatic game ends
8000         }
8001
8002 #if ZIPPY
8003         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8004             first.initDone) {
8005           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8006                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8007                 SendToICS("draw ");
8008                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8009           }
8010           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8011           ics_user_moved = 1;
8012           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8013                 char buf[3*MSG_SIZ];
8014
8015                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8016                         programStats.score / 100.,
8017                         programStats.depth,
8018                         programStats.time / 100.,
8019                         (unsigned int)programStats.nodes,
8020                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8021                         programStats.movelist);
8022                 SendToICS(buf);
8023 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8024           }
8025         }
8026 #endif
8027
8028         /* [AS] Clear stats for next move */
8029         ClearProgramStats();
8030         thinkOutput[0] = NULLCHAR;
8031         hiddenThinkOutputState = 0;
8032
8033         bookHit = NULL;
8034         if (gameMode == TwoMachinesPlay) {
8035             /* [HGM] relaying draw offers moved to after reception of move */
8036             /* and interpreting offer as claim if it brings draw condition */
8037             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8038                 SendToProgram("draw\n", cps->other);
8039             }
8040             if (cps->other->sendTime) {
8041                 SendTimeRemaining(cps->other,
8042                                   cps->other->twoMachinesColor[0] == 'w');
8043             }
8044             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8045             if (firstMove && !bookHit) {
8046                 firstMove = FALSE;
8047                 if (cps->other->useColors) {
8048                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8049                 }
8050                 SendToProgram("go\n", cps->other);
8051             }
8052             cps->other->maybeThinking = TRUE;
8053         }
8054
8055         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8056
8057         if (!pausing && appData.ringBellAfterMoves) {
8058             RingBell();
8059         }
8060
8061         /*
8062          * Reenable menu items that were disabled while
8063          * machine was thinking
8064          */
8065         if (gameMode != TwoMachinesPlay)
8066             SetUserThinkingEnables();
8067
8068         // [HGM] book: after book hit opponent has received move and is now in force mode
8069         // force the book reply into it, and then fake that it outputted this move by jumping
8070         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8071         if(bookHit) {
8072                 static char bookMove[MSG_SIZ]; // a bit generous?
8073
8074                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8075                 strcat(bookMove, bookHit);
8076                 message = bookMove;
8077                 cps = cps->other;
8078                 programStats.nodes = programStats.depth = programStats.time =
8079                 programStats.score = programStats.got_only_move = 0;
8080                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8081
8082                 if(cps->lastPing != cps->lastPong) {
8083                     savedMessage = message; // args for deferred call
8084                     savedState = cps;
8085                     ScheduleDelayedEvent(DeferredBookMove, 10);
8086                     return;
8087                 }
8088                 goto FakeBookMove;
8089         }
8090
8091         return;
8092     }
8093
8094     /* Set special modes for chess engines.  Later something general
8095      *  could be added here; for now there is just one kludge feature,
8096      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8097      *  when "xboard" is given as an interactive command.
8098      */
8099     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8100         cps->useSigint = FALSE;
8101         cps->useSigterm = FALSE;
8102     }
8103     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8104       ParseFeatures(message+8, cps);
8105       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8106     }
8107
8108     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8109       int dummy, s=6; char buf[MSG_SIZ];
8110       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8111       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8112       ParseFEN(boards[0], &dummy, message+s);
8113       DrawPosition(TRUE, boards[0]);
8114       startedFromSetupPosition = TRUE;
8115       return;
8116     }
8117     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8118      * want this, I was asked to put it in, and obliged.
8119      */
8120     if (!strncmp(message, "setboard ", 9)) {
8121         Board initial_position;
8122
8123         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8124
8125         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8126             DisplayError(_("Bad FEN received from engine"), 0);
8127             return ;
8128         } else {
8129            Reset(TRUE, FALSE);
8130            CopyBoard(boards[0], initial_position);
8131            initialRulePlies = FENrulePlies;
8132            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8133            else gameMode = MachinePlaysBlack;
8134            DrawPosition(FALSE, boards[currentMove]);
8135         }
8136         return;
8137     }
8138
8139     /*
8140      * Look for communication commands
8141      */
8142     if (!strncmp(message, "telluser ", 9)) {
8143         if(message[9] == '\\' && message[10] == '\\')
8144             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8145         PlayTellSound();
8146         DisplayNote(message + 9);
8147         return;
8148     }
8149     if (!strncmp(message, "tellusererror ", 14)) {
8150         cps->userError = 1;
8151         if(message[14] == '\\' && message[15] == '\\')
8152             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8153         PlayTellSound();
8154         DisplayError(message + 14, 0);
8155         return;
8156     }
8157     if (!strncmp(message, "tellopponent ", 13)) {
8158       if (appData.icsActive) {
8159         if (loggedOn) {
8160           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8161           SendToICS(buf1);
8162         }
8163       } else {
8164         DisplayNote(message + 13);
8165       }
8166       return;
8167     }
8168     if (!strncmp(message, "tellothers ", 11)) {
8169       if (appData.icsActive) {
8170         if (loggedOn) {
8171           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8172           SendToICS(buf1);
8173         }
8174       }
8175       return;
8176     }
8177     if (!strncmp(message, "tellall ", 8)) {
8178       if (appData.icsActive) {
8179         if (loggedOn) {
8180           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8181           SendToICS(buf1);
8182         }
8183       } else {
8184         DisplayNote(message + 8);
8185       }
8186       return;
8187     }
8188     if (strncmp(message, "warning", 7) == 0) {
8189         /* Undocumented feature, use tellusererror in new code */
8190         DisplayError(message, 0);
8191         return;
8192     }
8193     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8194         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8195         strcat(realname, " query");
8196         AskQuestion(realname, buf2, buf1, cps->pr);
8197         return;
8198     }
8199     /* Commands from the engine directly to ICS.  We don't allow these to be
8200      *  sent until we are logged on. Crafty kibitzes have been known to
8201      *  interfere with the login process.
8202      */
8203     if (loggedOn) {
8204         if (!strncmp(message, "tellics ", 8)) {
8205             SendToICS(message + 8);
8206             SendToICS("\n");
8207             return;
8208         }
8209         if (!strncmp(message, "tellicsnoalias ", 15)) {
8210             SendToICS(ics_prefix);
8211             SendToICS(message + 15);
8212             SendToICS("\n");
8213             return;
8214         }
8215         /* The following are for backward compatibility only */
8216         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8217             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8218             SendToICS(ics_prefix);
8219             SendToICS(message);
8220             SendToICS("\n");
8221             return;
8222         }
8223     }
8224     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8225         return;
8226     }
8227     /*
8228      * If the move is illegal, cancel it and redraw the board.
8229      * Also deal with other error cases.  Matching is rather loose
8230      * here to accommodate engines written before the spec.
8231      */
8232     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8233         strncmp(message, "Error", 5) == 0) {
8234         if (StrStr(message, "name") ||
8235             StrStr(message, "rating") || StrStr(message, "?") ||
8236             StrStr(message, "result") || StrStr(message, "board") ||
8237             StrStr(message, "bk") || StrStr(message, "computer") ||
8238             StrStr(message, "variant") || StrStr(message, "hint") ||
8239             StrStr(message, "random") || StrStr(message, "depth") ||
8240             StrStr(message, "accepted")) {
8241             return;
8242         }
8243         if (StrStr(message, "protover")) {
8244           /* Program is responding to input, so it's apparently done
8245              initializing, and this error message indicates it is
8246              protocol version 1.  So we don't need to wait any longer
8247              for it to initialize and send feature commands. */
8248           FeatureDone(cps, 1);
8249           cps->protocolVersion = 1;
8250           return;
8251         }
8252         cps->maybeThinking = FALSE;
8253
8254         if (StrStr(message, "draw")) {
8255             /* Program doesn't have "draw" command */
8256             cps->sendDrawOffers = 0;
8257             return;
8258         }
8259         if (cps->sendTime != 1 &&
8260             (StrStr(message, "time") || StrStr(message, "otim"))) {
8261           /* Program apparently doesn't have "time" or "otim" command */
8262           cps->sendTime = 0;
8263           return;
8264         }
8265         if (StrStr(message, "analyze")) {
8266             cps->analysisSupport = FALSE;
8267             cps->analyzing = FALSE;
8268 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8269             EditGameEvent(); // [HGM] try to preserve loaded game
8270             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8271             DisplayError(buf2, 0);
8272             return;
8273         }
8274         if (StrStr(message, "(no matching move)st")) {
8275           /* Special kludge for GNU Chess 4 only */
8276           cps->stKludge = TRUE;
8277           SendTimeControl(cps, movesPerSession, timeControl,
8278                           timeIncrement, appData.searchDepth,
8279                           searchTime);
8280           return;
8281         }
8282         if (StrStr(message, "(no matching move)sd")) {
8283           /* Special kludge for GNU Chess 4 only */
8284           cps->sdKludge = TRUE;
8285           SendTimeControl(cps, movesPerSession, timeControl,
8286                           timeIncrement, appData.searchDepth,
8287                           searchTime);
8288           return;
8289         }
8290         if (!StrStr(message, "llegal")) {
8291             return;
8292         }
8293         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8294             gameMode == IcsIdle) return;
8295         if (forwardMostMove <= backwardMostMove) return;
8296         if (pausing) PauseEvent();
8297       if(appData.forceIllegal) {
8298             // [HGM] illegal: machine refused move; force position after move into it
8299           SendToProgram("force\n", cps);
8300           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8301                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8302                 // when black is to move, while there might be nothing on a2 or black
8303                 // might already have the move. So send the board as if white has the move.
8304                 // But first we must change the stm of the engine, as it refused the last move
8305                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8306                 if(WhiteOnMove(forwardMostMove)) {
8307                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8308                     SendBoard(cps, forwardMostMove); // kludgeless board
8309                 } else {
8310                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8311                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8312                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8313                 }
8314           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8315             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8316                  gameMode == TwoMachinesPlay)
8317               SendToProgram("go\n", cps);
8318             return;
8319       } else
8320         if (gameMode == PlayFromGameFile) {
8321             /* Stop reading this game file */
8322             gameMode = EditGame;
8323             ModeHighlight();
8324         }
8325         /* [HGM] illegal-move claim should forfeit game when Xboard */
8326         /* only passes fully legal moves                            */
8327         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8328             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8329                                 "False illegal-move claim", GE_XBOARD );
8330             return; // do not take back move we tested as valid
8331         }
8332         currentMove = forwardMostMove-1;
8333         DisplayMove(currentMove-1); /* before DisplayMoveError */
8334         SwitchClocks(forwardMostMove-1); // [HGM] race
8335         DisplayBothClocks();
8336         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8337                 parseList[currentMove], _(cps->which));
8338         DisplayMoveError(buf1);
8339         DrawPosition(FALSE, boards[currentMove]);
8340         return;
8341     }
8342     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8343         /* Program has a broken "time" command that
8344            outputs a string not ending in newline.
8345            Don't use it. */
8346         cps->sendTime = 0;
8347     }
8348
8349     /*
8350      * If chess program startup fails, exit with an error message.
8351      * Attempts to recover here are futile.
8352      */
8353     if ((StrStr(message, "unknown host") != NULL)
8354         || (StrStr(message, "No remote directory") != NULL)
8355         || (StrStr(message, "not found") != NULL)
8356         || (StrStr(message, "No such file") != NULL)
8357         || (StrStr(message, "can't alloc") != NULL)
8358         || (StrStr(message, "Permission denied") != NULL)) {
8359
8360         cps->maybeThinking = FALSE;
8361         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8362                 _(cps->which), cps->program, cps->host, message);
8363         RemoveInputSource(cps->isr);
8364         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8365             if(cps == &first) appData.noChessProgram = TRUE;
8366             DisplayError(buf1, 0);
8367         }
8368         return;
8369     }
8370
8371     /*
8372      * Look for hint output
8373      */
8374     if (sscanf(message, "Hint: %s", buf1) == 1) {
8375         if (cps == &first && hintRequested) {
8376             hintRequested = FALSE;
8377             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8378                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8379                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8380                                     PosFlags(forwardMostMove),
8381                                     fromY, fromX, toY, toX, promoChar, buf1);
8382                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8383                 DisplayInformation(buf2);
8384             } else {
8385                 /* Hint move could not be parsed!? */
8386               snprintf(buf2, sizeof(buf2),
8387                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8388                         buf1, _(cps->which));
8389                 DisplayError(buf2, 0);
8390             }
8391         } else {
8392           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8393         }
8394         return;
8395     }
8396
8397     /*
8398      * Ignore other messages if game is not in progress
8399      */
8400     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8401         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8402
8403     /*
8404      * look for win, lose, draw, or draw offer
8405      */
8406     if (strncmp(message, "1-0", 3) == 0) {
8407         char *p, *q, *r = "";
8408         p = strchr(message, '{');
8409         if (p) {
8410             q = strchr(p, '}');
8411             if (q) {
8412                 *q = NULLCHAR;
8413                 r = p + 1;
8414             }
8415         }
8416         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8417         return;
8418     } else if (strncmp(message, "0-1", 3) == 0) {
8419         char *p, *q, *r = "";
8420         p = strchr(message, '{');
8421         if (p) {
8422             q = strchr(p, '}');
8423             if (q) {
8424                 *q = NULLCHAR;
8425                 r = p + 1;
8426             }
8427         }
8428         /* Kludge for Arasan 4.1 bug */
8429         if (strcmp(r, "Black resigns") == 0) {
8430             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8431             return;
8432         }
8433         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8434         return;
8435     } else if (strncmp(message, "1/2", 3) == 0) {
8436         char *p, *q, *r = "";
8437         p = strchr(message, '{');
8438         if (p) {
8439             q = strchr(p, '}');
8440             if (q) {
8441                 *q = NULLCHAR;
8442                 r = p + 1;
8443             }
8444         }
8445
8446         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8447         return;
8448
8449     } else if (strncmp(message, "White resign", 12) == 0) {
8450         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8451         return;
8452     } else if (strncmp(message, "Black resign", 12) == 0) {
8453         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8454         return;
8455     } else if (strncmp(message, "White matches", 13) == 0 ||
8456                strncmp(message, "Black matches", 13) == 0   ) {
8457         /* [HGM] ignore GNUShogi noises */
8458         return;
8459     } else if (strncmp(message, "White", 5) == 0 &&
8460                message[5] != '(' &&
8461                StrStr(message, "Black") == NULL) {
8462         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8463         return;
8464     } else if (strncmp(message, "Black", 5) == 0 &&
8465                message[5] != '(') {
8466         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8467         return;
8468     } else if (strcmp(message, "resign") == 0 ||
8469                strcmp(message, "computer resigns") == 0) {
8470         switch (gameMode) {
8471           case MachinePlaysBlack:
8472           case IcsPlayingBlack:
8473             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8474             break;
8475           case MachinePlaysWhite:
8476           case IcsPlayingWhite:
8477             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8478             break;
8479           case TwoMachinesPlay:
8480             if (cps->twoMachinesColor[0] == 'w')
8481               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8482             else
8483               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8484             break;
8485           default:
8486             /* can't happen */
8487             break;
8488         }
8489         return;
8490     } else if (strncmp(message, "opponent mates", 14) == 0) {
8491         switch (gameMode) {
8492           case MachinePlaysBlack:
8493           case IcsPlayingBlack:
8494             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8495             break;
8496           case MachinePlaysWhite:
8497           case IcsPlayingWhite:
8498             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8499             break;
8500           case TwoMachinesPlay:
8501             if (cps->twoMachinesColor[0] == 'w')
8502               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8503             else
8504               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8505             break;
8506           default:
8507             /* can't happen */
8508             break;
8509         }
8510         return;
8511     } else if (strncmp(message, "computer mates", 14) == 0) {
8512         switch (gameMode) {
8513           case MachinePlaysBlack:
8514           case IcsPlayingBlack:
8515             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8516             break;
8517           case MachinePlaysWhite:
8518           case IcsPlayingWhite:
8519             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8520             break;
8521           case TwoMachinesPlay:
8522             if (cps->twoMachinesColor[0] == 'w')
8523               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8524             else
8525               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8526             break;
8527           default:
8528             /* can't happen */
8529             break;
8530         }
8531         return;
8532     } else if (strncmp(message, "checkmate", 9) == 0) {
8533         if (WhiteOnMove(forwardMostMove)) {
8534             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8535         } else {
8536             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8537         }
8538         return;
8539     } else if (strstr(message, "Draw") != NULL ||
8540                strstr(message, "game is a draw") != NULL) {
8541         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8542         return;
8543     } else if (strstr(message, "offer") != NULL &&
8544                strstr(message, "draw") != NULL) {
8545 #if ZIPPY
8546         if (appData.zippyPlay && first.initDone) {
8547             /* Relay offer to ICS */
8548             SendToICS(ics_prefix);
8549             SendToICS("draw\n");
8550         }
8551 #endif
8552         cps->offeredDraw = 2; /* valid until this engine moves twice */
8553         if (gameMode == TwoMachinesPlay) {
8554             if (cps->other->offeredDraw) {
8555                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8556             /* [HGM] in two-machine mode we delay relaying draw offer      */
8557             /* until after we also have move, to see if it is really claim */
8558             }
8559         } else if (gameMode == MachinePlaysWhite ||
8560                    gameMode == MachinePlaysBlack) {
8561           if (userOfferedDraw) {
8562             DisplayInformation(_("Machine accepts your draw offer"));
8563             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8564           } else {
8565             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8566           }
8567         }
8568     }
8569
8570
8571     /*
8572      * Look for thinking output
8573      */
8574     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8575           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8576                                 ) {
8577         int plylev, mvleft, mvtot, curscore, time;
8578         char mvname[MOVE_LEN];
8579         u64 nodes; // [DM]
8580         char plyext;
8581         int ignore = FALSE;
8582         int prefixHint = FALSE;
8583         mvname[0] = NULLCHAR;
8584
8585         switch (gameMode) {
8586           case MachinePlaysBlack:
8587           case IcsPlayingBlack:
8588             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8589             break;
8590           case MachinePlaysWhite:
8591           case IcsPlayingWhite:
8592             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8593             break;
8594           case AnalyzeMode:
8595           case AnalyzeFile:
8596             break;
8597           case IcsObserving: /* [DM] icsEngineAnalyze */
8598             if (!appData.icsEngineAnalyze) ignore = TRUE;
8599             break;
8600           case TwoMachinesPlay:
8601             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8602                 ignore = TRUE;
8603             }
8604             break;
8605           default:
8606             ignore = TRUE;
8607             break;
8608         }
8609
8610         if (!ignore) {
8611             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8612             buf1[0] = NULLCHAR;
8613             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8614                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8615
8616                 if (plyext != ' ' && plyext != '\t') {
8617                     time *= 100;
8618                 }
8619
8620                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8621                 if( cps->scoreIsAbsolute &&
8622                     ( gameMode == MachinePlaysBlack ||
8623                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8624                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8625                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8626                      !WhiteOnMove(currentMove)
8627                     ) )
8628                 {
8629                     curscore = -curscore;
8630                 }
8631
8632                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8633
8634                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8635                         char buf[MSG_SIZ];
8636                         FILE *f;
8637                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8638                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8639                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8640                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8641                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8642                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8643                                 fclose(f);
8644                         } else DisplayError("failed writing PV", 0);
8645                 }
8646
8647                 tempStats.depth = plylev;
8648                 tempStats.nodes = nodes;
8649                 tempStats.time = time;
8650                 tempStats.score = curscore;
8651                 tempStats.got_only_move = 0;
8652
8653                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8654                         int ticklen;
8655
8656                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8657                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8658                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8659                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8660                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8661                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8662                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8663                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8664                 }
8665
8666                 /* Buffer overflow protection */
8667                 if (pv[0] != NULLCHAR) {
8668                     if (strlen(pv) >= sizeof(tempStats.movelist)
8669                         && appData.debugMode) {
8670                         fprintf(debugFP,
8671                                 "PV is too long; using the first %u bytes.\n",
8672                                 (unsigned) sizeof(tempStats.movelist) - 1);
8673                     }
8674
8675                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8676                 } else {
8677                     sprintf(tempStats.movelist, " no PV\n");
8678                 }
8679
8680                 if (tempStats.seen_stat) {
8681                     tempStats.ok_to_send = 1;
8682                 }
8683
8684                 if (strchr(tempStats.movelist, '(') != NULL) {
8685                     tempStats.line_is_book = 1;
8686                     tempStats.nr_moves = 0;
8687                     tempStats.moves_left = 0;
8688                 } else {
8689                     tempStats.line_is_book = 0;
8690                 }
8691
8692                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8693                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8694
8695                 SendProgramStatsToFrontend( cps, &tempStats );
8696
8697                 /*
8698                     [AS] Protect the thinkOutput buffer from overflow... this
8699                     is only useful if buf1 hasn't overflowed first!
8700                 */
8701                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8702                          plylev,
8703                          (gameMode == TwoMachinesPlay ?
8704                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8705                          ((double) curscore) / 100.0,
8706                          prefixHint ? lastHint : "",
8707                          prefixHint ? " " : "" );
8708
8709                 if( buf1[0] != NULLCHAR ) {
8710                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8711
8712                     if( strlen(pv) > max_len ) {
8713                         if( appData.debugMode) {
8714                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8715                         }
8716                         pv[max_len+1] = '\0';
8717                     }
8718
8719                     strcat( thinkOutput, pv);
8720                 }
8721
8722                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8723                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8724                     DisplayMove(currentMove - 1);
8725                 }
8726                 return;
8727
8728             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8729                 /* crafty (9.25+) says "(only move) <move>"
8730                  * if there is only 1 legal move
8731                  */
8732                 sscanf(p, "(only move) %s", buf1);
8733                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8734                 sprintf(programStats.movelist, "%s (only move)", buf1);
8735                 programStats.depth = 1;
8736                 programStats.nr_moves = 1;
8737                 programStats.moves_left = 1;
8738                 programStats.nodes = 1;
8739                 programStats.time = 1;
8740                 programStats.got_only_move = 1;
8741
8742                 /* Not really, but we also use this member to
8743                    mean "line isn't going to change" (Crafty
8744                    isn't searching, so stats won't change) */
8745                 programStats.line_is_book = 1;
8746
8747                 SendProgramStatsToFrontend( cps, &programStats );
8748
8749                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8750                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8751                     DisplayMove(currentMove - 1);
8752                 }
8753                 return;
8754             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8755                               &time, &nodes, &plylev, &mvleft,
8756                               &mvtot, mvname) >= 5) {
8757                 /* The stat01: line is from Crafty (9.29+) in response
8758                    to the "." command */
8759                 programStats.seen_stat = 1;
8760                 cps->maybeThinking = TRUE;
8761
8762                 if (programStats.got_only_move || !appData.periodicUpdates)
8763                   return;
8764
8765                 programStats.depth = plylev;
8766                 programStats.time = time;
8767                 programStats.nodes = nodes;
8768                 programStats.moves_left = mvleft;
8769                 programStats.nr_moves = mvtot;
8770                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8771                 programStats.ok_to_send = 1;
8772                 programStats.movelist[0] = '\0';
8773
8774                 SendProgramStatsToFrontend( cps, &programStats );
8775
8776                 return;
8777
8778             } else if (strncmp(message,"++",2) == 0) {
8779                 /* Crafty 9.29+ outputs this */
8780                 programStats.got_fail = 2;
8781                 return;
8782
8783             } else if (strncmp(message,"--",2) == 0) {
8784                 /* Crafty 9.29+ outputs this */
8785                 programStats.got_fail = 1;
8786                 return;
8787
8788             } else if (thinkOutput[0] != NULLCHAR &&
8789                        strncmp(message, "    ", 4) == 0) {
8790                 unsigned message_len;
8791
8792                 p = message;
8793                 while (*p && *p == ' ') p++;
8794
8795                 message_len = strlen( p );
8796
8797                 /* [AS] Avoid buffer overflow */
8798                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8799                     strcat(thinkOutput, " ");
8800                     strcat(thinkOutput, p);
8801                 }
8802
8803                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8804                     strcat(programStats.movelist, " ");
8805                     strcat(programStats.movelist, p);
8806                 }
8807
8808                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8809                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8810                     DisplayMove(currentMove - 1);
8811                 }
8812                 return;
8813             }
8814         }
8815         else {
8816             buf1[0] = NULLCHAR;
8817
8818             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8819                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8820             {
8821                 ChessProgramStats cpstats;
8822
8823                 if (plyext != ' ' && plyext != '\t') {
8824                     time *= 100;
8825                 }
8826
8827                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8828                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8829                     curscore = -curscore;
8830                 }
8831
8832                 cpstats.depth = plylev;
8833                 cpstats.nodes = nodes;
8834                 cpstats.time = time;
8835                 cpstats.score = curscore;
8836                 cpstats.got_only_move = 0;
8837                 cpstats.movelist[0] = '\0';
8838
8839                 if (buf1[0] != NULLCHAR) {
8840                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8841                 }
8842
8843                 cpstats.ok_to_send = 0;
8844                 cpstats.line_is_book = 0;
8845                 cpstats.nr_moves = 0;
8846                 cpstats.moves_left = 0;
8847
8848                 SendProgramStatsToFrontend( cps, &cpstats );
8849             }
8850         }
8851     }
8852 }
8853
8854
8855 /* Parse a game score from the character string "game", and
8856    record it as the history of the current game.  The game
8857    score is NOT assumed to start from the standard position.
8858    The display is not updated in any way.
8859    */
8860 void
8861 ParseGameHistory(game)
8862      char *game;
8863 {
8864     ChessMove moveType;
8865     int fromX, fromY, toX, toY, boardIndex;
8866     char promoChar;
8867     char *p, *q;
8868     char buf[MSG_SIZ];
8869
8870     if (appData.debugMode)
8871       fprintf(debugFP, "Parsing game history: %s\n", game);
8872
8873     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8874     gameInfo.site = StrSave(appData.icsHost);
8875     gameInfo.date = PGNDate();
8876     gameInfo.round = StrSave("-");
8877
8878     /* Parse out names of players */
8879     while (*game == ' ') game++;
8880     p = buf;
8881     while (*game != ' ') *p++ = *game++;
8882     *p = NULLCHAR;
8883     gameInfo.white = StrSave(buf);
8884     while (*game == ' ') game++;
8885     p = buf;
8886     while (*game != ' ' && *game != '\n') *p++ = *game++;
8887     *p = NULLCHAR;
8888     gameInfo.black = StrSave(buf);
8889
8890     /* Parse moves */
8891     boardIndex = blackPlaysFirst ? 1 : 0;
8892     yynewstr(game);
8893     for (;;) {
8894         yyboardindex = boardIndex;
8895         moveType = (ChessMove) Myylex();
8896         switch (moveType) {
8897           case IllegalMove:             /* maybe suicide chess, etc. */
8898   if (appData.debugMode) {
8899     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8900     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8901     setbuf(debugFP, NULL);
8902   }
8903           case WhitePromotion:
8904           case BlackPromotion:
8905           case WhiteNonPromotion:
8906           case BlackNonPromotion:
8907           case NormalMove:
8908           case WhiteCapturesEnPassant:
8909           case BlackCapturesEnPassant:
8910           case WhiteKingSideCastle:
8911           case WhiteQueenSideCastle:
8912           case BlackKingSideCastle:
8913           case BlackQueenSideCastle:
8914           case WhiteKingSideCastleWild:
8915           case WhiteQueenSideCastleWild:
8916           case BlackKingSideCastleWild:
8917           case BlackQueenSideCastleWild:
8918           /* PUSH Fabien */
8919           case WhiteHSideCastleFR:
8920           case WhiteASideCastleFR:
8921           case BlackHSideCastleFR:
8922           case BlackASideCastleFR:
8923           /* POP Fabien */
8924             fromX = currentMoveString[0] - AAA;
8925             fromY = currentMoveString[1] - ONE;
8926             toX = currentMoveString[2] - AAA;
8927             toY = currentMoveString[3] - ONE;
8928             promoChar = currentMoveString[4];
8929             break;
8930           case WhiteDrop:
8931           case BlackDrop:
8932             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8933             fromX = moveType == WhiteDrop ?
8934               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8935             (int) CharToPiece(ToLower(currentMoveString[0]));
8936             fromY = DROP_RANK;
8937             toX = currentMoveString[2] - AAA;
8938             toY = currentMoveString[3] - ONE;
8939             promoChar = NULLCHAR;
8940             break;
8941           case AmbiguousMove:
8942             /* bug? */
8943             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8944   if (appData.debugMode) {
8945     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8946     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8947     setbuf(debugFP, NULL);
8948   }
8949             DisplayError(buf, 0);
8950             return;
8951           case ImpossibleMove:
8952             /* bug? */
8953             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8954   if (appData.debugMode) {
8955     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8956     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8957     setbuf(debugFP, NULL);
8958   }
8959             DisplayError(buf, 0);
8960             return;
8961           case EndOfFile:
8962             if (boardIndex < backwardMostMove) {
8963                 /* Oops, gap.  How did that happen? */
8964                 DisplayError(_("Gap in move list"), 0);
8965                 return;
8966             }
8967             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8968             if (boardIndex > forwardMostMove) {
8969                 forwardMostMove = boardIndex;
8970             }
8971             return;
8972           case ElapsedTime:
8973             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8974                 strcat(parseList[boardIndex-1], " ");
8975                 strcat(parseList[boardIndex-1], yy_text);
8976             }
8977             continue;
8978           case Comment:
8979           case PGNTag:
8980           case NAG:
8981           default:
8982             /* ignore */
8983             continue;
8984           case WhiteWins:
8985           case BlackWins:
8986           case GameIsDrawn:
8987           case GameUnfinished:
8988             if (gameMode == IcsExamining) {
8989                 if (boardIndex < backwardMostMove) {
8990                     /* Oops, gap.  How did that happen? */
8991                     return;
8992                 }
8993                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8994                 return;
8995             }
8996             gameInfo.result = moveType;
8997             p = strchr(yy_text, '{');
8998             if (p == NULL) p = strchr(yy_text, '(');
8999             if (p == NULL) {
9000                 p = yy_text;
9001                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9002             } else {
9003                 q = strchr(p, *p == '{' ? '}' : ')');
9004                 if (q != NULL) *q = NULLCHAR;
9005                 p++;
9006             }
9007             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9008             gameInfo.resultDetails = StrSave(p);
9009             continue;
9010         }
9011         if (boardIndex >= forwardMostMove &&
9012             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9013             backwardMostMove = blackPlaysFirst ? 1 : 0;
9014             return;
9015         }
9016         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9017                                  fromY, fromX, toY, toX, promoChar,
9018                                  parseList[boardIndex]);
9019         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9020         /* currentMoveString is set as a side-effect of yylex */
9021         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9022         strcat(moveList[boardIndex], "\n");
9023         boardIndex++;
9024         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9025         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9026           case MT_NONE:
9027           case MT_STALEMATE:
9028           default:
9029             break;
9030           case MT_CHECK:
9031             if(gameInfo.variant != VariantShogi)
9032                 strcat(parseList[boardIndex - 1], "+");
9033             break;
9034           case MT_CHECKMATE:
9035           case MT_STAINMATE:
9036             strcat(parseList[boardIndex - 1], "#");
9037             break;
9038         }
9039     }
9040 }
9041
9042
9043 /* Apply a move to the given board  */
9044 void
9045 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9046      int fromX, fromY, toX, toY;
9047      int promoChar;
9048      Board board;
9049 {
9050   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9051   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9052
9053     /* [HGM] compute & store e.p. status and castling rights for new position */
9054     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9055
9056       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9057       oldEP = (signed char)board[EP_STATUS];
9058       board[EP_STATUS] = EP_NONE;
9059
9060   if (fromY == DROP_RANK) {
9061         /* must be first */
9062         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9063             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9064             return;
9065         }
9066         piece = board[toY][toX] = (ChessSquare) fromX;
9067   } else {
9068       int i;
9069
9070       if( board[toY][toX] != EmptySquare )
9071            board[EP_STATUS] = EP_CAPTURE;
9072
9073       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9074            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9075                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9076       } else
9077       if( board[fromY][fromX] == WhitePawn ) {
9078            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9079                board[EP_STATUS] = EP_PAWN_MOVE;
9080            if( toY-fromY==2) {
9081                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9082                         gameInfo.variant != VariantBerolina || toX < fromX)
9083                       board[EP_STATUS] = toX | berolina;
9084                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9085                         gameInfo.variant != VariantBerolina || toX > fromX)
9086                       board[EP_STATUS] = toX;
9087            }
9088       } else
9089       if( board[fromY][fromX] == BlackPawn ) {
9090            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9091                board[EP_STATUS] = EP_PAWN_MOVE;
9092            if( toY-fromY== -2) {
9093                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9094                         gameInfo.variant != VariantBerolina || toX < fromX)
9095                       board[EP_STATUS] = toX | berolina;
9096                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9097                         gameInfo.variant != VariantBerolina || toX > fromX)
9098                       board[EP_STATUS] = toX;
9099            }
9100        }
9101
9102        for(i=0; i<nrCastlingRights; i++) {
9103            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9104               board[CASTLING][i] == toX   && castlingRank[i] == toY
9105              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9106        }
9107
9108      if (fromX == toX && fromY == toY) return;
9109
9110      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9111      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9112      if(gameInfo.variant == VariantKnightmate)
9113          king += (int) WhiteUnicorn - (int) WhiteKing;
9114
9115     /* Code added by Tord: */
9116     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9117     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9118         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9119       board[fromY][fromX] = EmptySquare;
9120       board[toY][toX] = EmptySquare;
9121       if((toX > fromX) != (piece == WhiteRook)) {
9122         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9123       } else {
9124         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9125       }
9126     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9127                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9128       board[fromY][fromX] = EmptySquare;
9129       board[toY][toX] = EmptySquare;
9130       if((toX > fromX) != (piece == BlackRook)) {
9131         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9132       } else {
9133         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9134       }
9135     /* End of code added by Tord */
9136
9137     } else if (board[fromY][fromX] == king
9138         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9139         && toY == fromY && toX > fromX+1) {
9140         board[fromY][fromX] = EmptySquare;
9141         board[toY][toX] = king;
9142         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9143         board[fromY][BOARD_RGHT-1] = EmptySquare;
9144     } else if (board[fromY][fromX] == king
9145         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9146                && toY == fromY && toX < fromX-1) {
9147         board[fromY][fromX] = EmptySquare;
9148         board[toY][toX] = king;
9149         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9150         board[fromY][BOARD_LEFT] = EmptySquare;
9151     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9152                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9153                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9154                ) {
9155         /* white pawn promotion */
9156         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9157         if(gameInfo.variant==VariantBughouse ||
9158            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9159             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9160         board[fromY][fromX] = EmptySquare;
9161     } else if ((fromY >= BOARD_HEIGHT>>1)
9162                && (toX != fromX)
9163                && gameInfo.variant != VariantXiangqi
9164                && gameInfo.variant != VariantBerolina
9165                && (board[fromY][fromX] == WhitePawn)
9166                && (board[toY][toX] == EmptySquare)) {
9167         board[fromY][fromX] = EmptySquare;
9168         board[toY][toX] = WhitePawn;
9169         captured = board[toY - 1][toX];
9170         board[toY - 1][toX] = EmptySquare;
9171     } else if ((fromY == BOARD_HEIGHT-4)
9172                && (toX == fromX)
9173                && gameInfo.variant == VariantBerolina
9174                && (board[fromY][fromX] == WhitePawn)
9175                && (board[toY][toX] == EmptySquare)) {
9176         board[fromY][fromX] = EmptySquare;
9177         board[toY][toX] = WhitePawn;
9178         if(oldEP & EP_BEROLIN_A) {
9179                 captured = board[fromY][fromX-1];
9180                 board[fromY][fromX-1] = EmptySquare;
9181         }else{  captured = board[fromY][fromX+1];
9182                 board[fromY][fromX+1] = EmptySquare;
9183         }
9184     } else if (board[fromY][fromX] == king
9185         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9186                && toY == fromY && toX > fromX+1) {
9187         board[fromY][fromX] = EmptySquare;
9188         board[toY][toX] = king;
9189         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9190         board[fromY][BOARD_RGHT-1] = EmptySquare;
9191     } else if (board[fromY][fromX] == king
9192         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9193                && toY == fromY && toX < fromX-1) {
9194         board[fromY][fromX] = EmptySquare;
9195         board[toY][toX] = king;
9196         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9197         board[fromY][BOARD_LEFT] = EmptySquare;
9198     } else if (fromY == 7 && fromX == 3
9199                && board[fromY][fromX] == BlackKing
9200                && toY == 7 && toX == 5) {
9201         board[fromY][fromX] = EmptySquare;
9202         board[toY][toX] = BlackKing;
9203         board[fromY][7] = EmptySquare;
9204         board[toY][4] = BlackRook;
9205     } else if (fromY == 7 && fromX == 3
9206                && board[fromY][fromX] == BlackKing
9207                && toY == 7 && toX == 1) {
9208         board[fromY][fromX] = EmptySquare;
9209         board[toY][toX] = BlackKing;
9210         board[fromY][0] = EmptySquare;
9211         board[toY][2] = BlackRook;
9212     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9213                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9214                && toY < promoRank && promoChar
9215                ) {
9216         /* black pawn promotion */
9217         board[toY][toX] = CharToPiece(ToLower(promoChar));
9218         if(gameInfo.variant==VariantBughouse ||
9219            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9220             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9221         board[fromY][fromX] = EmptySquare;
9222     } else if ((fromY < BOARD_HEIGHT>>1)
9223                && (toX != fromX)
9224                && gameInfo.variant != VariantXiangqi
9225                && gameInfo.variant != VariantBerolina
9226                && (board[fromY][fromX] == BlackPawn)
9227                && (board[toY][toX] == EmptySquare)) {
9228         board[fromY][fromX] = EmptySquare;
9229         board[toY][toX] = BlackPawn;
9230         captured = board[toY + 1][toX];
9231         board[toY + 1][toX] = EmptySquare;
9232     } else if ((fromY == 3)
9233                && (toX == fromX)
9234                && gameInfo.variant == VariantBerolina
9235                && (board[fromY][fromX] == BlackPawn)
9236                && (board[toY][toX] == EmptySquare)) {
9237         board[fromY][fromX] = EmptySquare;
9238         board[toY][toX] = BlackPawn;
9239         if(oldEP & EP_BEROLIN_A) {
9240                 captured = board[fromY][fromX-1];
9241                 board[fromY][fromX-1] = EmptySquare;
9242         }else{  captured = board[fromY][fromX+1];
9243                 board[fromY][fromX+1] = EmptySquare;
9244         }
9245     } else {
9246         board[toY][toX] = board[fromY][fromX];
9247         board[fromY][fromX] = EmptySquare;
9248     }
9249   }
9250
9251     if (gameInfo.holdingsWidth != 0) {
9252
9253       /* !!A lot more code needs to be written to support holdings  */
9254       /* [HGM] OK, so I have written it. Holdings are stored in the */
9255       /* penultimate board files, so they are automaticlly stored   */
9256       /* in the game history.                                       */
9257       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9258                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9259         /* Delete from holdings, by decreasing count */
9260         /* and erasing image if necessary            */
9261         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9262         if(p < (int) BlackPawn) { /* white drop */
9263              p -= (int)WhitePawn;
9264                  p = PieceToNumber((ChessSquare)p);
9265              if(p >= gameInfo.holdingsSize) p = 0;
9266              if(--board[p][BOARD_WIDTH-2] <= 0)
9267                   board[p][BOARD_WIDTH-1] = EmptySquare;
9268              if((int)board[p][BOARD_WIDTH-2] < 0)
9269                         board[p][BOARD_WIDTH-2] = 0;
9270         } else {                  /* black drop */
9271              p -= (int)BlackPawn;
9272                  p = PieceToNumber((ChessSquare)p);
9273              if(p >= gameInfo.holdingsSize) p = 0;
9274              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9275                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9276              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9277                         board[BOARD_HEIGHT-1-p][1] = 0;
9278         }
9279       }
9280       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9281           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9282         /* [HGM] holdings: Add to holdings, if holdings exist */
9283         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9284                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9285                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9286         }
9287         p = (int) captured;
9288         if (p >= (int) BlackPawn) {
9289           p -= (int)BlackPawn;
9290           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9291                   /* in Shogi restore piece to its original  first */
9292                   captured = (ChessSquare) (DEMOTED captured);
9293                   p = DEMOTED p;
9294           }
9295           p = PieceToNumber((ChessSquare)p);
9296           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9297           board[p][BOARD_WIDTH-2]++;
9298           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9299         } else {
9300           p -= (int)WhitePawn;
9301           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9302                   captured = (ChessSquare) (DEMOTED captured);
9303                   p = DEMOTED p;
9304           }
9305           p = PieceToNumber((ChessSquare)p);
9306           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9307           board[BOARD_HEIGHT-1-p][1]++;
9308           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9309         }
9310       }
9311     } else if (gameInfo.variant == VariantAtomic) {
9312       if (captured != EmptySquare) {
9313         int y, x;
9314         for (y = toY-1; y <= toY+1; y++) {
9315           for (x = toX-1; x <= toX+1; x++) {
9316             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9317                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9318               board[y][x] = EmptySquare;
9319             }
9320           }
9321         }
9322         board[toY][toX] = EmptySquare;
9323       }
9324     }
9325     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9326         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9327     } else
9328     if(promoChar == '+') {
9329         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9330         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9331     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9332         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9333     }
9334     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9335                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9336         // [HGM] superchess: take promotion piece out of holdings
9337         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9338         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9339             if(!--board[k][BOARD_WIDTH-2])
9340                 board[k][BOARD_WIDTH-1] = EmptySquare;
9341         } else {
9342             if(!--board[BOARD_HEIGHT-1-k][1])
9343                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9344         }
9345     }
9346
9347 }
9348
9349 /* Updates forwardMostMove */
9350 void
9351 MakeMove(fromX, fromY, toX, toY, promoChar)
9352      int fromX, fromY, toX, toY;
9353      int promoChar;
9354 {
9355 //    forwardMostMove++; // [HGM] bare: moved downstream
9356
9357     (void) CoordsToAlgebraic(boards[forwardMostMove],
9358                              PosFlags(forwardMostMove),
9359                              fromY, fromX, toY, toX, promoChar,
9360                              parseList[forwardMostMove]);
9361
9362     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9363         int timeLeft; static int lastLoadFlag=0; int king, piece;
9364         piece = boards[forwardMostMove][fromY][fromX];
9365         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9366         if(gameInfo.variant == VariantKnightmate)
9367             king += (int) WhiteUnicorn - (int) WhiteKing;
9368         if(forwardMostMove == 0) {
9369             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9370                 fprintf(serverMoves, "%s;", UserName());
9371             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9372                 fprintf(serverMoves, "%s;", second.tidy);
9373             fprintf(serverMoves, "%s;", first.tidy);
9374             if(gameMode == MachinePlaysWhite)
9375                 fprintf(serverMoves, "%s;", UserName());
9376             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9377                 fprintf(serverMoves, "%s;", second.tidy);
9378         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9379         lastLoadFlag = loadFlag;
9380         // print base move
9381         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9382         // print castling suffix
9383         if( toY == fromY && piece == king ) {
9384             if(toX-fromX > 1)
9385                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9386             if(fromX-toX >1)
9387                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9388         }
9389         // e.p. suffix
9390         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9391              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9392              boards[forwardMostMove][toY][toX] == EmptySquare
9393              && fromX != toX && fromY != toY)
9394                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9395         // promotion suffix
9396         if(promoChar != NULLCHAR)
9397                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9398         if(!loadFlag) {
9399                 char buf[MOVE_LEN*2], *p; int len;
9400             fprintf(serverMoves, "/%d/%d",
9401                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9402             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9403             else                      timeLeft = blackTimeRemaining/1000;
9404             fprintf(serverMoves, "/%d", timeLeft);
9405                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9406                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9407                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9408             fprintf(serverMoves, "/%s", buf);
9409         }
9410         fflush(serverMoves);
9411     }
9412
9413     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9414       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9415                         0, 1);
9416       return;
9417     }
9418     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9419     if (commentList[forwardMostMove+1] != NULL) {
9420         free(commentList[forwardMostMove+1]);
9421         commentList[forwardMostMove+1] = NULL;
9422     }
9423     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9424     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9425     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9426     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9427     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9428     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9429     gameInfo.result = GameUnfinished;
9430     if (gameInfo.resultDetails != NULL) {
9431         free(gameInfo.resultDetails);
9432         gameInfo.resultDetails = NULL;
9433     }
9434     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9435                               moveList[forwardMostMove - 1]);
9436     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9437       case MT_NONE:
9438       case MT_STALEMATE:
9439       default:
9440         break;
9441       case MT_CHECK:
9442         if(gameInfo.variant != VariantShogi)
9443             strcat(parseList[forwardMostMove - 1], "+");
9444         break;
9445       case MT_CHECKMATE:
9446       case MT_STAINMATE:
9447         strcat(parseList[forwardMostMove - 1], "#");
9448         break;
9449     }
9450     if (appData.debugMode) {
9451         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9452     }
9453
9454 }
9455
9456 /* Updates currentMove if not pausing */
9457 void
9458 ShowMove(fromX, fromY, toX, toY)
9459 {
9460     int instant = (gameMode == PlayFromGameFile) ?
9461         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9462     if(appData.noGUI) return;
9463     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9464         if (!instant) {
9465             if (forwardMostMove == currentMove + 1) {
9466                 AnimateMove(boards[forwardMostMove - 1],
9467                             fromX, fromY, toX, toY);
9468             }
9469             if (appData.highlightLastMove) {
9470                 SetHighlights(fromX, fromY, toX, toY);
9471             }
9472         }
9473         currentMove = forwardMostMove;
9474     }
9475
9476     if (instant) return;
9477
9478     DisplayMove(currentMove - 1);
9479     DrawPosition(FALSE, boards[currentMove]);
9480     DisplayBothClocks();
9481     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9482     DisplayBook(currentMove);
9483 }
9484
9485 void SendEgtPath(ChessProgramState *cps)
9486 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9487         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9488
9489         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9490
9491         while(*p) {
9492             char c, *q = name+1, *r, *s;
9493
9494             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9495             while(*p && *p != ',') *q++ = *p++;
9496             *q++ = ':'; *q = 0;
9497             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9498                 strcmp(name, ",nalimov:") == 0 ) {
9499                 // take nalimov path from the menu-changeable option first, if it is defined
9500               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9501                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9502             } else
9503             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9504                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9505                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9506                 s = r = StrStr(s, ":") + 1; // beginning of path info
9507                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9508                 c = *r; *r = 0;             // temporarily null-terminate path info
9509                     *--q = 0;               // strip of trailig ':' from name
9510                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9511                 *r = c;
9512                 SendToProgram(buf,cps);     // send egtbpath command for this format
9513             }
9514             if(*p == ',') p++; // read away comma to position for next format name
9515         }
9516 }
9517
9518 void
9519 InitChessProgram(cps, setup)
9520      ChessProgramState *cps;
9521      int setup; /* [HGM] needed to setup FRC opening position */
9522 {
9523     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9524     if (appData.noChessProgram) return;
9525     hintRequested = FALSE;
9526     bookRequested = FALSE;
9527
9528     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9529     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9530     if(cps->memSize) { /* [HGM] memory */
9531       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9532         SendToProgram(buf, cps);
9533     }
9534     SendEgtPath(cps); /* [HGM] EGT */
9535     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9536       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9537         SendToProgram(buf, cps);
9538     }
9539
9540     SendToProgram(cps->initString, cps);
9541     if (gameInfo.variant != VariantNormal &&
9542         gameInfo.variant != VariantLoadable
9543         /* [HGM] also send variant if board size non-standard */
9544         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9545                                             ) {
9546       char *v = VariantName(gameInfo.variant);
9547       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9548         /* [HGM] in protocol 1 we have to assume all variants valid */
9549         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9550         DisplayFatalError(buf, 0, 1);
9551         return;
9552       }
9553
9554       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9555       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9556       if( gameInfo.variant == VariantXiangqi )
9557            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9558       if( gameInfo.variant == VariantShogi )
9559            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9560       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9561            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9562       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9563           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9564            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9565       if( gameInfo.variant == VariantCourier )
9566            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9567       if( gameInfo.variant == VariantSuper )
9568            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9569       if( gameInfo.variant == VariantGreat )
9570            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9571       if( gameInfo.variant == VariantSChess )
9572            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9573       if( gameInfo.variant == VariantGrand )
9574            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9575
9576       if(overruled) {
9577         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9578                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9579            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9580            if(StrStr(cps->variants, b) == NULL) {
9581                // specific sized variant not known, check if general sizing allowed
9582                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9583                    if(StrStr(cps->variants, "boardsize") == NULL) {
9584                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9585                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9586                        DisplayFatalError(buf, 0, 1);
9587                        return;
9588                    }
9589                    /* [HGM] here we really should compare with the maximum supported board size */
9590                }
9591            }
9592       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9593       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9594       SendToProgram(buf, cps);
9595     }
9596     currentlyInitializedVariant = gameInfo.variant;
9597
9598     /* [HGM] send opening position in FRC to first engine */
9599     if(setup) {
9600           SendToProgram("force\n", cps);
9601           SendBoard(cps, 0);
9602           /* engine is now in force mode! Set flag to wake it up after first move. */
9603           setboardSpoiledMachineBlack = 1;
9604     }
9605
9606     if (cps->sendICS) {
9607       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9608       SendToProgram(buf, cps);
9609     }
9610     cps->maybeThinking = FALSE;
9611     cps->offeredDraw = 0;
9612     if (!appData.icsActive) {
9613         SendTimeControl(cps, movesPerSession, timeControl,
9614                         timeIncrement, appData.searchDepth,
9615                         searchTime);
9616     }
9617     if (appData.showThinking
9618         // [HGM] thinking: four options require thinking output to be sent
9619         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9620                                 ) {
9621         SendToProgram("post\n", cps);
9622     }
9623     SendToProgram("hard\n", cps);
9624     if (!appData.ponderNextMove) {
9625         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9626            it without being sure what state we are in first.  "hard"
9627            is not a toggle, so that one is OK.
9628          */
9629         SendToProgram("easy\n", cps);
9630     }
9631     if (cps->usePing) {
9632       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9633       SendToProgram(buf, cps);
9634     }
9635     cps->initDone = TRUE;
9636     ClearEngineOutputPane(cps == &second);
9637 }
9638
9639
9640 void
9641 StartChessProgram(cps)
9642      ChessProgramState *cps;
9643 {
9644     char buf[MSG_SIZ];
9645     int err;
9646
9647     if (appData.noChessProgram) return;
9648     cps->initDone = FALSE;
9649
9650     if (strcmp(cps->host, "localhost") == 0) {
9651         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9652     } else if (*appData.remoteShell == NULLCHAR) {
9653         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9654     } else {
9655         if (*appData.remoteUser == NULLCHAR) {
9656           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9657                     cps->program);
9658         } else {
9659           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9660                     cps->host, appData.remoteUser, cps->program);
9661         }
9662         err = StartChildProcess(buf, "", &cps->pr);
9663     }
9664
9665     if (err != 0) {
9666       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9667         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9668         if(cps != &first) return;
9669         appData.noChessProgram = TRUE;
9670         ThawUI();
9671         SetNCPMode();
9672 //      DisplayFatalError(buf, err, 1);
9673 //      cps->pr = NoProc;
9674 //      cps->isr = NULL;
9675         return;
9676     }
9677
9678     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9679     if (cps->protocolVersion > 1) {
9680       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9681       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9682       cps->comboCnt = 0;  //                and values of combo boxes
9683       SendToProgram(buf, cps);
9684     } else {
9685       SendToProgram("xboard\n", cps);
9686     }
9687 }
9688
9689 void
9690 TwoMachinesEventIfReady P((void))
9691 {
9692   static int curMess = 0;
9693   if (first.lastPing != first.lastPong) {
9694     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9695     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9696     return;
9697   }
9698   if (second.lastPing != second.lastPong) {
9699     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9700     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9701     return;
9702   }
9703   DisplayMessage("", ""); curMess = 0;
9704   ThawUI();
9705   TwoMachinesEvent();
9706 }
9707
9708 char *
9709 MakeName(char *template)
9710 {
9711     time_t clock;
9712     struct tm *tm;
9713     static char buf[MSG_SIZ];
9714     char *p = buf;
9715     int i;
9716
9717     clock = time((time_t *)NULL);
9718     tm = localtime(&clock);
9719
9720     while(*p++ = *template++) if(p[-1] == '%') {
9721         switch(*template++) {
9722           case 0:   *p = 0; return buf;
9723           case 'Y': i = tm->tm_year+1900; break;
9724           case 'y': i = tm->tm_year-100; break;
9725           case 'M': i = tm->tm_mon+1; break;
9726           case 'd': i = tm->tm_mday; break;
9727           case 'h': i = tm->tm_hour; break;
9728           case 'm': i = tm->tm_min; break;
9729           case 's': i = tm->tm_sec; break;
9730           default:  i = 0;
9731         }
9732         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9733     }
9734     return buf;
9735 }
9736
9737 int
9738 CountPlayers(char *p)
9739 {
9740     int n = 0;
9741     while(p = strchr(p, '\n')) p++, n++; // count participants
9742     return n;
9743 }
9744
9745 FILE *
9746 WriteTourneyFile(char *results, FILE *f)
9747 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9748     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9749     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9750         // create a file with tournament description
9751         fprintf(f, "-participants {%s}\n", appData.participants);
9752         fprintf(f, "-seedBase %d\n", appData.seedBase);
9753         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9754         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9755         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9756         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9757         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9758         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9759         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9760         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9761         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9762         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9763         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9764         if(searchTime > 0)
9765                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9766         else {
9767                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9768                 fprintf(f, "-tc %s\n", appData.timeControl);
9769                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9770         }
9771         fprintf(f, "-results \"%s\"\n", results);
9772     }
9773     return f;
9774 }
9775
9776 #define MAXENGINES 1000
9777 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9778
9779 void Substitute(char *participants, int expunge)
9780 {
9781     int i, changed, changes=0, nPlayers=0;
9782     char *p, *q, *r, buf[MSG_SIZ];
9783     if(participants == NULL) return;
9784     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9785     r = p = participants; q = appData.participants;
9786     while(*p && *p == *q) {
9787         if(*p == '\n') r = p+1, nPlayers++;
9788         p++; q++;
9789     }
9790     if(*p) { // difference
9791         while(*p && *p++ != '\n');
9792         while(*q && *q++ != '\n');
9793       changed = nPlayers;
9794         changes = 1 + (strcmp(p, q) != 0);
9795     }
9796     if(changes == 1) { // a single engine mnemonic was changed
9797         q = r; while(*q) nPlayers += (*q++ == '\n');
9798         p = buf; while(*r && (*p = *r++) != '\n') p++;
9799         *p = NULLCHAR;
9800         NamesToList(firstChessProgramNames, command, mnemonic);
9801         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9802         if(mnemonic[i]) { // The substitute is valid
9803             FILE *f;
9804             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9805                 flock(fileno(f), LOCK_EX);
9806                 ParseArgsFromFile(f);
9807                 fseek(f, 0, SEEK_SET);
9808                 FREE(appData.participants); appData.participants = participants;
9809                 if(expunge) { // erase results of replaced engine
9810                     int len = strlen(appData.results), w, b, dummy;
9811                     for(i=0; i<len; i++) {
9812                         Pairing(i, nPlayers, &w, &b, &dummy);
9813                         if((w == changed || b == changed) && appData.results[i] == '*') {
9814                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9815                             fclose(f);
9816                             return;
9817                         }
9818                     }
9819                     for(i=0; i<len; i++) {
9820                         Pairing(i, nPlayers, &w, &b, &dummy);
9821                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9822                     }
9823                 }
9824                 WriteTourneyFile(appData.results, f);
9825                 fclose(f); // release lock
9826                 return;
9827             }
9828         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9829     }
9830     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9831     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9832     free(participants);
9833     return;
9834 }
9835
9836 int
9837 CreateTourney(char *name)
9838 {
9839         FILE *f;
9840         if(matchMode && strcmp(name, appData.tourneyFile)) {
9841              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9842         }
9843         if(name[0] == NULLCHAR) {
9844             if(appData.participants[0])
9845                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9846             return 0;
9847         }
9848         f = fopen(name, "r");
9849         if(f) { // file exists
9850             ASSIGN(appData.tourneyFile, name);
9851             ParseArgsFromFile(f); // parse it
9852         } else {
9853             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9854             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9855                 DisplayError(_("Not enough participants"), 0);
9856                 return 0;
9857             }
9858             ASSIGN(appData.tourneyFile, name);
9859             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9860             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9861         }
9862         fclose(f);
9863         appData.noChessProgram = FALSE;
9864         appData.clockMode = TRUE;
9865         SetGNUMode();
9866         return 1;
9867 }
9868
9869 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9870 {
9871     char buf[MSG_SIZ], *p, *q;
9872     int i=1;
9873     while(*names) {
9874         p = names; q = buf;
9875         while(*p && *p != '\n') *q++ = *p++;
9876         *q = 0;
9877         if(engineList[i]) free(engineList[i]);
9878         engineList[i] = strdup(buf);
9879         if(*p == '\n') p++;
9880         TidyProgramName(engineList[i], "localhost", buf);
9881         if(engineMnemonic[i]) free(engineMnemonic[i]);
9882         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9883             strcat(buf, " (");
9884             sscanf(q + 8, "%s", buf + strlen(buf));
9885             strcat(buf, ")");
9886         }
9887         engineMnemonic[i] = strdup(buf);
9888         names = p; i++;
9889       if(i > MAXENGINES - 2) break;
9890     }
9891     engineList[i] = engineMnemonic[i] = NULL;
9892 }
9893
9894 // following implemented as macro to avoid type limitations
9895 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9896
9897 void SwapEngines(int n)
9898 {   // swap settings for first engine and other engine (so far only some selected options)
9899     int h;
9900     char *p;
9901     if(n == 0) return;
9902     SWAP(directory, p)
9903     SWAP(chessProgram, p)
9904     SWAP(isUCI, h)
9905     SWAP(hasOwnBookUCI, h)
9906     SWAP(protocolVersion, h)
9907     SWAP(reuse, h)
9908     SWAP(scoreIsAbsolute, h)
9909     SWAP(timeOdds, h)
9910     SWAP(logo, p)
9911     SWAP(pgnName, p)
9912     SWAP(pvSAN, h)
9913 }
9914
9915 void
9916 SetPlayer(int player)
9917 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9918     int i;
9919     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9920     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9921     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9922     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9923     if(mnemonic[i]) {
9924         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9925         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9926         ParseArgsFromString(buf);
9927     }
9928     free(engineName);
9929 }
9930
9931 int
9932 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9933 {   // determine players from game number
9934     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9935
9936     if(appData.tourneyType == 0) {
9937         roundsPerCycle = (nPlayers - 1) | 1;
9938         pairingsPerRound = nPlayers / 2;
9939     } else if(appData.tourneyType > 0) {
9940         roundsPerCycle = nPlayers - appData.tourneyType;
9941         pairingsPerRound = appData.tourneyType;
9942     }
9943     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9944     gamesPerCycle = gamesPerRound * roundsPerCycle;
9945     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9946     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9947     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9948     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9949     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9950     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9951
9952     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9953     if(appData.roundSync) *syncInterval = gamesPerRound;
9954
9955     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9956
9957     if(appData.tourneyType == 0) {
9958         if(curPairing == (nPlayers-1)/2 ) {
9959             *whitePlayer = curRound;
9960             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9961         } else {
9962             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9963             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9964             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9965             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9966         }
9967     } else if(appData.tourneyType > 0) {
9968         *whitePlayer = curPairing;
9969         *blackPlayer = curRound + appData.tourneyType;
9970     }
9971
9972     // take care of white/black alternation per round. 
9973     // For cycles and games this is already taken care of by default, derived from matchGame!
9974     return curRound & 1;
9975 }
9976
9977 int
9978 NextTourneyGame(int nr, int *swapColors)
9979 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9980     char *p, *q;
9981     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9982     FILE *tf;
9983     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9984     tf = fopen(appData.tourneyFile, "r");
9985     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9986     ParseArgsFromFile(tf); fclose(tf);
9987     InitTimeControls(); // TC might be altered from tourney file
9988
9989     nPlayers = CountPlayers(appData.participants); // count participants
9990     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9991     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9992
9993     if(syncInterval) {
9994         p = q = appData.results;
9995         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9996         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9997             DisplayMessage(_("Waiting for other game(s)"),"");
9998             waitingForGame = TRUE;
9999             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10000             return 0;
10001         }
10002         waitingForGame = FALSE;
10003     }
10004
10005     if(appData.tourneyType < 0) {
10006         if(nr>=0 && !pairingReceived) {
10007             char buf[1<<16];
10008             if(pairing.pr == NoProc) {
10009                 if(!appData.pairingEngine[0]) {
10010                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10011                     return 0;
10012                 }
10013                 StartChessProgram(&pairing); // starts the pairing engine
10014             }
10015             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10016             SendToProgram(buf, &pairing);
10017             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10018             SendToProgram(buf, &pairing);
10019             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10020         }
10021         pairingReceived = 0;                              // ... so we continue here 
10022         *swapColors = 0;
10023         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10024         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10025         matchGame = 1; roundNr = nr / syncInterval + 1;
10026     }
10027
10028     if(first.pr != NoProc) return 1; // engines already loaded
10029
10030     // redefine engines, engine dir, etc.
10031     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10032     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10033     SwapEngines(1);
10034     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10035     SwapEngines(1);         // and make that valid for second engine by swapping
10036     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10037     InitEngine(&second, 1);
10038     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10039     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10040     return 1;
10041 }
10042
10043 void
10044 NextMatchGame()
10045 {   // performs game initialization that does not invoke engines, and then tries to start the game
10046     int firstWhite, swapColors = 0;
10047     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10048     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10049     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10050     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10051     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10052     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10053     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10054     Reset(FALSE, first.pr != NoProc);
10055     appData.noChessProgram = FALSE;
10056     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
10057     TwoMachinesEvent();
10058 }
10059
10060 void UserAdjudicationEvent( int result )
10061 {
10062     ChessMove gameResult = GameIsDrawn;
10063
10064     if( result > 0 ) {
10065         gameResult = WhiteWins;
10066     }
10067     else if( result < 0 ) {
10068         gameResult = BlackWins;
10069     }
10070
10071     if( gameMode == TwoMachinesPlay ) {
10072         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10073     }
10074 }
10075
10076
10077 // [HGM] save: calculate checksum of game to make games easily identifiable
10078 int StringCheckSum(char *s)
10079 {
10080         int i = 0;
10081         if(s==NULL) return 0;
10082         while(*s) i = i*259 + *s++;
10083         return i;
10084 }
10085
10086 int GameCheckSum()
10087 {
10088         int i, sum=0;
10089         for(i=backwardMostMove; i<forwardMostMove; i++) {
10090                 sum += pvInfoList[i].depth;
10091                 sum += StringCheckSum(parseList[i]);
10092                 sum += StringCheckSum(commentList[i]);
10093                 sum *= 261;
10094         }
10095         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10096         return sum + StringCheckSum(commentList[i]);
10097 } // end of save patch
10098
10099 void
10100 GameEnds(result, resultDetails, whosays)
10101      ChessMove result;
10102      char *resultDetails;
10103      int whosays;
10104 {
10105     GameMode nextGameMode;
10106     int isIcsGame;
10107     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10108
10109     if(endingGame) return; /* [HGM] crash: forbid recursion */
10110     endingGame = 1;
10111     if(twoBoards) { // [HGM] dual: switch back to one board
10112         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10113         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10114     }
10115     if (appData.debugMode) {
10116       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10117               result, resultDetails ? resultDetails : "(null)", whosays);
10118     }
10119
10120     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10121
10122     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10123         /* If we are playing on ICS, the server decides when the
10124            game is over, but the engine can offer to draw, claim
10125            a draw, or resign.
10126          */
10127 #if ZIPPY
10128         if (appData.zippyPlay && first.initDone) {
10129             if (result == GameIsDrawn) {
10130                 /* In case draw still needs to be claimed */
10131                 SendToICS(ics_prefix);
10132                 SendToICS("draw\n");
10133             } else if (StrCaseStr(resultDetails, "resign")) {
10134                 SendToICS(ics_prefix);
10135                 SendToICS("resign\n");
10136             }
10137         }
10138 #endif
10139         endingGame = 0; /* [HGM] crash */
10140         return;
10141     }
10142
10143     /* If we're loading the game from a file, stop */
10144     if (whosays == GE_FILE) {
10145       (void) StopLoadGameTimer();
10146       gameFileFP = NULL;
10147     }
10148
10149     /* Cancel draw offers */
10150     first.offeredDraw = second.offeredDraw = 0;
10151
10152     /* If this is an ICS game, only ICS can really say it's done;
10153        if not, anyone can. */
10154     isIcsGame = (gameMode == IcsPlayingWhite ||
10155                  gameMode == IcsPlayingBlack ||
10156                  gameMode == IcsObserving    ||
10157                  gameMode == IcsExamining);
10158
10159     if (!isIcsGame || whosays == GE_ICS) {
10160         /* OK -- not an ICS game, or ICS said it was done */
10161         StopClocks();
10162         if (!isIcsGame && !appData.noChessProgram)
10163           SetUserThinkingEnables();
10164
10165         /* [HGM] if a machine claims the game end we verify this claim */
10166         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10167             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10168                 char claimer;
10169                 ChessMove trueResult = (ChessMove) -1;
10170
10171                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10172                                             first.twoMachinesColor[0] :
10173                                             second.twoMachinesColor[0] ;
10174
10175                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10176                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10177                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10178                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10179                 } else
10180                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10181                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10182                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10183                 } else
10184                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10185                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10186                 }
10187
10188                 // now verify win claims, but not in drop games, as we don't understand those yet
10189                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10190                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10191                     (result == WhiteWins && claimer == 'w' ||
10192                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10193                       if (appData.debugMode) {
10194                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10195                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10196                       }
10197                       if(result != trueResult) {
10198                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10199                               result = claimer == 'w' ? BlackWins : WhiteWins;
10200                               resultDetails = buf;
10201                       }
10202                 } else
10203                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10204                     && (forwardMostMove <= backwardMostMove ||
10205                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10206                         (claimer=='b')==(forwardMostMove&1))
10207                                                                                   ) {
10208                       /* [HGM] verify: draws that were not flagged are false claims */
10209                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10210                       result = claimer == 'w' ? BlackWins : WhiteWins;
10211                       resultDetails = buf;
10212                 }
10213                 /* (Claiming a loss is accepted no questions asked!) */
10214             }
10215             /* [HGM] bare: don't allow bare King to win */
10216             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10217                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10218                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10219                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10220                && result != GameIsDrawn)
10221             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10222                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10223                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10224                         if(p >= 0 && p <= (int)WhiteKing) k++;
10225                 }
10226                 if (appData.debugMode) {
10227                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10228                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10229                 }
10230                 if(k <= 1) {
10231                         result = GameIsDrawn;
10232                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10233                         resultDetails = buf;
10234                 }
10235             }
10236         }
10237
10238
10239         if(serverMoves != NULL && !loadFlag) { char c = '=';
10240             if(result==WhiteWins) c = '+';
10241             if(result==BlackWins) c = '-';
10242             if(resultDetails != NULL)
10243                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10244         }
10245         if (resultDetails != NULL) {
10246             gameInfo.result = result;
10247             gameInfo.resultDetails = StrSave(resultDetails);
10248
10249             /* display last move only if game was not loaded from file */
10250             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10251                 DisplayMove(currentMove - 1);
10252
10253             if (forwardMostMove != 0) {
10254                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10255                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10256                                                                 ) {
10257                     if (*appData.saveGameFile != NULLCHAR) {
10258                         SaveGameToFile(appData.saveGameFile, TRUE);
10259                     } else if (appData.autoSaveGames) {
10260                         AutoSaveGame();
10261                     }
10262                     if (*appData.savePositionFile != NULLCHAR) {
10263                         SavePositionToFile(appData.savePositionFile);
10264                     }
10265                 }
10266             }
10267
10268             /* Tell program how game ended in case it is learning */
10269             /* [HGM] Moved this to after saving the PGN, just in case */
10270             /* engine died and we got here through time loss. In that */
10271             /* case we will get a fatal error writing the pipe, which */
10272             /* would otherwise lose us the PGN.                       */
10273             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10274             /* output during GameEnds should never be fatal anymore   */
10275             if (gameMode == MachinePlaysWhite ||
10276                 gameMode == MachinePlaysBlack ||
10277                 gameMode == TwoMachinesPlay ||
10278                 gameMode == IcsPlayingWhite ||
10279                 gameMode == IcsPlayingBlack ||
10280                 gameMode == BeginningOfGame) {
10281                 char buf[MSG_SIZ];
10282                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10283                         resultDetails);
10284                 if (first.pr != NoProc) {
10285                     SendToProgram(buf, &first);
10286                 }
10287                 if (second.pr != NoProc &&
10288                     gameMode == TwoMachinesPlay) {
10289                     SendToProgram(buf, &second);
10290                 }
10291             }
10292         }
10293
10294         if (appData.icsActive) {
10295             if (appData.quietPlay &&
10296                 (gameMode == IcsPlayingWhite ||
10297                  gameMode == IcsPlayingBlack)) {
10298                 SendToICS(ics_prefix);
10299                 SendToICS("set shout 1\n");
10300             }
10301             nextGameMode = IcsIdle;
10302             ics_user_moved = FALSE;
10303             /* clean up premove.  It's ugly when the game has ended and the
10304              * premove highlights are still on the board.
10305              */
10306             if (gotPremove) {
10307               gotPremove = FALSE;
10308               ClearPremoveHighlights();
10309               DrawPosition(FALSE, boards[currentMove]);
10310             }
10311             if (whosays == GE_ICS) {
10312                 switch (result) {
10313                 case WhiteWins:
10314                     if (gameMode == IcsPlayingWhite)
10315                         PlayIcsWinSound();
10316                     else if(gameMode == IcsPlayingBlack)
10317                         PlayIcsLossSound();
10318                     break;
10319                 case BlackWins:
10320                     if (gameMode == IcsPlayingBlack)
10321                         PlayIcsWinSound();
10322                     else if(gameMode == IcsPlayingWhite)
10323                         PlayIcsLossSound();
10324                     break;
10325                 case GameIsDrawn:
10326                     PlayIcsDrawSound();
10327                     break;
10328                 default:
10329                     PlayIcsUnfinishedSound();
10330                 }
10331             }
10332         } else if (gameMode == EditGame ||
10333                    gameMode == PlayFromGameFile ||
10334                    gameMode == AnalyzeMode ||
10335                    gameMode == AnalyzeFile) {
10336             nextGameMode = gameMode;
10337         } else {
10338             nextGameMode = EndOfGame;
10339         }
10340         pausing = FALSE;
10341         ModeHighlight();
10342     } else {
10343         nextGameMode = gameMode;
10344     }
10345
10346     if (appData.noChessProgram) {
10347         gameMode = nextGameMode;
10348         ModeHighlight();
10349         endingGame = 0; /* [HGM] crash */
10350         return;
10351     }
10352
10353     if (first.reuse) {
10354         /* Put first chess program into idle state */
10355         if (first.pr != NoProc &&
10356             (gameMode == MachinePlaysWhite ||
10357              gameMode == MachinePlaysBlack ||
10358              gameMode == TwoMachinesPlay ||
10359              gameMode == IcsPlayingWhite ||
10360              gameMode == IcsPlayingBlack ||
10361              gameMode == BeginningOfGame)) {
10362             SendToProgram("force\n", &first);
10363             if (first.usePing) {
10364               char buf[MSG_SIZ];
10365               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10366               SendToProgram(buf, &first);
10367             }
10368         }
10369     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10370         /* Kill off first chess program */
10371         if (first.isr != NULL)
10372           RemoveInputSource(first.isr);
10373         first.isr = NULL;
10374
10375         if (first.pr != NoProc) {
10376             ExitAnalyzeMode();
10377             DoSleep( appData.delayBeforeQuit );
10378             SendToProgram("quit\n", &first);
10379             DoSleep( appData.delayAfterQuit );
10380             DestroyChildProcess(first.pr, first.useSigterm);
10381         }
10382         first.pr = NoProc;
10383     }
10384     if (second.reuse) {
10385         /* Put second chess program into idle state */
10386         if (second.pr != NoProc &&
10387             gameMode == TwoMachinesPlay) {
10388             SendToProgram("force\n", &second);
10389             if (second.usePing) {
10390               char buf[MSG_SIZ];
10391               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10392               SendToProgram(buf, &second);
10393             }
10394         }
10395     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10396         /* Kill off second chess program */
10397         if (second.isr != NULL)
10398           RemoveInputSource(second.isr);
10399         second.isr = NULL;
10400
10401         if (second.pr != NoProc) {
10402             DoSleep( appData.delayBeforeQuit );
10403             SendToProgram("quit\n", &second);
10404             DoSleep( appData.delayAfterQuit );
10405             DestroyChildProcess(second.pr, second.useSigterm);
10406         }
10407         second.pr = NoProc;
10408     }
10409
10410     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10411         char resChar = '=';
10412         switch (result) {
10413         case WhiteWins:
10414           resChar = '+';
10415           if (first.twoMachinesColor[0] == 'w') {
10416             first.matchWins++;
10417           } else {
10418             second.matchWins++;
10419           }
10420           break;
10421         case BlackWins:
10422           resChar = '-';
10423           if (first.twoMachinesColor[0] == 'b') {
10424             first.matchWins++;
10425           } else {
10426             second.matchWins++;
10427           }
10428           break;
10429         case GameUnfinished:
10430           resChar = ' ';
10431         default:
10432           break;
10433         }
10434
10435         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10436         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10437             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10438             ReserveGame(nextGame, resChar); // sets nextGame
10439             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10440             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10441         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10442
10443         if (nextGame <= appData.matchGames && !abortMatch) {
10444             gameMode = nextGameMode;
10445             matchGame = nextGame; // this will be overruled in tourney mode!
10446             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10447             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10448             endingGame = 0; /* [HGM] crash */
10449             return;
10450         } else {
10451             gameMode = nextGameMode;
10452             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10453                      first.tidy, second.tidy,
10454                      first.matchWins, second.matchWins,
10455                      appData.matchGames - (first.matchWins + second.matchWins));
10456             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10457             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10458             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10459                 first.twoMachinesColor = "black\n";
10460                 second.twoMachinesColor = "white\n";
10461             } else {
10462                 first.twoMachinesColor = "white\n";
10463                 second.twoMachinesColor = "black\n";
10464             }
10465         }
10466     }
10467     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10468         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10469       ExitAnalyzeMode();
10470     gameMode = nextGameMode;
10471     ModeHighlight();
10472     endingGame = 0;  /* [HGM] crash */
10473     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10474         if(matchMode == TRUE) { // match through command line: exit with or without popup
10475             if(ranking) {
10476                 ToNrEvent(forwardMostMove);
10477                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10478                 else ExitEvent(0);
10479             } else DisplayFatalError(buf, 0, 0);
10480         } else { // match through menu; just stop, with or without popup
10481             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10482             ModeHighlight();
10483             if(ranking){
10484                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10485             } else DisplayNote(buf);
10486       }
10487       if(ranking) free(ranking);
10488     }
10489 }
10490
10491 /* Assumes program was just initialized (initString sent).
10492    Leaves program in force mode. */
10493 void
10494 FeedMovesToProgram(cps, upto)
10495      ChessProgramState *cps;
10496      int upto;
10497 {
10498     int i;
10499
10500     if (appData.debugMode)
10501       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10502               startedFromSetupPosition ? "position and " : "",
10503               backwardMostMove, upto, cps->which);
10504     if(currentlyInitializedVariant != gameInfo.variant) {
10505       char buf[MSG_SIZ];
10506         // [HGM] variantswitch: make engine aware of new variant
10507         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10508                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10509         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10510         SendToProgram(buf, cps);
10511         currentlyInitializedVariant = gameInfo.variant;
10512     }
10513     SendToProgram("force\n", cps);
10514     if (startedFromSetupPosition) {
10515         SendBoard(cps, backwardMostMove);
10516     if (appData.debugMode) {
10517         fprintf(debugFP, "feedMoves\n");
10518     }
10519     }
10520     for (i = backwardMostMove; i < upto; i++) {
10521         SendMoveToProgram(i, cps);
10522     }
10523 }
10524
10525
10526 int
10527 ResurrectChessProgram()
10528 {
10529      /* The chess program may have exited.
10530         If so, restart it and feed it all the moves made so far. */
10531     static int doInit = 0;
10532
10533     if (appData.noChessProgram) return 1;
10534
10535     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10536         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10537         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10538         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10539     } else {
10540         if (first.pr != NoProc) return 1;
10541         StartChessProgram(&first);
10542     }
10543     InitChessProgram(&first, FALSE);
10544     FeedMovesToProgram(&first, currentMove);
10545
10546     if (!first.sendTime) {
10547         /* can't tell gnuchess what its clock should read,
10548            so we bow to its notion. */
10549         ResetClocks();
10550         timeRemaining[0][currentMove] = whiteTimeRemaining;
10551         timeRemaining[1][currentMove] = blackTimeRemaining;
10552     }
10553
10554     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10555                 appData.icsEngineAnalyze) && first.analysisSupport) {
10556       SendToProgram("analyze\n", &first);
10557       first.analyzing = TRUE;
10558     }
10559     return 1;
10560 }
10561
10562 /*
10563  * Button procedures
10564  */
10565 void
10566 Reset(redraw, init)
10567      int redraw, init;
10568 {
10569     int i;
10570
10571     if (appData.debugMode) {
10572         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10573                 redraw, init, gameMode);
10574     }
10575     CleanupTail(); // [HGM] vari: delete any stored variations
10576     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10577     pausing = pauseExamInvalid = FALSE;
10578     startedFromSetupPosition = blackPlaysFirst = FALSE;
10579     firstMove = TRUE;
10580     whiteFlag = blackFlag = FALSE;
10581     userOfferedDraw = FALSE;
10582     hintRequested = bookRequested = FALSE;
10583     first.maybeThinking = FALSE;
10584     second.maybeThinking = FALSE;
10585     first.bookSuspend = FALSE; // [HGM] book
10586     second.bookSuspend = FALSE;
10587     thinkOutput[0] = NULLCHAR;
10588     lastHint[0] = NULLCHAR;
10589     ClearGameInfo(&gameInfo);
10590     gameInfo.variant = StringToVariant(appData.variant);
10591     ics_user_moved = ics_clock_paused = FALSE;
10592     ics_getting_history = H_FALSE;
10593     ics_gamenum = -1;
10594     white_holding[0] = black_holding[0] = NULLCHAR;
10595     ClearProgramStats();
10596     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10597
10598     ResetFrontEnd();
10599     ClearHighlights();
10600     flipView = appData.flipView;
10601     ClearPremoveHighlights();
10602     gotPremove = FALSE;
10603     alarmSounded = FALSE;
10604
10605     GameEnds(EndOfFile, NULL, GE_PLAYER);
10606     if(appData.serverMovesName != NULL) {
10607         /* [HGM] prepare to make moves file for broadcasting */
10608         clock_t t = clock();
10609         if(serverMoves != NULL) fclose(serverMoves);
10610         serverMoves = fopen(appData.serverMovesName, "r");
10611         if(serverMoves != NULL) {
10612             fclose(serverMoves);
10613             /* delay 15 sec before overwriting, so all clients can see end */
10614             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10615         }
10616         serverMoves = fopen(appData.serverMovesName, "w");
10617     }
10618
10619     ExitAnalyzeMode();
10620     gameMode = BeginningOfGame;
10621     ModeHighlight();
10622     if(appData.icsActive) gameInfo.variant = VariantNormal;
10623     currentMove = forwardMostMove = backwardMostMove = 0;
10624     InitPosition(redraw);
10625     for (i = 0; i < MAX_MOVES; i++) {
10626         if (commentList[i] != NULL) {
10627             free(commentList[i]);
10628             commentList[i] = NULL;
10629         }
10630     }
10631     ResetClocks();
10632     timeRemaining[0][0] = whiteTimeRemaining;
10633     timeRemaining[1][0] = blackTimeRemaining;
10634
10635     if (first.pr == NULL) {
10636         StartChessProgram(&first);
10637     }
10638     if (init) {
10639             InitChessProgram(&first, startedFromSetupPosition);
10640     }
10641     DisplayTitle("");
10642     DisplayMessage("", "");
10643     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10644     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10645 }
10646
10647 void
10648 AutoPlayGameLoop()
10649 {
10650     for (;;) {
10651         if (!AutoPlayOneMove())
10652           return;
10653         if (matchMode || appData.timeDelay == 0)
10654           continue;
10655         if (appData.timeDelay < 0)
10656           return;
10657         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10658         break;
10659     }
10660 }
10661
10662
10663 int
10664 AutoPlayOneMove()
10665 {
10666     int fromX, fromY, toX, toY;
10667
10668     if (appData.debugMode) {
10669       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10670     }
10671
10672     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10673       return FALSE;
10674
10675     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10676       pvInfoList[currentMove].depth = programStats.depth;
10677       pvInfoList[currentMove].score = programStats.score;
10678       pvInfoList[currentMove].time  = 0;
10679       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10680     }
10681
10682     if (currentMove >= forwardMostMove) {
10683       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10684 //      gameMode = EndOfGame;
10685 //      ModeHighlight();
10686
10687       /* [AS] Clear current move marker at the end of a game */
10688       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10689
10690       return FALSE;
10691     }
10692
10693     toX = moveList[currentMove][2] - AAA;
10694     toY = moveList[currentMove][3] - ONE;
10695
10696     if (moveList[currentMove][1] == '@') {
10697         if (appData.highlightLastMove) {
10698             SetHighlights(-1, -1, toX, toY);
10699         }
10700     } else {
10701         fromX = moveList[currentMove][0] - AAA;
10702         fromY = moveList[currentMove][1] - ONE;
10703
10704         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10705
10706         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10707
10708         if (appData.highlightLastMove) {
10709             SetHighlights(fromX, fromY, toX, toY);
10710         }
10711     }
10712     DisplayMove(currentMove);
10713     SendMoveToProgram(currentMove++, &first);
10714     DisplayBothClocks();
10715     DrawPosition(FALSE, boards[currentMove]);
10716     // [HGM] PV info: always display, routine tests if empty
10717     DisplayComment(currentMove - 1, commentList[currentMove]);
10718     return TRUE;
10719 }
10720
10721
10722 int
10723 LoadGameOneMove(readAhead)
10724      ChessMove readAhead;
10725 {
10726     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10727     char promoChar = NULLCHAR;
10728     ChessMove moveType;
10729     char move[MSG_SIZ];
10730     char *p, *q;
10731
10732     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10733         gameMode != AnalyzeMode && gameMode != Training) {
10734         gameFileFP = NULL;
10735         return FALSE;
10736     }
10737
10738     yyboardindex = forwardMostMove;
10739     if (readAhead != EndOfFile) {
10740       moveType = readAhead;
10741     } else {
10742       if (gameFileFP == NULL)
10743           return FALSE;
10744       moveType = (ChessMove) Myylex();
10745     }
10746
10747     done = FALSE;
10748     switch (moveType) {
10749       case Comment:
10750         if (appData.debugMode)
10751           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10752         p = yy_text;
10753
10754         /* append the comment but don't display it */
10755         AppendComment(currentMove, p, FALSE);
10756         return TRUE;
10757
10758       case WhiteCapturesEnPassant:
10759       case BlackCapturesEnPassant:
10760       case WhitePromotion:
10761       case BlackPromotion:
10762       case WhiteNonPromotion:
10763       case BlackNonPromotion:
10764       case NormalMove:
10765       case WhiteKingSideCastle:
10766       case WhiteQueenSideCastle:
10767       case BlackKingSideCastle:
10768       case BlackQueenSideCastle:
10769       case WhiteKingSideCastleWild:
10770       case WhiteQueenSideCastleWild:
10771       case BlackKingSideCastleWild:
10772       case BlackQueenSideCastleWild:
10773       /* PUSH Fabien */
10774       case WhiteHSideCastleFR:
10775       case WhiteASideCastleFR:
10776       case BlackHSideCastleFR:
10777       case BlackASideCastleFR:
10778       /* POP Fabien */
10779         if (appData.debugMode)
10780           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10781         fromX = currentMoveString[0] - AAA;
10782         fromY = currentMoveString[1] - ONE;
10783         toX = currentMoveString[2] - AAA;
10784         toY = currentMoveString[3] - ONE;
10785         promoChar = currentMoveString[4];
10786         break;
10787
10788       case WhiteDrop:
10789       case BlackDrop:
10790         if (appData.debugMode)
10791           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10792         fromX = moveType == WhiteDrop ?
10793           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10794         (int) CharToPiece(ToLower(currentMoveString[0]));
10795         fromY = DROP_RANK;
10796         toX = currentMoveString[2] - AAA;
10797         toY = currentMoveString[3] - ONE;
10798         break;
10799
10800       case WhiteWins:
10801       case BlackWins:
10802       case GameIsDrawn:
10803       case GameUnfinished:
10804         if (appData.debugMode)
10805           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10806         p = strchr(yy_text, '{');
10807         if (p == NULL) p = strchr(yy_text, '(');
10808         if (p == NULL) {
10809             p = yy_text;
10810             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10811         } else {
10812             q = strchr(p, *p == '{' ? '}' : ')');
10813             if (q != NULL) *q = NULLCHAR;
10814             p++;
10815         }
10816         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10817         GameEnds(moveType, p, GE_FILE);
10818         done = TRUE;
10819         if (cmailMsgLoaded) {
10820             ClearHighlights();
10821             flipView = WhiteOnMove(currentMove);
10822             if (moveType == GameUnfinished) flipView = !flipView;
10823             if (appData.debugMode)
10824               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10825         }
10826         break;
10827
10828       case EndOfFile:
10829         if (appData.debugMode)
10830           fprintf(debugFP, "Parser hit end of file\n");
10831         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10832           case MT_NONE:
10833           case MT_CHECK:
10834             break;
10835           case MT_CHECKMATE:
10836           case MT_STAINMATE:
10837             if (WhiteOnMove(currentMove)) {
10838                 GameEnds(BlackWins, "Black mates", GE_FILE);
10839             } else {
10840                 GameEnds(WhiteWins, "White mates", GE_FILE);
10841             }
10842             break;
10843           case MT_STALEMATE:
10844             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10845             break;
10846         }
10847         done = TRUE;
10848         break;
10849
10850       case MoveNumberOne:
10851         if (lastLoadGameStart == GNUChessGame) {
10852             /* GNUChessGames have numbers, but they aren't move numbers */
10853             if (appData.debugMode)
10854               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10855                       yy_text, (int) moveType);
10856             return LoadGameOneMove(EndOfFile); /* tail recursion */
10857         }
10858         /* else fall thru */
10859
10860       case XBoardGame:
10861       case GNUChessGame:
10862       case PGNTag:
10863         /* Reached start of next game in file */
10864         if (appData.debugMode)
10865           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10866         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10867           case MT_NONE:
10868           case MT_CHECK:
10869             break;
10870           case MT_CHECKMATE:
10871           case MT_STAINMATE:
10872             if (WhiteOnMove(currentMove)) {
10873                 GameEnds(BlackWins, "Black mates", GE_FILE);
10874             } else {
10875                 GameEnds(WhiteWins, "White mates", GE_FILE);
10876             }
10877             break;
10878           case MT_STALEMATE:
10879             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10880             break;
10881         }
10882         done = TRUE;
10883         break;
10884
10885       case PositionDiagram:     /* should not happen; ignore */
10886       case ElapsedTime:         /* ignore */
10887       case NAG:                 /* ignore */
10888         if (appData.debugMode)
10889           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10890                   yy_text, (int) moveType);
10891         return LoadGameOneMove(EndOfFile); /* tail recursion */
10892
10893       case IllegalMove:
10894         if (appData.testLegality) {
10895             if (appData.debugMode)
10896               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10897             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10898                     (forwardMostMove / 2) + 1,
10899                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10900             DisplayError(move, 0);
10901             done = TRUE;
10902         } else {
10903             if (appData.debugMode)
10904               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10905                       yy_text, currentMoveString);
10906             fromX = currentMoveString[0] - AAA;
10907             fromY = currentMoveString[1] - ONE;
10908             toX = currentMoveString[2] - AAA;
10909             toY = currentMoveString[3] - ONE;
10910             promoChar = currentMoveString[4];
10911         }
10912         break;
10913
10914       case AmbiguousMove:
10915         if (appData.debugMode)
10916           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10917         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10918                 (forwardMostMove / 2) + 1,
10919                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10920         DisplayError(move, 0);
10921         done = TRUE;
10922         break;
10923
10924       default:
10925       case ImpossibleMove:
10926         if (appData.debugMode)
10927           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10928         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10929                 (forwardMostMove / 2) + 1,
10930                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10931         DisplayError(move, 0);
10932         done = TRUE;
10933         break;
10934     }
10935
10936     if (done) {
10937         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10938             DrawPosition(FALSE, boards[currentMove]);
10939             DisplayBothClocks();
10940             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10941               DisplayComment(currentMove - 1, commentList[currentMove]);
10942         }
10943         (void) StopLoadGameTimer();
10944         gameFileFP = NULL;
10945         cmailOldMove = forwardMostMove;
10946         return FALSE;
10947     } else {
10948         /* currentMoveString is set as a side-effect of yylex */
10949
10950         thinkOutput[0] = NULLCHAR;
10951         MakeMove(fromX, fromY, toX, toY, promoChar);
10952         currentMove = forwardMostMove;
10953         return TRUE;
10954     }
10955 }
10956
10957 /* Load the nth game from the given file */
10958 int
10959 LoadGameFromFile(filename, n, title, useList)
10960      char *filename;
10961      int n;
10962      char *title;
10963      /*Boolean*/ int useList;
10964 {
10965     FILE *f;
10966     char buf[MSG_SIZ];
10967
10968     if (strcmp(filename, "-") == 0) {
10969         f = stdin;
10970         title = "stdin";
10971     } else {
10972         f = fopen(filename, "rb");
10973         if (f == NULL) {
10974           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10975             DisplayError(buf, errno);
10976             return FALSE;
10977         }
10978     }
10979     if (fseek(f, 0, 0) == -1) {
10980         /* f is not seekable; probably a pipe */
10981         useList = FALSE;
10982     }
10983     if (useList && n == 0) {
10984         int error = GameListBuild(f);
10985         if (error) {
10986             DisplayError(_("Cannot build game list"), error);
10987         } else if (!ListEmpty(&gameList) &&
10988                    ((ListGame *) gameList.tailPred)->number > 1) {
10989             GameListPopUp(f, title);
10990             return TRUE;
10991         }
10992         GameListDestroy();
10993         n = 1;
10994     }
10995     if (n == 0) n = 1;
10996     return LoadGame(f, n, title, FALSE);
10997 }
10998
10999
11000 void
11001 MakeRegisteredMove()
11002 {
11003     int fromX, fromY, toX, toY;
11004     char promoChar;
11005     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11006         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11007           case CMAIL_MOVE:
11008           case CMAIL_DRAW:
11009             if (appData.debugMode)
11010               fprintf(debugFP, "Restoring %s for game %d\n",
11011                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11012
11013             thinkOutput[0] = NULLCHAR;
11014             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11015             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11016             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11017             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11018             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11019             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11020             MakeMove(fromX, fromY, toX, toY, promoChar);
11021             ShowMove(fromX, fromY, toX, toY);
11022
11023             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11024               case MT_NONE:
11025               case MT_CHECK:
11026                 break;
11027
11028               case MT_CHECKMATE:
11029               case MT_STAINMATE:
11030                 if (WhiteOnMove(currentMove)) {
11031                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11032                 } else {
11033                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11034                 }
11035                 break;
11036
11037               case MT_STALEMATE:
11038                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11039                 break;
11040             }
11041
11042             break;
11043
11044           case CMAIL_RESIGN:
11045             if (WhiteOnMove(currentMove)) {
11046                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11047             } else {
11048                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11049             }
11050             break;
11051
11052           case CMAIL_ACCEPT:
11053             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11054             break;
11055
11056           default:
11057             break;
11058         }
11059     }
11060
11061     return;
11062 }
11063
11064 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11065 int
11066 CmailLoadGame(f, gameNumber, title, useList)
11067      FILE *f;
11068      int gameNumber;
11069      char *title;
11070      int useList;
11071 {
11072     int retVal;
11073
11074     if (gameNumber > nCmailGames) {
11075         DisplayError(_("No more games in this message"), 0);
11076         return FALSE;
11077     }
11078     if (f == lastLoadGameFP) {
11079         int offset = gameNumber - lastLoadGameNumber;
11080         if (offset == 0) {
11081             cmailMsg[0] = NULLCHAR;
11082             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11083                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11084                 nCmailMovesRegistered--;
11085             }
11086             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11087             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11088                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11089             }
11090         } else {
11091             if (! RegisterMove()) return FALSE;
11092         }
11093     }
11094
11095     retVal = LoadGame(f, gameNumber, title, useList);
11096
11097     /* Make move registered during previous look at this game, if any */
11098     MakeRegisteredMove();
11099
11100     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11101         commentList[currentMove]
11102           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11103         DisplayComment(currentMove - 1, commentList[currentMove]);
11104     }
11105
11106     return retVal;
11107 }
11108
11109 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11110 int
11111 ReloadGame(offset)
11112      int offset;
11113 {
11114     int gameNumber = lastLoadGameNumber + offset;
11115     if (lastLoadGameFP == NULL) {
11116         DisplayError(_("No game has been loaded yet"), 0);
11117         return FALSE;
11118     }
11119     if (gameNumber <= 0) {
11120         DisplayError(_("Can't back up any further"), 0);
11121         return FALSE;
11122     }
11123     if (cmailMsgLoaded) {
11124         return CmailLoadGame(lastLoadGameFP, gameNumber,
11125                              lastLoadGameTitle, lastLoadGameUseList);
11126     } else {
11127         return LoadGame(lastLoadGameFP, gameNumber,
11128                         lastLoadGameTitle, lastLoadGameUseList);
11129     }
11130 }
11131
11132 int keys[EmptySquare+1];
11133
11134 int
11135 PositionMatches(Board b1, Board b2)
11136 {
11137     int r, f, sum=0;
11138     switch(appData.searchMode) {
11139         case 1: return CompareWithRights(b1, b2);
11140         case 2:
11141             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11142                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11143             }
11144             return TRUE;
11145         case 3:
11146             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11147               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11148                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11149             }
11150             return sum==0;
11151         case 4:
11152             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11153                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11154             }
11155             return sum==0;
11156     }
11157     return TRUE;
11158 }
11159
11160 GameInfo dummyInfo;
11161
11162 int GameContainsPosition(FILE *f, ListGame *lg)
11163 {
11164     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11165     int fromX, fromY, toX, toY;
11166     char promoChar;
11167     static int initDone=FALSE;
11168
11169     if(!initDone) {
11170         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11171         initDone = TRUE;
11172     }
11173     dummyInfo.variant = VariantNormal;
11174     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11175     dummyInfo.whiteRating = 0;
11176     dummyInfo.blackRating = 0;
11177     FREE(dummyInfo.date); dummyInfo.date = NULL;
11178     fseek(f, lg->offset, 0);
11179     yynewfile(f);
11180     CopyBoard(boards[scratch], initialPosition); // default start position
11181     while(1) {
11182         yyboardindex = scratch + (plyNr&1);
11183       quickFlag = 1;
11184         next = Myylex();
11185       quickFlag = 0;
11186         switch(next) {
11187             case PGNTag:
11188                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11189 #if 0
11190                 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11191                 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11192 #else
11193                 // do it ourselves avoiding malloc
11194                 { char *p = yy_text+1, *q;
11195                   while(!isdigit(*p) && !isalpha(*p)) p++;
11196                   q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11197                   *p = NULLCHAR;
11198                   if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11199                   if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11200                   if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11201                   if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11202                   if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11203                   if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11204                   if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11205                 }
11206 #endif
11207             default:
11208                 continue;
11209
11210             case XBoardGame:
11211             case GNUChessGame:
11212                 if(plyNr) return -1; // after we have seen moves, this is for new game
11213               continue;
11214
11215             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11216             case ImpossibleMove:
11217             case WhiteWins: // game ends here with these four
11218             case BlackWins:
11219             case GameIsDrawn:
11220             case GameUnfinished:
11221                 return -1;
11222
11223             case IllegalMove:
11224                 if(appData.testLegality) return -1;
11225             case WhiteCapturesEnPassant:
11226             case BlackCapturesEnPassant:
11227             case WhitePromotion:
11228             case BlackPromotion:
11229             case WhiteNonPromotion:
11230             case BlackNonPromotion:
11231             case NormalMove:
11232             case WhiteKingSideCastle:
11233             case WhiteQueenSideCastle:
11234             case BlackKingSideCastle:
11235             case BlackQueenSideCastle:
11236             case WhiteKingSideCastleWild:
11237             case WhiteQueenSideCastleWild:
11238             case BlackKingSideCastleWild:
11239             case BlackQueenSideCastleWild:
11240             case WhiteHSideCastleFR:
11241             case WhiteASideCastleFR:
11242             case BlackHSideCastleFR:
11243             case BlackASideCastleFR:
11244                 fromX = currentMoveString[0] - AAA;
11245                 fromY = currentMoveString[1] - ONE;
11246                 toX = currentMoveString[2] - AAA;
11247                 toY = currentMoveString[3] - ONE;
11248                 promoChar = currentMoveString[4];
11249                 break;
11250             case WhiteDrop:
11251             case BlackDrop:
11252                 fromX = next == WhiteDrop ?
11253                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11254                   (int) CharToPiece(ToLower(currentMoveString[0]));
11255                 fromY = DROP_RANK;
11256                 toX = currentMoveString[2] - AAA;
11257                 toY = currentMoveString[3] - ONE;
11258                 break;
11259         }
11260         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11261         if(plyNr == 0) { // but first figure out variant and initial position
11262             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11263             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11264             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11265             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11266             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11267             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11268         }
11269         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11270         plyNr++;
11271         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11272         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11273     }
11274 }
11275
11276 /* Load the nth game from open file f */
11277 int
11278 LoadGame(f, gameNumber, title, useList)
11279      FILE *f;
11280      int gameNumber;
11281      char *title;
11282      int useList;
11283 {
11284     ChessMove cm;
11285     char buf[MSG_SIZ];
11286     int gn = gameNumber;
11287     ListGame *lg = NULL;
11288     int numPGNTags = 0;
11289     int err, pos = -1;
11290     GameMode oldGameMode;
11291     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11292
11293     if (appData.debugMode)
11294         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11295
11296     if (gameMode == Training )
11297         SetTrainingModeOff();
11298
11299     oldGameMode = gameMode;
11300     if (gameMode != BeginningOfGame) {
11301       Reset(FALSE, TRUE);
11302     }
11303
11304     gameFileFP = f;
11305     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11306         fclose(lastLoadGameFP);
11307     }
11308
11309     if (useList) {
11310         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11311
11312         if (lg) {
11313             fseek(f, lg->offset, 0);
11314             GameListHighlight(gameNumber);
11315             pos = lg->position;
11316             gn = 1;
11317         }
11318         else {
11319             DisplayError(_("Game number out of range"), 0);
11320             return FALSE;
11321         }
11322     } else {
11323         GameListDestroy();
11324         if (fseek(f, 0, 0) == -1) {
11325             if (f == lastLoadGameFP ?
11326                 gameNumber == lastLoadGameNumber + 1 :
11327                 gameNumber == 1) {
11328                 gn = 1;
11329             } else {
11330                 DisplayError(_("Can't seek on game file"), 0);
11331                 return FALSE;
11332             }
11333         }
11334     }
11335     lastLoadGameFP = f;
11336     lastLoadGameNumber = gameNumber;
11337     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11338     lastLoadGameUseList = useList;
11339
11340     yynewfile(f);
11341
11342     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11343       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11344                 lg->gameInfo.black);
11345             DisplayTitle(buf);
11346     } else if (*title != NULLCHAR) {
11347         if (gameNumber > 1) {
11348           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11349             DisplayTitle(buf);
11350         } else {
11351             DisplayTitle(title);
11352         }
11353     }
11354
11355     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11356         gameMode = PlayFromGameFile;
11357         ModeHighlight();
11358     }
11359
11360     currentMove = forwardMostMove = backwardMostMove = 0;
11361     CopyBoard(boards[0], initialPosition);
11362     StopClocks();
11363
11364     /*
11365      * Skip the first gn-1 games in the file.
11366      * Also skip over anything that precedes an identifiable
11367      * start of game marker, to avoid being confused by
11368      * garbage at the start of the file.  Currently
11369      * recognized start of game markers are the move number "1",
11370      * the pattern "gnuchess .* game", the pattern
11371      * "^[#;%] [^ ]* game file", and a PGN tag block.
11372      * A game that starts with one of the latter two patterns
11373      * will also have a move number 1, possibly
11374      * following a position diagram.
11375      * 5-4-02: Let's try being more lenient and allowing a game to
11376      * start with an unnumbered move.  Does that break anything?
11377      */
11378     cm = lastLoadGameStart = EndOfFile;
11379     while (gn > 0) {
11380         yyboardindex = forwardMostMove;
11381         cm = (ChessMove) Myylex();
11382         switch (cm) {
11383           case EndOfFile:
11384             if (cmailMsgLoaded) {
11385                 nCmailGames = CMAIL_MAX_GAMES - gn;
11386             } else {
11387                 Reset(TRUE, TRUE);
11388                 DisplayError(_("Game not found in file"), 0);
11389             }
11390             return FALSE;
11391
11392           case GNUChessGame:
11393           case XBoardGame:
11394             gn--;
11395             lastLoadGameStart = cm;
11396             break;
11397
11398           case MoveNumberOne:
11399             switch (lastLoadGameStart) {
11400               case GNUChessGame:
11401               case XBoardGame:
11402               case PGNTag:
11403                 break;
11404               case MoveNumberOne:
11405               case EndOfFile:
11406                 gn--;           /* count this game */
11407                 lastLoadGameStart = cm;
11408                 break;
11409               default:
11410                 /* impossible */
11411                 break;
11412             }
11413             break;
11414
11415           case PGNTag:
11416             switch (lastLoadGameStart) {
11417               case GNUChessGame:
11418               case PGNTag:
11419               case MoveNumberOne:
11420               case EndOfFile:
11421                 gn--;           /* count this game */
11422                 lastLoadGameStart = cm;
11423                 break;
11424               case XBoardGame:
11425                 lastLoadGameStart = cm; /* game counted already */
11426                 break;
11427               default:
11428                 /* impossible */
11429                 break;
11430             }
11431             if (gn > 0) {
11432                 do {
11433                     yyboardindex = forwardMostMove;
11434                     cm = (ChessMove) Myylex();
11435                 } while (cm == PGNTag || cm == Comment);
11436             }
11437             break;
11438
11439           case WhiteWins:
11440           case BlackWins:
11441           case GameIsDrawn:
11442             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11443                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11444                     != CMAIL_OLD_RESULT) {
11445                     nCmailResults ++ ;
11446                     cmailResult[  CMAIL_MAX_GAMES
11447                                 - gn - 1] = CMAIL_OLD_RESULT;
11448                 }
11449             }
11450             break;
11451
11452           case NormalMove:
11453             /* Only a NormalMove can be at the start of a game
11454              * without a position diagram. */
11455             if (lastLoadGameStart == EndOfFile ) {
11456               gn--;
11457               lastLoadGameStart = MoveNumberOne;
11458             }
11459             break;
11460
11461           default:
11462             break;
11463         }
11464     }
11465
11466     if (appData.debugMode)
11467       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11468
11469     if (cm == XBoardGame) {
11470         /* Skip any header junk before position diagram and/or move 1 */
11471         for (;;) {
11472             yyboardindex = forwardMostMove;
11473             cm = (ChessMove) Myylex();
11474
11475             if (cm == EndOfFile ||
11476                 cm == GNUChessGame || cm == XBoardGame) {
11477                 /* Empty game; pretend end-of-file and handle later */
11478                 cm = EndOfFile;
11479                 break;
11480             }
11481
11482             if (cm == MoveNumberOne || cm == PositionDiagram ||
11483                 cm == PGNTag || cm == Comment)
11484               break;
11485         }
11486     } else if (cm == GNUChessGame) {
11487         if (gameInfo.event != NULL) {
11488             free(gameInfo.event);
11489         }
11490         gameInfo.event = StrSave(yy_text);
11491     }
11492
11493     startedFromSetupPosition = FALSE;
11494     while (cm == PGNTag) {
11495         if (appData.debugMode)
11496           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11497         err = ParsePGNTag(yy_text, &gameInfo);
11498         if (!err) numPGNTags++;
11499
11500         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11501         if(gameInfo.variant != oldVariant) {
11502             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11503             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11504             InitPosition(TRUE);
11505             oldVariant = gameInfo.variant;
11506             if (appData.debugMode)
11507               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11508         }
11509
11510
11511         if (gameInfo.fen != NULL) {
11512           Board initial_position;
11513           startedFromSetupPosition = TRUE;
11514           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11515             Reset(TRUE, TRUE);
11516             DisplayError(_("Bad FEN position in file"), 0);
11517             return FALSE;
11518           }
11519           CopyBoard(boards[0], initial_position);
11520           if (blackPlaysFirst) {
11521             currentMove = forwardMostMove = backwardMostMove = 1;
11522             CopyBoard(boards[1], initial_position);
11523             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11524             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11525             timeRemaining[0][1] = whiteTimeRemaining;
11526             timeRemaining[1][1] = blackTimeRemaining;
11527             if (commentList[0] != NULL) {
11528               commentList[1] = commentList[0];
11529               commentList[0] = NULL;
11530             }
11531           } else {
11532             currentMove = forwardMostMove = backwardMostMove = 0;
11533           }
11534           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11535           {   int i;
11536               initialRulePlies = FENrulePlies;
11537               for( i=0; i< nrCastlingRights; i++ )
11538                   initialRights[i] = initial_position[CASTLING][i];
11539           }
11540           yyboardindex = forwardMostMove;
11541           free(gameInfo.fen);
11542           gameInfo.fen = NULL;
11543         }
11544
11545         yyboardindex = forwardMostMove;
11546         cm = (ChessMove) Myylex();
11547
11548         /* Handle comments interspersed among the tags */
11549         while (cm == Comment) {
11550             char *p;
11551             if (appData.debugMode)
11552               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11553             p = yy_text;
11554             AppendComment(currentMove, p, FALSE);
11555             yyboardindex = forwardMostMove;
11556             cm = (ChessMove) Myylex();
11557         }
11558     }
11559
11560     /* don't rely on existence of Event tag since if game was
11561      * pasted from clipboard the Event tag may not exist
11562      */
11563     if (numPGNTags > 0){
11564         char *tags;
11565         if (gameInfo.variant == VariantNormal) {
11566           VariantClass v = StringToVariant(gameInfo.event);
11567           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11568           if(v < VariantShogi) gameInfo.variant = v;
11569         }
11570         if (!matchMode) {
11571           if( appData.autoDisplayTags ) {
11572             tags = PGNTags(&gameInfo);
11573             TagsPopUp(tags, CmailMsg());
11574             free(tags);
11575           }
11576         }
11577     } else {
11578         /* Make something up, but don't display it now */
11579         SetGameInfo();
11580         TagsPopDown();
11581     }
11582
11583     if (cm == PositionDiagram) {
11584         int i, j;
11585         char *p;
11586         Board initial_position;
11587
11588         if (appData.debugMode)
11589           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11590
11591         if (!startedFromSetupPosition) {
11592             p = yy_text;
11593             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11594               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11595                 switch (*p) {
11596                   case '{':
11597                   case '[':
11598                   case '-':
11599                   case ' ':
11600                   case '\t':
11601                   case '\n':
11602                   case '\r':
11603                     break;
11604                   default:
11605                     initial_position[i][j++] = CharToPiece(*p);
11606                     break;
11607                 }
11608             while (*p == ' ' || *p == '\t' ||
11609                    *p == '\n' || *p == '\r') p++;
11610
11611             if (strncmp(p, "black", strlen("black"))==0)
11612               blackPlaysFirst = TRUE;
11613             else
11614               blackPlaysFirst = FALSE;
11615             startedFromSetupPosition = TRUE;
11616
11617             CopyBoard(boards[0], initial_position);
11618             if (blackPlaysFirst) {
11619                 currentMove = forwardMostMove = backwardMostMove = 1;
11620                 CopyBoard(boards[1], initial_position);
11621                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11622                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11623                 timeRemaining[0][1] = whiteTimeRemaining;
11624                 timeRemaining[1][1] = blackTimeRemaining;
11625                 if (commentList[0] != NULL) {
11626                     commentList[1] = commentList[0];
11627                     commentList[0] = NULL;
11628                 }
11629             } else {
11630                 currentMove = forwardMostMove = backwardMostMove = 0;
11631             }
11632         }
11633         yyboardindex = forwardMostMove;
11634         cm = (ChessMove) Myylex();
11635     }
11636
11637     if (first.pr == NoProc) {
11638         StartChessProgram(&first);
11639     }
11640     InitChessProgram(&first, FALSE);
11641     SendToProgram("force\n", &first);
11642     if (startedFromSetupPosition) {
11643         SendBoard(&first, forwardMostMove);
11644     if (appData.debugMode) {
11645         fprintf(debugFP, "Load Game\n");
11646     }
11647         DisplayBothClocks();
11648     }
11649
11650     /* [HGM] server: flag to write setup moves in broadcast file as one */
11651     loadFlag = appData.suppressLoadMoves;
11652
11653     while (cm == Comment) {
11654         char *p;
11655         if (appData.debugMode)
11656           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11657         p = yy_text;
11658         AppendComment(currentMove, p, FALSE);
11659         yyboardindex = forwardMostMove;
11660         cm = (ChessMove) Myylex();
11661     }
11662
11663     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11664         cm == WhiteWins || cm == BlackWins ||
11665         cm == GameIsDrawn || cm == GameUnfinished) {
11666         DisplayMessage("", _("No moves in game"));
11667         if (cmailMsgLoaded) {
11668             if (appData.debugMode)
11669               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11670             ClearHighlights();
11671             flipView = FALSE;
11672         }
11673         DrawPosition(FALSE, boards[currentMove]);
11674         DisplayBothClocks();
11675         gameMode = EditGame;
11676         ModeHighlight();
11677         gameFileFP = NULL;
11678         cmailOldMove = 0;
11679         return TRUE;
11680     }
11681
11682     // [HGM] PV info: routine tests if comment empty
11683     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11684         DisplayComment(currentMove - 1, commentList[currentMove]);
11685     }
11686     if (!matchMode && appData.timeDelay != 0)
11687       DrawPosition(FALSE, boards[currentMove]);
11688
11689     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11690       programStats.ok_to_send = 1;
11691     }
11692
11693     /* if the first token after the PGN tags is a move
11694      * and not move number 1, retrieve it from the parser
11695      */
11696     if (cm != MoveNumberOne)
11697         LoadGameOneMove(cm);
11698
11699     /* load the remaining moves from the file */
11700     while (LoadGameOneMove(EndOfFile)) {
11701       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11702       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11703     }
11704
11705     /* rewind to the start of the game */
11706     currentMove = backwardMostMove;
11707
11708     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11709
11710     if (oldGameMode == AnalyzeFile ||
11711         oldGameMode == AnalyzeMode) {
11712       AnalyzeFileEvent();
11713     }
11714
11715     if (!matchMode && pos >= 0) {
11716         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11717     } else
11718     if (matchMode || appData.timeDelay == 0) {
11719       ToEndEvent();
11720     } else if (appData.timeDelay > 0) {
11721       AutoPlayGameLoop();
11722     }
11723
11724     if (appData.debugMode)
11725         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11726
11727     loadFlag = 0; /* [HGM] true game starts */
11728     return TRUE;
11729 }
11730
11731 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11732 int
11733 ReloadPosition(offset)
11734      int offset;
11735 {
11736     int positionNumber = lastLoadPositionNumber + offset;
11737     if (lastLoadPositionFP == NULL) {
11738         DisplayError(_("No position has been loaded yet"), 0);
11739         return FALSE;
11740     }
11741     if (positionNumber <= 0) {
11742         DisplayError(_("Can't back up any further"), 0);
11743         return FALSE;
11744     }
11745     return LoadPosition(lastLoadPositionFP, positionNumber,
11746                         lastLoadPositionTitle);
11747 }
11748
11749 /* Load the nth position from the given file */
11750 int
11751 LoadPositionFromFile(filename, n, title)
11752      char *filename;
11753      int n;
11754      char *title;
11755 {
11756     FILE *f;
11757     char buf[MSG_SIZ];
11758
11759     if (strcmp(filename, "-") == 0) {
11760         return LoadPosition(stdin, n, "stdin");
11761     } else {
11762         f = fopen(filename, "rb");
11763         if (f == NULL) {
11764             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11765             DisplayError(buf, errno);
11766             return FALSE;
11767         } else {
11768             return LoadPosition(f, n, title);
11769         }
11770     }
11771 }
11772
11773 /* Load the nth position from the given open file, and close it */
11774 int
11775 LoadPosition(f, positionNumber, title)
11776      FILE *f;
11777      int positionNumber;
11778      char *title;
11779 {
11780     char *p, line[MSG_SIZ];
11781     Board initial_position;
11782     int i, j, fenMode, pn;
11783
11784     if (gameMode == Training )
11785         SetTrainingModeOff();
11786
11787     if (gameMode != BeginningOfGame) {
11788         Reset(FALSE, TRUE);
11789     }
11790     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11791         fclose(lastLoadPositionFP);
11792     }
11793     if (positionNumber == 0) positionNumber = 1;
11794     lastLoadPositionFP = f;
11795     lastLoadPositionNumber = positionNumber;
11796     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11797     if (first.pr == NoProc) {
11798       StartChessProgram(&first);
11799       InitChessProgram(&first, FALSE);
11800     }
11801     pn = positionNumber;
11802     if (positionNumber < 0) {
11803         /* Negative position number means to seek to that byte offset */
11804         if (fseek(f, -positionNumber, 0) == -1) {
11805             DisplayError(_("Can't seek on position file"), 0);
11806             return FALSE;
11807         };
11808         pn = 1;
11809     } else {
11810         if (fseek(f, 0, 0) == -1) {
11811             if (f == lastLoadPositionFP ?
11812                 positionNumber == lastLoadPositionNumber + 1 :
11813                 positionNumber == 1) {
11814                 pn = 1;
11815             } else {
11816                 DisplayError(_("Can't seek on position file"), 0);
11817                 return FALSE;
11818             }
11819         }
11820     }
11821     /* See if this file is FEN or old-style xboard */
11822     if (fgets(line, MSG_SIZ, f) == NULL) {
11823         DisplayError(_("Position not found in file"), 0);
11824         return FALSE;
11825     }
11826     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11827     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11828
11829     if (pn >= 2) {
11830         if (fenMode || line[0] == '#') pn--;
11831         while (pn > 0) {
11832             /* skip positions before number pn */
11833             if (fgets(line, MSG_SIZ, f) == NULL) {
11834                 Reset(TRUE, TRUE);
11835                 DisplayError(_("Position not found in file"), 0);
11836                 return FALSE;
11837             }
11838             if (fenMode || line[0] == '#') pn--;
11839         }
11840     }
11841
11842     if (fenMode) {
11843         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11844             DisplayError(_("Bad FEN position in file"), 0);
11845             return FALSE;
11846         }
11847     } else {
11848         (void) fgets(line, MSG_SIZ, f);
11849         (void) fgets(line, MSG_SIZ, f);
11850
11851         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11852             (void) fgets(line, MSG_SIZ, f);
11853             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11854                 if (*p == ' ')
11855                   continue;
11856                 initial_position[i][j++] = CharToPiece(*p);
11857             }
11858         }
11859
11860         blackPlaysFirst = FALSE;
11861         if (!feof(f)) {
11862             (void) fgets(line, MSG_SIZ, f);
11863             if (strncmp(line, "black", strlen("black"))==0)
11864               blackPlaysFirst = TRUE;
11865         }
11866     }
11867     startedFromSetupPosition = TRUE;
11868
11869     SendToProgram("force\n", &first);
11870     CopyBoard(boards[0], initial_position);
11871     if (blackPlaysFirst) {
11872         currentMove = forwardMostMove = backwardMostMove = 1;
11873         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11874         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11875         CopyBoard(boards[1], initial_position);
11876         DisplayMessage("", _("Black to play"));
11877     } else {
11878         currentMove = forwardMostMove = backwardMostMove = 0;
11879         DisplayMessage("", _("White to play"));
11880     }
11881     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11882     SendBoard(&first, forwardMostMove);
11883     if (appData.debugMode) {
11884 int i, j;
11885   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11886   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11887         fprintf(debugFP, "Load Position\n");
11888     }
11889
11890     if (positionNumber > 1) {
11891       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11892         DisplayTitle(line);
11893     } else {
11894         DisplayTitle(title);
11895     }
11896     gameMode = EditGame;
11897     ModeHighlight();
11898     ResetClocks();
11899     timeRemaining[0][1] = whiteTimeRemaining;
11900     timeRemaining[1][1] = blackTimeRemaining;
11901     DrawPosition(FALSE, boards[currentMove]);
11902
11903     return TRUE;
11904 }
11905
11906
11907 void
11908 CopyPlayerNameIntoFileName(dest, src)
11909      char **dest, *src;
11910 {
11911     while (*src != NULLCHAR && *src != ',') {
11912         if (*src == ' ') {
11913             *(*dest)++ = '_';
11914             src++;
11915         } else {
11916             *(*dest)++ = *src++;
11917         }
11918     }
11919 }
11920
11921 char *DefaultFileName(ext)
11922      char *ext;
11923 {
11924     static char def[MSG_SIZ];
11925     char *p;
11926
11927     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11928         p = def;
11929         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11930         *p++ = '-';
11931         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11932         *p++ = '.';
11933         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11934     } else {
11935         def[0] = NULLCHAR;
11936     }
11937     return def;
11938 }
11939
11940 /* Save the current game to the given file */
11941 int
11942 SaveGameToFile(filename, append)
11943      char *filename;
11944      int append;
11945 {
11946     FILE *f;
11947     char buf[MSG_SIZ];
11948     int result;
11949
11950     if (strcmp(filename, "-") == 0) {
11951         return SaveGame(stdout, 0, NULL);
11952     } else {
11953         f = fopen(filename, append ? "a" : "w");
11954         if (f == NULL) {
11955             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11956             DisplayError(buf, errno);
11957             return FALSE;
11958         } else {
11959             safeStrCpy(buf, lastMsg, MSG_SIZ);
11960             DisplayMessage(_("Waiting for access to save file"), "");
11961             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11962             DisplayMessage(_("Saving game"), "");
11963             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11964             result = SaveGame(f, 0, NULL);
11965             DisplayMessage(buf, "");
11966             return result;
11967         }
11968     }
11969 }
11970
11971 char *
11972 SavePart(str)
11973      char *str;
11974 {
11975     static char buf[MSG_SIZ];
11976     char *p;
11977
11978     p = strchr(str, ' ');
11979     if (p == NULL) return str;
11980     strncpy(buf, str, p - str);
11981     buf[p - str] = NULLCHAR;
11982     return buf;
11983 }
11984
11985 #define PGN_MAX_LINE 75
11986
11987 #define PGN_SIDE_WHITE  0
11988 #define PGN_SIDE_BLACK  1
11989
11990 /* [AS] */
11991 static int FindFirstMoveOutOfBook( int side )
11992 {
11993     int result = -1;
11994
11995     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11996         int index = backwardMostMove;
11997         int has_book_hit = 0;
11998
11999         if( (index % 2) != side ) {
12000             index++;
12001         }
12002
12003         while( index < forwardMostMove ) {
12004             /* Check to see if engine is in book */
12005             int depth = pvInfoList[index].depth;
12006             int score = pvInfoList[index].score;
12007             int in_book = 0;
12008
12009             if( depth <= 2 ) {
12010                 in_book = 1;
12011             }
12012             else if( score == 0 && depth == 63 ) {
12013                 in_book = 1; /* Zappa */
12014             }
12015             else if( score == 2 && depth == 99 ) {
12016                 in_book = 1; /* Abrok */
12017             }
12018
12019             has_book_hit += in_book;
12020
12021             if( ! in_book ) {
12022                 result = index;
12023
12024                 break;
12025             }
12026
12027             index += 2;
12028         }
12029     }
12030
12031     return result;
12032 }
12033
12034 /* [AS] */
12035 void GetOutOfBookInfo( char * buf )
12036 {
12037     int oob[2];
12038     int i;
12039     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12040
12041     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12042     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12043
12044     *buf = '\0';
12045
12046     if( oob[0] >= 0 || oob[1] >= 0 ) {
12047         for( i=0; i<2; i++ ) {
12048             int idx = oob[i];
12049
12050             if( idx >= 0 ) {
12051                 if( i > 0 && oob[0] >= 0 ) {
12052                     strcat( buf, "   " );
12053                 }
12054
12055                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12056                 sprintf( buf+strlen(buf), "%s%.2f",
12057                     pvInfoList[idx].score >= 0 ? "+" : "",
12058                     pvInfoList[idx].score / 100.0 );
12059             }
12060         }
12061     }
12062 }
12063
12064 /* Save game in PGN style and close the file */
12065 int
12066 SaveGamePGN(f)
12067      FILE *f;
12068 {
12069     int i, offset, linelen, newblock;
12070     time_t tm;
12071 //    char *movetext;
12072     char numtext[32];
12073     int movelen, numlen, blank;
12074     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12075
12076     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12077
12078     tm = time((time_t *) NULL);
12079
12080     PrintPGNTags(f, &gameInfo);
12081
12082     if (backwardMostMove > 0 || startedFromSetupPosition) {
12083         char *fen = PositionToFEN(backwardMostMove, NULL);
12084         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12085         fprintf(f, "\n{--------------\n");
12086         PrintPosition(f, backwardMostMove);
12087         fprintf(f, "--------------}\n");
12088         free(fen);
12089     }
12090     else {
12091         /* [AS] Out of book annotation */
12092         if( appData.saveOutOfBookInfo ) {
12093             char buf[64];
12094
12095             GetOutOfBookInfo( buf );
12096
12097             if( buf[0] != '\0' ) {
12098                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12099             }
12100         }
12101
12102         fprintf(f, "\n");
12103     }
12104
12105     i = backwardMostMove;
12106     linelen = 0;
12107     newblock = TRUE;
12108
12109     while (i < forwardMostMove) {
12110         /* Print comments preceding this move */
12111         if (commentList[i] != NULL) {
12112             if (linelen > 0) fprintf(f, "\n");
12113             fprintf(f, "%s", commentList[i]);
12114             linelen = 0;
12115             newblock = TRUE;
12116         }
12117
12118         /* Format move number */
12119         if ((i % 2) == 0)
12120           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12121         else
12122           if (newblock)
12123             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12124           else
12125             numtext[0] = NULLCHAR;
12126
12127         numlen = strlen(numtext);
12128         newblock = FALSE;
12129
12130         /* Print move number */
12131         blank = linelen > 0 && numlen > 0;
12132         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12133             fprintf(f, "\n");
12134             linelen = 0;
12135             blank = 0;
12136         }
12137         if (blank) {
12138             fprintf(f, " ");
12139             linelen++;
12140         }
12141         fprintf(f, "%s", numtext);
12142         linelen += numlen;
12143
12144         /* Get move */
12145         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12146         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12147
12148         /* Print move */
12149         blank = linelen > 0 && movelen > 0;
12150         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12151             fprintf(f, "\n");
12152             linelen = 0;
12153             blank = 0;
12154         }
12155         if (blank) {
12156             fprintf(f, " ");
12157             linelen++;
12158         }
12159         fprintf(f, "%s", move_buffer);
12160         linelen += movelen;
12161
12162         /* [AS] Add PV info if present */
12163         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12164             /* [HGM] add time */
12165             char buf[MSG_SIZ]; int seconds;
12166
12167             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12168
12169             if( seconds <= 0)
12170               buf[0] = 0;
12171             else
12172               if( seconds < 30 )
12173                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12174               else
12175                 {
12176                   seconds = (seconds + 4)/10; // round to full seconds
12177                   if( seconds < 60 )
12178                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12179                   else
12180                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12181                 }
12182
12183             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12184                       pvInfoList[i].score >= 0 ? "+" : "",
12185                       pvInfoList[i].score / 100.0,
12186                       pvInfoList[i].depth,
12187                       buf );
12188
12189             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12190
12191             /* Print score/depth */
12192             blank = linelen > 0 && movelen > 0;
12193             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12194                 fprintf(f, "\n");
12195                 linelen = 0;
12196                 blank = 0;
12197             }
12198             if (blank) {
12199                 fprintf(f, " ");
12200                 linelen++;
12201             }
12202             fprintf(f, "%s", move_buffer);
12203             linelen += movelen;
12204         }
12205
12206         i++;
12207     }
12208
12209     /* Start a new line */
12210     if (linelen > 0) fprintf(f, "\n");
12211
12212     /* Print comments after last move */
12213     if (commentList[i] != NULL) {
12214         fprintf(f, "%s\n", commentList[i]);
12215     }
12216
12217     /* Print result */
12218     if (gameInfo.resultDetails != NULL &&
12219         gameInfo.resultDetails[0] != NULLCHAR) {
12220         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12221                 PGNResult(gameInfo.result));
12222     } else {
12223         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12224     }
12225
12226     fclose(f);
12227     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12228     return TRUE;
12229 }
12230
12231 /* Save game in old style and close the file */
12232 int
12233 SaveGameOldStyle(f)
12234      FILE *f;
12235 {
12236     int i, offset;
12237     time_t tm;
12238
12239     tm = time((time_t *) NULL);
12240
12241     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12242     PrintOpponents(f);
12243
12244     if (backwardMostMove > 0 || startedFromSetupPosition) {
12245         fprintf(f, "\n[--------------\n");
12246         PrintPosition(f, backwardMostMove);
12247         fprintf(f, "--------------]\n");
12248     } else {
12249         fprintf(f, "\n");
12250     }
12251
12252     i = backwardMostMove;
12253     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12254
12255     while (i < forwardMostMove) {
12256         if (commentList[i] != NULL) {
12257             fprintf(f, "[%s]\n", commentList[i]);
12258         }
12259
12260         if ((i % 2) == 1) {
12261             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12262             i++;
12263         } else {
12264             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12265             i++;
12266             if (commentList[i] != NULL) {
12267                 fprintf(f, "\n");
12268                 continue;
12269             }
12270             if (i >= forwardMostMove) {
12271                 fprintf(f, "\n");
12272                 break;
12273             }
12274             fprintf(f, "%s\n", parseList[i]);
12275             i++;
12276         }
12277     }
12278
12279     if (commentList[i] != NULL) {
12280         fprintf(f, "[%s]\n", commentList[i]);
12281     }
12282
12283     /* This isn't really the old style, but it's close enough */
12284     if (gameInfo.resultDetails != NULL &&
12285         gameInfo.resultDetails[0] != NULLCHAR) {
12286         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12287                 gameInfo.resultDetails);
12288     } else {
12289         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12290     }
12291
12292     fclose(f);
12293     return TRUE;
12294 }
12295
12296 /* Save the current game to open file f and close the file */
12297 int
12298 SaveGame(f, dummy, dummy2)
12299      FILE *f;
12300      int dummy;
12301      char *dummy2;
12302 {
12303     if (gameMode == EditPosition) EditPositionDone(TRUE);
12304     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12305     if (appData.oldSaveStyle)
12306       return SaveGameOldStyle(f);
12307     else
12308       return SaveGamePGN(f);
12309 }
12310
12311 /* Save the current position to the given file */
12312 int
12313 SavePositionToFile(filename)
12314      char *filename;
12315 {
12316     FILE *f;
12317     char buf[MSG_SIZ];
12318
12319     if (strcmp(filename, "-") == 0) {
12320         return SavePosition(stdout, 0, NULL);
12321     } else {
12322         f = fopen(filename, "a");
12323         if (f == NULL) {
12324             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12325             DisplayError(buf, errno);
12326             return FALSE;
12327         } else {
12328             safeStrCpy(buf, lastMsg, MSG_SIZ);
12329             DisplayMessage(_("Waiting for access to save file"), "");
12330             flock(fileno(f), LOCK_EX); // [HGM] lock
12331             DisplayMessage(_("Saving position"), "");
12332             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12333             SavePosition(f, 0, NULL);
12334             DisplayMessage(buf, "");
12335             return TRUE;
12336         }
12337     }
12338 }
12339
12340 /* Save the current position to the given open file and close the file */
12341 int
12342 SavePosition(f, dummy, dummy2)
12343      FILE *f;
12344      int dummy;
12345      char *dummy2;
12346 {
12347     time_t tm;
12348     char *fen;
12349
12350     if (gameMode == EditPosition) EditPositionDone(TRUE);
12351     if (appData.oldSaveStyle) {
12352         tm = time((time_t *) NULL);
12353
12354         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12355         PrintOpponents(f);
12356         fprintf(f, "[--------------\n");
12357         PrintPosition(f, currentMove);
12358         fprintf(f, "--------------]\n");
12359     } else {
12360         fen = PositionToFEN(currentMove, NULL);
12361         fprintf(f, "%s\n", fen);
12362         free(fen);
12363     }
12364     fclose(f);
12365     return TRUE;
12366 }
12367
12368 void
12369 ReloadCmailMsgEvent(unregister)
12370      int unregister;
12371 {
12372 #if !WIN32
12373     static char *inFilename = NULL;
12374     static char *outFilename;
12375     int i;
12376     struct stat inbuf, outbuf;
12377     int status;
12378
12379     /* Any registered moves are unregistered if unregister is set, */
12380     /* i.e. invoked by the signal handler */
12381     if (unregister) {
12382         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12383             cmailMoveRegistered[i] = FALSE;
12384             if (cmailCommentList[i] != NULL) {
12385                 free(cmailCommentList[i]);
12386                 cmailCommentList[i] = NULL;
12387             }
12388         }
12389         nCmailMovesRegistered = 0;
12390     }
12391
12392     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12393         cmailResult[i] = CMAIL_NOT_RESULT;
12394     }
12395     nCmailResults = 0;
12396
12397     if (inFilename == NULL) {
12398         /* Because the filenames are static they only get malloced once  */
12399         /* and they never get freed                                      */
12400         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12401         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12402
12403         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12404         sprintf(outFilename, "%s.out", appData.cmailGameName);
12405     }
12406
12407     status = stat(outFilename, &outbuf);
12408     if (status < 0) {
12409         cmailMailedMove = FALSE;
12410     } else {
12411         status = stat(inFilename, &inbuf);
12412         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12413     }
12414
12415     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12416        counts the games, notes how each one terminated, etc.
12417
12418        It would be nice to remove this kludge and instead gather all
12419        the information while building the game list.  (And to keep it
12420        in the game list nodes instead of having a bunch of fixed-size
12421        parallel arrays.)  Note this will require getting each game's
12422        termination from the PGN tags, as the game list builder does
12423        not process the game moves.  --mann
12424        */
12425     cmailMsgLoaded = TRUE;
12426     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12427
12428     /* Load first game in the file or popup game menu */
12429     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12430
12431 #endif /* !WIN32 */
12432     return;
12433 }
12434
12435 int
12436 RegisterMove()
12437 {
12438     FILE *f;
12439     char string[MSG_SIZ];
12440
12441     if (   cmailMailedMove
12442         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12443         return TRUE;            /* Allow free viewing  */
12444     }
12445
12446     /* Unregister move to ensure that we don't leave RegisterMove        */
12447     /* with the move registered when the conditions for registering no   */
12448     /* longer hold                                                       */
12449     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12450         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12451         nCmailMovesRegistered --;
12452
12453         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12454           {
12455               free(cmailCommentList[lastLoadGameNumber - 1]);
12456               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12457           }
12458     }
12459
12460     if (cmailOldMove == -1) {
12461         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12462         return FALSE;
12463     }
12464
12465     if (currentMove > cmailOldMove + 1) {
12466         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12467         return FALSE;
12468     }
12469
12470     if (currentMove < cmailOldMove) {
12471         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12472         return FALSE;
12473     }
12474
12475     if (forwardMostMove > currentMove) {
12476         /* Silently truncate extra moves */
12477         TruncateGame();
12478     }
12479
12480     if (   (currentMove == cmailOldMove + 1)
12481         || (   (currentMove == cmailOldMove)
12482             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12483                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12484         if (gameInfo.result != GameUnfinished) {
12485             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12486         }
12487
12488         if (commentList[currentMove] != NULL) {
12489             cmailCommentList[lastLoadGameNumber - 1]
12490               = StrSave(commentList[currentMove]);
12491         }
12492         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12493
12494         if (appData.debugMode)
12495           fprintf(debugFP, "Saving %s for game %d\n",
12496                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12497
12498         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12499
12500         f = fopen(string, "w");
12501         if (appData.oldSaveStyle) {
12502             SaveGameOldStyle(f); /* also closes the file */
12503
12504             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12505             f = fopen(string, "w");
12506             SavePosition(f, 0, NULL); /* also closes the file */
12507         } else {
12508             fprintf(f, "{--------------\n");
12509             PrintPosition(f, currentMove);
12510             fprintf(f, "--------------}\n\n");
12511
12512             SaveGame(f, 0, NULL); /* also closes the file*/
12513         }
12514
12515         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12516         nCmailMovesRegistered ++;
12517     } else if (nCmailGames == 1) {
12518         DisplayError(_("You have not made a move yet"), 0);
12519         return FALSE;
12520     }
12521
12522     return TRUE;
12523 }
12524
12525 void
12526 MailMoveEvent()
12527 {
12528 #if !WIN32
12529     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12530     FILE *commandOutput;
12531     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12532     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12533     int nBuffers;
12534     int i;
12535     int archived;
12536     char *arcDir;
12537
12538     if (! cmailMsgLoaded) {
12539         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12540         return;
12541     }
12542
12543     if (nCmailGames == nCmailResults) {
12544         DisplayError(_("No unfinished games"), 0);
12545         return;
12546     }
12547
12548 #if CMAIL_PROHIBIT_REMAIL
12549     if (cmailMailedMove) {
12550       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);
12551         DisplayError(msg, 0);
12552         return;
12553     }
12554 #endif
12555
12556     if (! (cmailMailedMove || RegisterMove())) return;
12557
12558     if (   cmailMailedMove
12559         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12560       snprintf(string, MSG_SIZ, partCommandString,
12561                appData.debugMode ? " -v" : "", appData.cmailGameName);
12562         commandOutput = popen(string, "r");
12563
12564         if (commandOutput == NULL) {
12565             DisplayError(_("Failed to invoke cmail"), 0);
12566         } else {
12567             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12568                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12569             }
12570             if (nBuffers > 1) {
12571                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12572                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12573                 nBytes = MSG_SIZ - 1;
12574             } else {
12575                 (void) memcpy(msg, buffer, nBytes);
12576             }
12577             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12578
12579             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12580                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12581
12582                 archived = TRUE;
12583                 for (i = 0; i < nCmailGames; i ++) {
12584                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12585                         archived = FALSE;
12586                     }
12587                 }
12588                 if (   archived
12589                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12590                         != NULL)) {
12591                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12592                            arcDir,
12593                            appData.cmailGameName,
12594                            gameInfo.date);
12595                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12596                     cmailMsgLoaded = FALSE;
12597                 }
12598             }
12599
12600             DisplayInformation(msg);
12601             pclose(commandOutput);
12602         }
12603     } else {
12604         if ((*cmailMsg) != '\0') {
12605             DisplayInformation(cmailMsg);
12606         }
12607     }
12608
12609     return;
12610 #endif /* !WIN32 */
12611 }
12612
12613 char *
12614 CmailMsg()
12615 {
12616 #if WIN32
12617     return NULL;
12618 #else
12619     int  prependComma = 0;
12620     char number[5];
12621     char string[MSG_SIZ];       /* Space for game-list */
12622     int  i;
12623
12624     if (!cmailMsgLoaded) return "";
12625
12626     if (cmailMailedMove) {
12627       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12628     } else {
12629         /* Create a list of games left */
12630       snprintf(string, MSG_SIZ, "[");
12631         for (i = 0; i < nCmailGames; i ++) {
12632             if (! (   cmailMoveRegistered[i]
12633                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12634                 if (prependComma) {
12635                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12636                 } else {
12637                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12638                     prependComma = 1;
12639                 }
12640
12641                 strcat(string, number);
12642             }
12643         }
12644         strcat(string, "]");
12645
12646         if (nCmailMovesRegistered + nCmailResults == 0) {
12647             switch (nCmailGames) {
12648               case 1:
12649                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12650                 break;
12651
12652               case 2:
12653                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12654                 break;
12655
12656               default:
12657                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12658                          nCmailGames);
12659                 break;
12660             }
12661         } else {
12662             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12663               case 1:
12664                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12665                          string);
12666                 break;
12667
12668               case 0:
12669                 if (nCmailResults == nCmailGames) {
12670                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12671                 } else {
12672                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12673                 }
12674                 break;
12675
12676               default:
12677                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12678                          string);
12679             }
12680         }
12681     }
12682     return cmailMsg;
12683 #endif /* WIN32 */
12684 }
12685
12686 void
12687 ResetGameEvent()
12688 {
12689     if (gameMode == Training)
12690       SetTrainingModeOff();
12691
12692     Reset(TRUE, TRUE);
12693     cmailMsgLoaded = FALSE;
12694     if (appData.icsActive) {
12695       SendToICS(ics_prefix);
12696       SendToICS("refresh\n");
12697     }
12698 }
12699
12700 void
12701 ExitEvent(status)
12702      int status;
12703 {
12704     exiting++;
12705     if (exiting > 2) {
12706       /* Give up on clean exit */
12707       exit(status);
12708     }
12709     if (exiting > 1) {
12710       /* Keep trying for clean exit */
12711       return;
12712     }
12713
12714     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12715
12716     if (telnetISR != NULL) {
12717       RemoveInputSource(telnetISR);
12718     }
12719     if (icsPR != NoProc) {
12720       DestroyChildProcess(icsPR, TRUE);
12721     }
12722
12723     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12724     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12725
12726     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12727     /* make sure this other one finishes before killing it!                  */
12728     if(endingGame) { int count = 0;
12729         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12730         while(endingGame && count++ < 10) DoSleep(1);
12731         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12732     }
12733
12734     /* Kill off chess programs */
12735     if (first.pr != NoProc) {
12736         ExitAnalyzeMode();
12737
12738         DoSleep( appData.delayBeforeQuit );
12739         SendToProgram("quit\n", &first);
12740         DoSleep( appData.delayAfterQuit );
12741         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12742     }
12743     if (second.pr != NoProc) {
12744         DoSleep( appData.delayBeforeQuit );
12745         SendToProgram("quit\n", &second);
12746         DoSleep( appData.delayAfterQuit );
12747         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12748     }
12749     if (first.isr != NULL) {
12750         RemoveInputSource(first.isr);
12751     }
12752     if (second.isr != NULL) {
12753         RemoveInputSource(second.isr);
12754     }
12755
12756     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12757     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12758
12759     ShutDownFrontEnd();
12760     exit(status);
12761 }
12762
12763 void
12764 PauseEvent()
12765 {
12766     if (appData.debugMode)
12767         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12768     if (pausing) {
12769         pausing = FALSE;
12770         ModeHighlight();
12771         if (gameMode == MachinePlaysWhite ||
12772             gameMode == MachinePlaysBlack) {
12773             StartClocks();
12774         } else {
12775             DisplayBothClocks();
12776         }
12777         if (gameMode == PlayFromGameFile) {
12778             if (appData.timeDelay >= 0)
12779                 AutoPlayGameLoop();
12780         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12781             Reset(FALSE, TRUE);
12782             SendToICS(ics_prefix);
12783             SendToICS("refresh\n");
12784         } else if (currentMove < forwardMostMove) {
12785             ForwardInner(forwardMostMove);
12786         }
12787         pauseExamInvalid = FALSE;
12788     } else {
12789         switch (gameMode) {
12790           default:
12791             return;
12792           case IcsExamining:
12793             pauseExamForwardMostMove = forwardMostMove;
12794             pauseExamInvalid = FALSE;
12795             /* fall through */
12796           case IcsObserving:
12797           case IcsPlayingWhite:
12798           case IcsPlayingBlack:
12799             pausing = TRUE;
12800             ModeHighlight();
12801             return;
12802           case PlayFromGameFile:
12803             (void) StopLoadGameTimer();
12804             pausing = TRUE;
12805             ModeHighlight();
12806             break;
12807           case BeginningOfGame:
12808             if (appData.icsActive) return;
12809             /* else fall through */
12810           case MachinePlaysWhite:
12811           case MachinePlaysBlack:
12812           case TwoMachinesPlay:
12813             if (forwardMostMove == 0)
12814               return;           /* don't pause if no one has moved */
12815             if ((gameMode == MachinePlaysWhite &&
12816                  !WhiteOnMove(forwardMostMove)) ||
12817                 (gameMode == MachinePlaysBlack &&
12818                  WhiteOnMove(forwardMostMove))) {
12819                 StopClocks();
12820             }
12821             pausing = TRUE;
12822             ModeHighlight();
12823             break;
12824         }
12825     }
12826 }
12827
12828 void
12829 EditCommentEvent()
12830 {
12831     char title[MSG_SIZ];
12832
12833     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12834       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12835     } else {
12836       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12837                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12838                parseList[currentMove - 1]);
12839     }
12840
12841     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12842 }
12843
12844
12845 void
12846 EditTagsEvent()
12847 {
12848     char *tags = PGNTags(&gameInfo);
12849     bookUp = FALSE;
12850     EditTagsPopUp(tags, NULL);
12851     free(tags);
12852 }
12853
12854 void
12855 AnalyzeModeEvent()
12856 {
12857     if (appData.noChessProgram || gameMode == AnalyzeMode)
12858       return;
12859
12860     if (gameMode != AnalyzeFile) {
12861         if (!appData.icsEngineAnalyze) {
12862                EditGameEvent();
12863                if (gameMode != EditGame) return;
12864         }
12865         ResurrectChessProgram();
12866         SendToProgram("analyze\n", &first);
12867         first.analyzing = TRUE;
12868         /*first.maybeThinking = TRUE;*/
12869         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12870         EngineOutputPopUp();
12871     }
12872     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12873     pausing = FALSE;
12874     ModeHighlight();
12875     SetGameInfo();
12876
12877     StartAnalysisClock();
12878     GetTimeMark(&lastNodeCountTime);
12879     lastNodeCount = 0;
12880 }
12881
12882 void
12883 AnalyzeFileEvent()
12884 {
12885     if (appData.noChessProgram || gameMode == AnalyzeFile)
12886       return;
12887
12888     if (gameMode != AnalyzeMode) {
12889         EditGameEvent();
12890         if (gameMode != EditGame) return;
12891         ResurrectChessProgram();
12892         SendToProgram("analyze\n", &first);
12893         first.analyzing = TRUE;
12894         /*first.maybeThinking = TRUE;*/
12895         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12896         EngineOutputPopUp();
12897     }
12898     gameMode = AnalyzeFile;
12899     pausing = FALSE;
12900     ModeHighlight();
12901     SetGameInfo();
12902
12903     StartAnalysisClock();
12904     GetTimeMark(&lastNodeCountTime);
12905     lastNodeCount = 0;
12906     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12907 }
12908
12909 void
12910 MachineWhiteEvent()
12911 {
12912     char buf[MSG_SIZ];
12913     char *bookHit = NULL;
12914
12915     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12916       return;
12917
12918
12919     if (gameMode == PlayFromGameFile ||
12920         gameMode == TwoMachinesPlay  ||
12921         gameMode == Training         ||
12922         gameMode == AnalyzeMode      ||
12923         gameMode == EndOfGame)
12924         EditGameEvent();
12925
12926     if (gameMode == EditPosition)
12927         EditPositionDone(TRUE);
12928
12929     if (!WhiteOnMove(currentMove)) {
12930         DisplayError(_("It is not White's turn"), 0);
12931         return;
12932     }
12933
12934     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12935       ExitAnalyzeMode();
12936
12937     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12938         gameMode == AnalyzeFile)
12939         TruncateGame();
12940
12941     ResurrectChessProgram();    /* in case it isn't running */
12942     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12943         gameMode = MachinePlaysWhite;
12944         ResetClocks();
12945     } else
12946     gameMode = MachinePlaysWhite;
12947     pausing = FALSE;
12948     ModeHighlight();
12949     SetGameInfo();
12950     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12951     DisplayTitle(buf);
12952     if (first.sendName) {
12953       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12954       SendToProgram(buf, &first);
12955     }
12956     if (first.sendTime) {
12957       if (first.useColors) {
12958         SendToProgram("black\n", &first); /*gnu kludge*/
12959       }
12960       SendTimeRemaining(&first, TRUE);
12961     }
12962     if (first.useColors) {
12963       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12964     }
12965     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12966     SetMachineThinkingEnables();
12967     first.maybeThinking = TRUE;
12968     StartClocks();
12969     firstMove = FALSE;
12970
12971     if (appData.autoFlipView && !flipView) {
12972       flipView = !flipView;
12973       DrawPosition(FALSE, NULL);
12974       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12975     }
12976
12977     if(bookHit) { // [HGM] book: simulate book reply
12978         static char bookMove[MSG_SIZ]; // a bit generous?
12979
12980         programStats.nodes = programStats.depth = programStats.time =
12981         programStats.score = programStats.got_only_move = 0;
12982         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12983
12984         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12985         strcat(bookMove, bookHit);
12986         HandleMachineMove(bookMove, &first);
12987     }
12988 }
12989
12990 void
12991 MachineBlackEvent()
12992 {
12993   char buf[MSG_SIZ];
12994   char *bookHit = NULL;
12995
12996     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12997         return;
12998
12999
13000     if (gameMode == PlayFromGameFile ||
13001         gameMode == TwoMachinesPlay  ||
13002         gameMode == Training         ||
13003         gameMode == AnalyzeMode      ||
13004         gameMode == EndOfGame)
13005         EditGameEvent();
13006
13007     if (gameMode == EditPosition)
13008         EditPositionDone(TRUE);
13009
13010     if (WhiteOnMove(currentMove)) {
13011         DisplayError(_("It is not Black's turn"), 0);
13012         return;
13013     }
13014
13015     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13016       ExitAnalyzeMode();
13017
13018     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13019         gameMode == AnalyzeFile)
13020         TruncateGame();
13021
13022     ResurrectChessProgram();    /* in case it isn't running */
13023     gameMode = MachinePlaysBlack;
13024     pausing = FALSE;
13025     ModeHighlight();
13026     SetGameInfo();
13027     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13028     DisplayTitle(buf);
13029     if (first.sendName) {
13030       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13031       SendToProgram(buf, &first);
13032     }
13033     if (first.sendTime) {
13034       if (first.useColors) {
13035         SendToProgram("white\n", &first); /*gnu kludge*/
13036       }
13037       SendTimeRemaining(&first, FALSE);
13038     }
13039     if (first.useColors) {
13040       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13041     }
13042     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13043     SetMachineThinkingEnables();
13044     first.maybeThinking = TRUE;
13045     StartClocks();
13046
13047     if (appData.autoFlipView && flipView) {
13048       flipView = !flipView;
13049       DrawPosition(FALSE, NULL);
13050       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13051     }
13052     if(bookHit) { // [HGM] book: simulate book reply
13053         static char bookMove[MSG_SIZ]; // a bit generous?
13054
13055         programStats.nodes = programStats.depth = programStats.time =
13056         programStats.score = programStats.got_only_move = 0;
13057         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13058
13059         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13060         strcat(bookMove, bookHit);
13061         HandleMachineMove(bookMove, &first);
13062     }
13063 }
13064
13065
13066 void
13067 DisplayTwoMachinesTitle()
13068 {
13069     char buf[MSG_SIZ];
13070     if (appData.matchGames > 0) {
13071         if(appData.tourneyFile[0]) {
13072           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13073                    gameInfo.white, gameInfo.black,
13074                    nextGame+1, appData.matchGames+1,
13075                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13076         } else 
13077         if (first.twoMachinesColor[0] == 'w') {
13078           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13079                    gameInfo.white, gameInfo.black,
13080                    first.matchWins, second.matchWins,
13081                    matchGame - 1 - (first.matchWins + second.matchWins));
13082         } else {
13083           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13084                    gameInfo.white, gameInfo.black,
13085                    second.matchWins, first.matchWins,
13086                    matchGame - 1 - (first.matchWins + second.matchWins));
13087         }
13088     } else {
13089       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13090     }
13091     DisplayTitle(buf);
13092 }
13093
13094 void
13095 SettingsMenuIfReady()
13096 {
13097   if (second.lastPing != second.lastPong) {
13098     DisplayMessage("", _("Waiting for second chess program"));
13099     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13100     return;
13101   }
13102   ThawUI();
13103   DisplayMessage("", "");
13104   SettingsPopUp(&second);
13105 }
13106
13107 int
13108 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13109 {
13110     char buf[MSG_SIZ];
13111     if (cps->pr == NULL) {
13112         StartChessProgram(cps);
13113         if (cps->protocolVersion == 1) {
13114           retry();
13115         } else {
13116           /* kludge: allow timeout for initial "feature" command */
13117           FreezeUI();
13118           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13119           DisplayMessage("", buf);
13120           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13121         }
13122         return 1;
13123     }
13124     return 0;
13125 }
13126
13127 void
13128 TwoMachinesEvent P((void))
13129 {
13130     int i;
13131     char buf[MSG_SIZ];
13132     ChessProgramState *onmove;
13133     char *bookHit = NULL;
13134     static int stalling = 0;
13135     TimeMark now;
13136     long wait;
13137
13138     if (appData.noChessProgram) return;
13139
13140     switch (gameMode) {
13141       case TwoMachinesPlay:
13142         return;
13143       case MachinePlaysWhite:
13144       case MachinePlaysBlack:
13145         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13146             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13147             return;
13148         }
13149         /* fall through */
13150       case BeginningOfGame:
13151       case PlayFromGameFile:
13152       case EndOfGame:
13153         EditGameEvent();
13154         if (gameMode != EditGame) return;
13155         break;
13156       case EditPosition:
13157         EditPositionDone(TRUE);
13158         break;
13159       case AnalyzeMode:
13160       case AnalyzeFile:
13161         ExitAnalyzeMode();
13162         break;
13163       case EditGame:
13164       default:
13165         break;
13166     }
13167
13168 //    forwardMostMove = currentMove;
13169     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13170
13171     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13172
13173     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13174     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13175       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13176       return;
13177     }
13178     if(!stalling) {
13179       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13180       SendToProgram("force\n", &second);
13181       stalling = 1;
13182       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13183       return;
13184     }
13185     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13186     if(appData.matchPause>10000 || appData.matchPause<10)
13187                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13188     wait = SubtractTimeMarks(&now, &pauseStart);
13189     if(wait < appData.matchPause) {
13190         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13191         return;
13192     }
13193     stalling = 0;
13194     DisplayMessage("", "");
13195     if (startedFromSetupPosition) {
13196         SendBoard(&second, backwardMostMove);
13197     if (appData.debugMode) {
13198         fprintf(debugFP, "Two Machines\n");
13199     }
13200     }
13201     for (i = backwardMostMove; i < forwardMostMove; i++) {
13202         SendMoveToProgram(i, &second);
13203     }
13204
13205     gameMode = TwoMachinesPlay;
13206     pausing = FALSE;
13207     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13208     SetGameInfo();
13209     DisplayTwoMachinesTitle();
13210     firstMove = TRUE;
13211     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13212         onmove = &first;
13213     } else {
13214         onmove = &second;
13215     }
13216     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13217     SendToProgram(first.computerString, &first);
13218     if (first.sendName) {
13219       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13220       SendToProgram(buf, &first);
13221     }
13222     SendToProgram(second.computerString, &second);
13223     if (second.sendName) {
13224       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13225       SendToProgram(buf, &second);
13226     }
13227
13228     ResetClocks();
13229     if (!first.sendTime || !second.sendTime) {
13230         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13231         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13232     }
13233     if (onmove->sendTime) {
13234       if (onmove->useColors) {
13235         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13236       }
13237       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13238     }
13239     if (onmove->useColors) {
13240       SendToProgram(onmove->twoMachinesColor, onmove);
13241     }
13242     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13243 //    SendToProgram("go\n", onmove);
13244     onmove->maybeThinking = TRUE;
13245     SetMachineThinkingEnables();
13246
13247     StartClocks();
13248
13249     if(bookHit) { // [HGM] book: simulate book reply
13250         static char bookMove[MSG_SIZ]; // a bit generous?
13251
13252         programStats.nodes = programStats.depth = programStats.time =
13253         programStats.score = programStats.got_only_move = 0;
13254         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13255
13256         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13257         strcat(bookMove, bookHit);
13258         savedMessage = bookMove; // args for deferred call
13259         savedState = onmove;
13260         ScheduleDelayedEvent(DeferredBookMove, 1);
13261     }
13262 }
13263
13264 void
13265 TrainingEvent()
13266 {
13267     if (gameMode == Training) {
13268       SetTrainingModeOff();
13269       gameMode = PlayFromGameFile;
13270       DisplayMessage("", _("Training mode off"));
13271     } else {
13272       gameMode = Training;
13273       animateTraining = appData.animate;
13274
13275       /* make sure we are not already at the end of the game */
13276       if (currentMove < forwardMostMove) {
13277         SetTrainingModeOn();
13278         DisplayMessage("", _("Training mode on"));
13279       } else {
13280         gameMode = PlayFromGameFile;
13281         DisplayError(_("Already at end of game"), 0);
13282       }
13283     }
13284     ModeHighlight();
13285 }
13286
13287 void
13288 IcsClientEvent()
13289 {
13290     if (!appData.icsActive) return;
13291     switch (gameMode) {
13292       case IcsPlayingWhite:
13293       case IcsPlayingBlack:
13294       case IcsObserving:
13295       case IcsIdle:
13296       case BeginningOfGame:
13297       case IcsExamining:
13298         return;
13299
13300       case EditGame:
13301         break;
13302
13303       case EditPosition:
13304         EditPositionDone(TRUE);
13305         break;
13306
13307       case AnalyzeMode:
13308       case AnalyzeFile:
13309         ExitAnalyzeMode();
13310         break;
13311
13312       default:
13313         EditGameEvent();
13314         break;
13315     }
13316
13317     gameMode = IcsIdle;
13318     ModeHighlight();
13319     return;
13320 }
13321
13322
13323 void
13324 EditGameEvent()
13325 {
13326     int i;
13327
13328     switch (gameMode) {
13329       case Training:
13330         SetTrainingModeOff();
13331         break;
13332       case MachinePlaysWhite:
13333       case MachinePlaysBlack:
13334       case BeginningOfGame:
13335         SendToProgram("force\n", &first);
13336         SetUserThinkingEnables();
13337         break;
13338       case PlayFromGameFile:
13339         (void) StopLoadGameTimer();
13340         if (gameFileFP != NULL) {
13341             gameFileFP = NULL;
13342         }
13343         break;
13344       case EditPosition:
13345         EditPositionDone(TRUE);
13346         break;
13347       case AnalyzeMode:
13348       case AnalyzeFile:
13349         ExitAnalyzeMode();
13350         SendToProgram("force\n", &first);
13351         break;
13352       case TwoMachinesPlay:
13353         GameEnds(EndOfFile, NULL, GE_PLAYER);
13354         ResurrectChessProgram();
13355         SetUserThinkingEnables();
13356         break;
13357       case EndOfGame:
13358         ResurrectChessProgram();
13359         break;
13360       case IcsPlayingBlack:
13361       case IcsPlayingWhite:
13362         DisplayError(_("Warning: You are still playing a game"), 0);
13363         break;
13364       case IcsObserving:
13365         DisplayError(_("Warning: You are still observing a game"), 0);
13366         break;
13367       case IcsExamining:
13368         DisplayError(_("Warning: You are still examining a game"), 0);
13369         break;
13370       case IcsIdle:
13371         break;
13372       case EditGame:
13373       default:
13374         return;
13375     }
13376
13377     pausing = FALSE;
13378     StopClocks();
13379     first.offeredDraw = second.offeredDraw = 0;
13380
13381     if (gameMode == PlayFromGameFile) {
13382         whiteTimeRemaining = timeRemaining[0][currentMove];
13383         blackTimeRemaining = timeRemaining[1][currentMove];
13384         DisplayTitle("");
13385     }
13386
13387     if (gameMode == MachinePlaysWhite ||
13388         gameMode == MachinePlaysBlack ||
13389         gameMode == TwoMachinesPlay ||
13390         gameMode == EndOfGame) {
13391         i = forwardMostMove;
13392         while (i > currentMove) {
13393             SendToProgram("undo\n", &first);
13394             i--;
13395         }
13396         whiteTimeRemaining = timeRemaining[0][currentMove];
13397         blackTimeRemaining = timeRemaining[1][currentMove];
13398         DisplayBothClocks();
13399         if (whiteFlag || blackFlag) {
13400             whiteFlag = blackFlag = 0;
13401         }
13402         DisplayTitle("");
13403     }
13404
13405     gameMode = EditGame;
13406     ModeHighlight();
13407     SetGameInfo();
13408 }
13409
13410
13411 void
13412 EditPositionEvent()
13413 {
13414     if (gameMode == EditPosition) {
13415         EditGameEvent();
13416         return;
13417     }
13418
13419     EditGameEvent();
13420     if (gameMode != EditGame) return;
13421
13422     gameMode = EditPosition;
13423     ModeHighlight();
13424     SetGameInfo();
13425     if (currentMove > 0)
13426       CopyBoard(boards[0], boards[currentMove]);
13427
13428     blackPlaysFirst = !WhiteOnMove(currentMove);
13429     ResetClocks();
13430     currentMove = forwardMostMove = backwardMostMove = 0;
13431     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13432     DisplayMove(-1);
13433 }
13434
13435 void
13436 ExitAnalyzeMode()
13437 {
13438     /* [DM] icsEngineAnalyze - possible call from other functions */
13439     if (appData.icsEngineAnalyze) {
13440         appData.icsEngineAnalyze = FALSE;
13441
13442         DisplayMessage("",_("Close ICS engine analyze..."));
13443     }
13444     if (first.analysisSupport && first.analyzing) {
13445       SendToProgram("exit\n", &first);
13446       first.analyzing = FALSE;
13447     }
13448     thinkOutput[0] = NULLCHAR;
13449 }
13450
13451 void
13452 EditPositionDone(Boolean fakeRights)
13453 {
13454     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13455
13456     startedFromSetupPosition = TRUE;
13457     InitChessProgram(&first, FALSE);
13458     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13459       boards[0][EP_STATUS] = EP_NONE;
13460       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13461     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13462         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13463         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13464       } else boards[0][CASTLING][2] = NoRights;
13465     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13466         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13467         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13468       } else boards[0][CASTLING][5] = NoRights;
13469     }
13470     SendToProgram("force\n", &first);
13471     if (blackPlaysFirst) {
13472         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13473         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13474         currentMove = forwardMostMove = backwardMostMove = 1;
13475         CopyBoard(boards[1], boards[0]);
13476     } else {
13477         currentMove = forwardMostMove = backwardMostMove = 0;
13478     }
13479     SendBoard(&first, forwardMostMove);
13480     if (appData.debugMode) {
13481         fprintf(debugFP, "EditPosDone\n");
13482     }
13483     DisplayTitle("");
13484     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13485     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13486     gameMode = EditGame;
13487     ModeHighlight();
13488     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13489     ClearHighlights(); /* [AS] */
13490 }
13491
13492 /* Pause for `ms' milliseconds */
13493 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13494 void
13495 TimeDelay(ms)
13496      long ms;
13497 {
13498     TimeMark m1, m2;
13499
13500     GetTimeMark(&m1);
13501     do {
13502         GetTimeMark(&m2);
13503     } while (SubtractTimeMarks(&m2, &m1) < ms);
13504 }
13505
13506 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13507 void
13508 SendMultiLineToICS(buf)
13509      char *buf;
13510 {
13511     char temp[MSG_SIZ+1], *p;
13512     int len;
13513
13514     len = strlen(buf);
13515     if (len > MSG_SIZ)
13516       len = MSG_SIZ;
13517
13518     strncpy(temp, buf, len);
13519     temp[len] = 0;
13520
13521     p = temp;
13522     while (*p) {
13523         if (*p == '\n' || *p == '\r')
13524           *p = ' ';
13525         ++p;
13526     }
13527
13528     strcat(temp, "\n");
13529     SendToICS(temp);
13530     SendToPlayer(temp, strlen(temp));
13531 }
13532
13533 void
13534 SetWhiteToPlayEvent()
13535 {
13536     if (gameMode == EditPosition) {
13537         blackPlaysFirst = FALSE;
13538         DisplayBothClocks();    /* works because currentMove is 0 */
13539     } else if (gameMode == IcsExamining) {
13540         SendToICS(ics_prefix);
13541         SendToICS("tomove white\n");
13542     }
13543 }
13544
13545 void
13546 SetBlackToPlayEvent()
13547 {
13548     if (gameMode == EditPosition) {
13549         blackPlaysFirst = TRUE;
13550         currentMove = 1;        /* kludge */
13551         DisplayBothClocks();
13552         currentMove = 0;
13553     } else if (gameMode == IcsExamining) {
13554         SendToICS(ics_prefix);
13555         SendToICS("tomove black\n");
13556     }
13557 }
13558
13559 void
13560 EditPositionMenuEvent(selection, x, y)
13561      ChessSquare selection;
13562      int x, y;
13563 {
13564     char buf[MSG_SIZ];
13565     ChessSquare piece = boards[0][y][x];
13566
13567     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13568
13569     switch (selection) {
13570       case ClearBoard:
13571         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13572             SendToICS(ics_prefix);
13573             SendToICS("bsetup clear\n");
13574         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13575             SendToICS(ics_prefix);
13576             SendToICS("clearboard\n");
13577         } else {
13578             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13579                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13580                 for (y = 0; y < BOARD_HEIGHT; y++) {
13581                     if (gameMode == IcsExamining) {
13582                         if (boards[currentMove][y][x] != EmptySquare) {
13583                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13584                                     AAA + x, ONE + y);
13585                             SendToICS(buf);
13586                         }
13587                     } else {
13588                         boards[0][y][x] = p;
13589                     }
13590                 }
13591             }
13592         }
13593         if (gameMode == EditPosition) {
13594             DrawPosition(FALSE, boards[0]);
13595         }
13596         break;
13597
13598       case WhitePlay:
13599         SetWhiteToPlayEvent();
13600         break;
13601
13602       case BlackPlay:
13603         SetBlackToPlayEvent();
13604         break;
13605
13606       case EmptySquare:
13607         if (gameMode == IcsExamining) {
13608             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13609             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13610             SendToICS(buf);
13611         } else {
13612             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13613                 if(x == BOARD_LEFT-2) {
13614                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13615                     boards[0][y][1] = 0;
13616                 } else
13617                 if(x == BOARD_RGHT+1) {
13618                     if(y >= gameInfo.holdingsSize) break;
13619                     boards[0][y][BOARD_WIDTH-2] = 0;
13620                 } else break;
13621             }
13622             boards[0][y][x] = EmptySquare;
13623             DrawPosition(FALSE, boards[0]);
13624         }
13625         break;
13626
13627       case PromotePiece:
13628         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13629            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13630             selection = (ChessSquare) (PROMOTED piece);
13631         } else if(piece == EmptySquare) selection = WhiteSilver;
13632         else selection = (ChessSquare)((int)piece - 1);
13633         goto defaultlabel;
13634
13635       case DemotePiece:
13636         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13637            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13638             selection = (ChessSquare) (DEMOTED piece);
13639         } else if(piece == EmptySquare) selection = BlackSilver;
13640         else selection = (ChessSquare)((int)piece + 1);
13641         goto defaultlabel;
13642
13643       case WhiteQueen:
13644       case BlackQueen:
13645         if(gameInfo.variant == VariantShatranj ||
13646            gameInfo.variant == VariantXiangqi  ||
13647            gameInfo.variant == VariantCourier  ||
13648            gameInfo.variant == VariantMakruk     )
13649             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13650         goto defaultlabel;
13651
13652       case WhiteKing:
13653       case BlackKing:
13654         if(gameInfo.variant == VariantXiangqi)
13655             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13656         if(gameInfo.variant == VariantKnightmate)
13657             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13658       default:
13659         defaultlabel:
13660         if (gameMode == IcsExamining) {
13661             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13662             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13663                      PieceToChar(selection), AAA + x, ONE + y);
13664             SendToICS(buf);
13665         } else {
13666             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13667                 int n;
13668                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13669                     n = PieceToNumber(selection - BlackPawn);
13670                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13671                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13672                     boards[0][BOARD_HEIGHT-1-n][1]++;
13673                 } else
13674                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13675                     n = PieceToNumber(selection);
13676                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13677                     boards[0][n][BOARD_WIDTH-1] = selection;
13678                     boards[0][n][BOARD_WIDTH-2]++;
13679                 }
13680             } else
13681             boards[0][y][x] = selection;
13682             DrawPosition(TRUE, boards[0]);
13683         }
13684         break;
13685     }
13686 }
13687
13688
13689 void
13690 DropMenuEvent(selection, x, y)
13691      ChessSquare selection;
13692      int x, y;
13693 {
13694     ChessMove moveType;
13695
13696     switch (gameMode) {
13697       case IcsPlayingWhite:
13698       case MachinePlaysBlack:
13699         if (!WhiteOnMove(currentMove)) {
13700             DisplayMoveError(_("It is Black's turn"));
13701             return;
13702         }
13703         moveType = WhiteDrop;
13704         break;
13705       case IcsPlayingBlack:
13706       case MachinePlaysWhite:
13707         if (WhiteOnMove(currentMove)) {
13708             DisplayMoveError(_("It is White's turn"));
13709             return;
13710         }
13711         moveType = BlackDrop;
13712         break;
13713       case EditGame:
13714         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13715         break;
13716       default:
13717         return;
13718     }
13719
13720     if (moveType == BlackDrop && selection < BlackPawn) {
13721       selection = (ChessSquare) ((int) selection
13722                                  + (int) BlackPawn - (int) WhitePawn);
13723     }
13724     if (boards[currentMove][y][x] != EmptySquare) {
13725         DisplayMoveError(_("That square is occupied"));
13726         return;
13727     }
13728
13729     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13730 }
13731
13732 void
13733 AcceptEvent()
13734 {
13735     /* Accept a pending offer of any kind from opponent */
13736
13737     if (appData.icsActive) {
13738         SendToICS(ics_prefix);
13739         SendToICS("accept\n");
13740     } else if (cmailMsgLoaded) {
13741         if (currentMove == cmailOldMove &&
13742             commentList[cmailOldMove] != NULL &&
13743             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13744                    "Black offers a draw" : "White offers a draw")) {
13745             TruncateGame();
13746             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13747             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13748         } else {
13749             DisplayError(_("There is no pending offer on this move"), 0);
13750             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13751         }
13752     } else {
13753         /* Not used for offers from chess program */
13754     }
13755 }
13756
13757 void
13758 DeclineEvent()
13759 {
13760     /* Decline a pending offer of any kind from opponent */
13761
13762     if (appData.icsActive) {
13763         SendToICS(ics_prefix);
13764         SendToICS("decline\n");
13765     } else if (cmailMsgLoaded) {
13766         if (currentMove == cmailOldMove &&
13767             commentList[cmailOldMove] != NULL &&
13768             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13769                    "Black offers a draw" : "White offers a draw")) {
13770 #ifdef NOTDEF
13771             AppendComment(cmailOldMove, "Draw declined", TRUE);
13772             DisplayComment(cmailOldMove - 1, "Draw declined");
13773 #endif /*NOTDEF*/
13774         } else {
13775             DisplayError(_("There is no pending offer on this move"), 0);
13776         }
13777     } else {
13778         /* Not used for offers from chess program */
13779     }
13780 }
13781
13782 void
13783 RematchEvent()
13784 {
13785     /* Issue ICS rematch command */
13786     if (appData.icsActive) {
13787         SendToICS(ics_prefix);
13788         SendToICS("rematch\n");
13789     }
13790 }
13791
13792 void
13793 CallFlagEvent()
13794 {
13795     /* Call your opponent's flag (claim a win on time) */
13796     if (appData.icsActive) {
13797         SendToICS(ics_prefix);
13798         SendToICS("flag\n");
13799     } else {
13800         switch (gameMode) {
13801           default:
13802             return;
13803           case MachinePlaysWhite:
13804             if (whiteFlag) {
13805                 if (blackFlag)
13806                   GameEnds(GameIsDrawn, "Both players ran out of time",
13807                            GE_PLAYER);
13808                 else
13809                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13810             } else {
13811                 DisplayError(_("Your opponent is not out of time"), 0);
13812             }
13813             break;
13814           case MachinePlaysBlack:
13815             if (blackFlag) {
13816                 if (whiteFlag)
13817                   GameEnds(GameIsDrawn, "Both players ran out of time",
13818                            GE_PLAYER);
13819                 else
13820                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13821             } else {
13822                 DisplayError(_("Your opponent is not out of time"), 0);
13823             }
13824             break;
13825         }
13826     }
13827 }
13828
13829 void
13830 ClockClick(int which)
13831 {       // [HGM] code moved to back-end from winboard.c
13832         if(which) { // black clock
13833           if (gameMode == EditPosition || gameMode == IcsExamining) {
13834             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13835             SetBlackToPlayEvent();
13836           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13837           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13838           } else if (shiftKey) {
13839             AdjustClock(which, -1);
13840           } else if (gameMode == IcsPlayingWhite ||
13841                      gameMode == MachinePlaysBlack) {
13842             CallFlagEvent();
13843           }
13844         } else { // white clock
13845           if (gameMode == EditPosition || gameMode == IcsExamining) {
13846             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13847             SetWhiteToPlayEvent();
13848           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13849           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13850           } else if (shiftKey) {
13851             AdjustClock(which, -1);
13852           } else if (gameMode == IcsPlayingBlack ||
13853                    gameMode == MachinePlaysWhite) {
13854             CallFlagEvent();
13855           }
13856         }
13857 }
13858
13859 void
13860 DrawEvent()
13861 {
13862     /* Offer draw or accept pending draw offer from opponent */
13863
13864     if (appData.icsActive) {
13865         /* Note: tournament rules require draw offers to be
13866            made after you make your move but before you punch
13867            your clock.  Currently ICS doesn't let you do that;
13868            instead, you immediately punch your clock after making
13869            a move, but you can offer a draw at any time. */
13870
13871         SendToICS(ics_prefix);
13872         SendToICS("draw\n");
13873         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13874     } else if (cmailMsgLoaded) {
13875         if (currentMove == cmailOldMove &&
13876             commentList[cmailOldMove] != NULL &&
13877             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13878                    "Black offers a draw" : "White offers a draw")) {
13879             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13880             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13881         } else if (currentMove == cmailOldMove + 1) {
13882             char *offer = WhiteOnMove(cmailOldMove) ?
13883               "White offers a draw" : "Black offers a draw";
13884             AppendComment(currentMove, offer, TRUE);
13885             DisplayComment(currentMove - 1, offer);
13886             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13887         } else {
13888             DisplayError(_("You must make your move before offering a draw"), 0);
13889             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13890         }
13891     } else if (first.offeredDraw) {
13892         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13893     } else {
13894         if (first.sendDrawOffers) {
13895             SendToProgram("draw\n", &first);
13896             userOfferedDraw = TRUE;
13897         }
13898     }
13899 }
13900
13901 void
13902 AdjournEvent()
13903 {
13904     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13905
13906     if (appData.icsActive) {
13907         SendToICS(ics_prefix);
13908         SendToICS("adjourn\n");
13909     } else {
13910         /* Currently GNU Chess doesn't offer or accept Adjourns */
13911     }
13912 }
13913
13914
13915 void
13916 AbortEvent()
13917 {
13918     /* Offer Abort or accept pending Abort offer from opponent */
13919
13920     if (appData.icsActive) {
13921         SendToICS(ics_prefix);
13922         SendToICS("abort\n");
13923     } else {
13924         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13925     }
13926 }
13927
13928 void
13929 ResignEvent()
13930 {
13931     /* Resign.  You can do this even if it's not your turn. */
13932
13933     if (appData.icsActive) {
13934         SendToICS(ics_prefix);
13935         SendToICS("resign\n");
13936     } else {
13937         switch (gameMode) {
13938           case MachinePlaysWhite:
13939             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13940             break;
13941           case MachinePlaysBlack:
13942             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13943             break;
13944           case EditGame:
13945             if (cmailMsgLoaded) {
13946                 TruncateGame();
13947                 if (WhiteOnMove(cmailOldMove)) {
13948                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13949                 } else {
13950                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13951                 }
13952                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13953             }
13954             break;
13955           default:
13956             break;
13957         }
13958     }
13959 }
13960
13961
13962 void
13963 StopObservingEvent()
13964 {
13965     /* Stop observing current games */
13966     SendToICS(ics_prefix);
13967     SendToICS("unobserve\n");
13968 }
13969
13970 void
13971 StopExaminingEvent()
13972 {
13973     /* Stop observing current game */
13974     SendToICS(ics_prefix);
13975     SendToICS("unexamine\n");
13976 }
13977
13978 void
13979 ForwardInner(target)
13980      int target;
13981 {
13982     int limit;
13983
13984     if (appData.debugMode)
13985         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13986                 target, currentMove, forwardMostMove);
13987
13988     if (gameMode == EditPosition)
13989       return;
13990
13991     if (gameMode == PlayFromGameFile && !pausing)
13992       PauseEvent();
13993
13994     if (gameMode == IcsExamining && pausing)
13995       limit = pauseExamForwardMostMove;
13996     else
13997       limit = forwardMostMove;
13998
13999     if (target > limit) target = limit;
14000
14001     if (target > 0 && moveList[target - 1][0]) {
14002         int fromX, fromY, toX, toY;
14003         toX = moveList[target - 1][2] - AAA;
14004         toY = moveList[target - 1][3] - ONE;
14005         if (moveList[target - 1][1] == '@') {
14006             if (appData.highlightLastMove) {
14007                 SetHighlights(-1, -1, toX, toY);
14008             }
14009         } else {
14010             fromX = moveList[target - 1][0] - AAA;
14011             fromY = moveList[target - 1][1] - ONE;
14012             if (target == currentMove + 1) {
14013                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14014             }
14015             if (appData.highlightLastMove) {
14016                 SetHighlights(fromX, fromY, toX, toY);
14017             }
14018         }
14019     }
14020     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14021         gameMode == Training || gameMode == PlayFromGameFile ||
14022         gameMode == AnalyzeFile) {
14023         while (currentMove < target) {
14024             SendMoveToProgram(currentMove++, &first);
14025         }
14026     } else {
14027         currentMove = target;
14028     }
14029
14030     if (gameMode == EditGame || gameMode == EndOfGame) {
14031         whiteTimeRemaining = timeRemaining[0][currentMove];
14032         blackTimeRemaining = timeRemaining[1][currentMove];
14033     }
14034     DisplayBothClocks();
14035     DisplayMove(currentMove - 1);
14036     DrawPosition(FALSE, boards[currentMove]);
14037     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14038     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14039         DisplayComment(currentMove - 1, commentList[currentMove]);
14040     }
14041     DisplayBook(currentMove);
14042 }
14043
14044
14045 void
14046 ForwardEvent()
14047 {
14048     if (gameMode == IcsExamining && !pausing) {
14049         SendToICS(ics_prefix);
14050         SendToICS("forward\n");
14051     } else {
14052         ForwardInner(currentMove + 1);
14053     }
14054 }
14055
14056 void
14057 ToEndEvent()
14058 {
14059     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14060         /* to optimze, we temporarily turn off analysis mode while we feed
14061          * the remaining moves to the engine. Otherwise we get analysis output
14062          * after each move.
14063          */
14064         if (first.analysisSupport) {
14065           SendToProgram("exit\nforce\n", &first);
14066           first.analyzing = FALSE;
14067         }
14068     }
14069
14070     if (gameMode == IcsExamining && !pausing) {
14071         SendToICS(ics_prefix);
14072         SendToICS("forward 999999\n");
14073     } else {
14074         ForwardInner(forwardMostMove);
14075     }
14076
14077     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14078         /* we have fed all the moves, so reactivate analysis mode */
14079         SendToProgram("analyze\n", &first);
14080         first.analyzing = TRUE;
14081         /*first.maybeThinking = TRUE;*/
14082         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14083     }
14084 }
14085
14086 void
14087 BackwardInner(target)
14088      int target;
14089 {
14090     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14091
14092     if (appData.debugMode)
14093         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14094                 target, currentMove, forwardMostMove);
14095
14096     if (gameMode == EditPosition) return;
14097     if (currentMove <= backwardMostMove) {
14098         ClearHighlights();
14099         DrawPosition(full_redraw, boards[currentMove]);
14100         return;
14101     }
14102     if (gameMode == PlayFromGameFile && !pausing)
14103       PauseEvent();
14104
14105     if (moveList[target][0]) {
14106         int fromX, fromY, toX, toY;
14107         toX = moveList[target][2] - AAA;
14108         toY = moveList[target][3] - ONE;
14109         if (moveList[target][1] == '@') {
14110             if (appData.highlightLastMove) {
14111                 SetHighlights(-1, -1, toX, toY);
14112             }
14113         } else {
14114             fromX = moveList[target][0] - AAA;
14115             fromY = moveList[target][1] - ONE;
14116             if (target == currentMove - 1) {
14117                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14118             }
14119             if (appData.highlightLastMove) {
14120                 SetHighlights(fromX, fromY, toX, toY);
14121             }
14122         }
14123     }
14124     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14125         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14126         while (currentMove > target) {
14127             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14128                 // null move cannot be undone. Reload program with move history before it.
14129                 int i;
14130                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14131                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14132                 }
14133                 SendBoard(&first, i); 
14134                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14135                 break;
14136             }
14137             SendToProgram("undo\n", &first);
14138             currentMove--;
14139         }
14140     } else {
14141         currentMove = target;
14142     }
14143
14144     if (gameMode == EditGame || gameMode == EndOfGame) {
14145         whiteTimeRemaining = timeRemaining[0][currentMove];
14146         blackTimeRemaining = timeRemaining[1][currentMove];
14147     }
14148     DisplayBothClocks();
14149     DisplayMove(currentMove - 1);
14150     DrawPosition(full_redraw, boards[currentMove]);
14151     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14152     // [HGM] PV info: routine tests if comment empty
14153     DisplayComment(currentMove - 1, commentList[currentMove]);
14154     DisplayBook(currentMove);
14155 }
14156
14157 void
14158 BackwardEvent()
14159 {
14160     if (gameMode == IcsExamining && !pausing) {
14161         SendToICS(ics_prefix);
14162         SendToICS("backward\n");
14163     } else {
14164         BackwardInner(currentMove - 1);
14165     }
14166 }
14167
14168 void
14169 ToStartEvent()
14170 {
14171     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14172         /* to optimize, we temporarily turn off analysis mode while we undo
14173          * all the moves. Otherwise we get analysis output after each undo.
14174          */
14175         if (first.analysisSupport) {
14176           SendToProgram("exit\nforce\n", &first);
14177           first.analyzing = FALSE;
14178         }
14179     }
14180
14181     if (gameMode == IcsExamining && !pausing) {
14182         SendToICS(ics_prefix);
14183         SendToICS("backward 999999\n");
14184     } else {
14185         BackwardInner(backwardMostMove);
14186     }
14187
14188     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14189         /* we have fed all the moves, so reactivate analysis mode */
14190         SendToProgram("analyze\n", &first);
14191         first.analyzing = TRUE;
14192         /*first.maybeThinking = TRUE;*/
14193         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14194     }
14195 }
14196
14197 void
14198 ToNrEvent(int to)
14199 {
14200   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14201   if (to >= forwardMostMove) to = forwardMostMove;
14202   if (to <= backwardMostMove) to = backwardMostMove;
14203   if (to < currentMove) {
14204     BackwardInner(to);
14205   } else {
14206     ForwardInner(to);
14207   }
14208 }
14209
14210 void
14211 RevertEvent(Boolean annotate)
14212 {
14213     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14214         return;
14215     }
14216     if (gameMode != IcsExamining) {
14217         DisplayError(_("You are not examining a game"), 0);
14218         return;
14219     }
14220     if (pausing) {
14221         DisplayError(_("You can't revert while pausing"), 0);
14222         return;
14223     }
14224     SendToICS(ics_prefix);
14225     SendToICS("revert\n");
14226 }
14227
14228 void
14229 RetractMoveEvent()
14230 {
14231     switch (gameMode) {
14232       case MachinePlaysWhite:
14233       case MachinePlaysBlack:
14234         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14235             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14236             return;
14237         }
14238         if (forwardMostMove < 2) return;
14239         currentMove = forwardMostMove = forwardMostMove - 2;
14240         whiteTimeRemaining = timeRemaining[0][currentMove];
14241         blackTimeRemaining = timeRemaining[1][currentMove];
14242         DisplayBothClocks();
14243         DisplayMove(currentMove - 1);
14244         ClearHighlights();/*!! could figure this out*/
14245         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14246         SendToProgram("remove\n", &first);
14247         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14248         break;
14249
14250       case BeginningOfGame:
14251       default:
14252         break;
14253
14254       case IcsPlayingWhite:
14255       case IcsPlayingBlack:
14256         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14257             SendToICS(ics_prefix);
14258             SendToICS("takeback 2\n");
14259         } else {
14260             SendToICS(ics_prefix);
14261             SendToICS("takeback 1\n");
14262         }
14263         break;
14264     }
14265 }
14266
14267 void
14268 MoveNowEvent()
14269 {
14270     ChessProgramState *cps;
14271
14272     switch (gameMode) {
14273       case MachinePlaysWhite:
14274         if (!WhiteOnMove(forwardMostMove)) {
14275             DisplayError(_("It is your turn"), 0);
14276             return;
14277         }
14278         cps = &first;
14279         break;
14280       case MachinePlaysBlack:
14281         if (WhiteOnMove(forwardMostMove)) {
14282             DisplayError(_("It is your turn"), 0);
14283             return;
14284         }
14285         cps = &first;
14286         break;
14287       case TwoMachinesPlay:
14288         if (WhiteOnMove(forwardMostMove) ==
14289             (first.twoMachinesColor[0] == 'w')) {
14290             cps = &first;
14291         } else {
14292             cps = &second;
14293         }
14294         break;
14295       case BeginningOfGame:
14296       default:
14297         return;
14298     }
14299     SendToProgram("?\n", cps);
14300 }
14301
14302 void
14303 TruncateGameEvent()
14304 {
14305     EditGameEvent();
14306     if (gameMode != EditGame) return;
14307     TruncateGame();
14308 }
14309
14310 void
14311 TruncateGame()
14312 {
14313     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14314     if (forwardMostMove > currentMove) {
14315         if (gameInfo.resultDetails != NULL) {
14316             free(gameInfo.resultDetails);
14317             gameInfo.resultDetails = NULL;
14318             gameInfo.result = GameUnfinished;
14319         }
14320         forwardMostMove = currentMove;
14321         HistorySet(parseList, backwardMostMove, forwardMostMove,
14322                    currentMove-1);
14323     }
14324 }
14325
14326 void
14327 HintEvent()
14328 {
14329     if (appData.noChessProgram) return;
14330     switch (gameMode) {
14331       case MachinePlaysWhite:
14332         if (WhiteOnMove(forwardMostMove)) {
14333             DisplayError(_("Wait until your turn"), 0);
14334             return;
14335         }
14336         break;
14337       case BeginningOfGame:
14338       case MachinePlaysBlack:
14339         if (!WhiteOnMove(forwardMostMove)) {
14340             DisplayError(_("Wait until your turn"), 0);
14341             return;
14342         }
14343         break;
14344       default:
14345         DisplayError(_("No hint available"), 0);
14346         return;
14347     }
14348     SendToProgram("hint\n", &first);
14349     hintRequested = TRUE;
14350 }
14351
14352 void
14353 BookEvent()
14354 {
14355     if (appData.noChessProgram) return;
14356     switch (gameMode) {
14357       case MachinePlaysWhite:
14358         if (WhiteOnMove(forwardMostMove)) {
14359             DisplayError(_("Wait until your turn"), 0);
14360             return;
14361         }
14362         break;
14363       case BeginningOfGame:
14364       case MachinePlaysBlack:
14365         if (!WhiteOnMove(forwardMostMove)) {
14366             DisplayError(_("Wait until your turn"), 0);
14367             return;
14368         }
14369         break;
14370       case EditPosition:
14371         EditPositionDone(TRUE);
14372         break;
14373       case TwoMachinesPlay:
14374         return;
14375       default:
14376         break;
14377     }
14378     SendToProgram("bk\n", &first);
14379     bookOutput[0] = NULLCHAR;
14380     bookRequested = TRUE;
14381 }
14382
14383 void
14384 AboutGameEvent()
14385 {
14386     char *tags = PGNTags(&gameInfo);
14387     TagsPopUp(tags, CmailMsg());
14388     free(tags);
14389 }
14390
14391 /* end button procedures */
14392
14393 void
14394 PrintPosition(fp, move)
14395      FILE *fp;
14396      int move;
14397 {
14398     int i, j;
14399
14400     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14401         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14402             char c = PieceToChar(boards[move][i][j]);
14403             fputc(c == 'x' ? '.' : c, fp);
14404             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14405         }
14406     }
14407     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14408       fprintf(fp, "white to play\n");
14409     else
14410       fprintf(fp, "black to play\n");
14411 }
14412
14413 void
14414 PrintOpponents(fp)
14415      FILE *fp;
14416 {
14417     if (gameInfo.white != NULL) {
14418         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14419     } else {
14420         fprintf(fp, "\n");
14421     }
14422 }
14423
14424 /* Find last component of program's own name, using some heuristics */
14425 void
14426 TidyProgramName(prog, host, buf)
14427      char *prog, *host, buf[MSG_SIZ];
14428 {
14429     char *p, *q;
14430     int local = (strcmp(host, "localhost") == 0);
14431     while (!local && (p = strchr(prog, ';')) != NULL) {
14432         p++;
14433         while (*p == ' ') p++;
14434         prog = p;
14435     }
14436     if (*prog == '"' || *prog == '\'') {
14437         q = strchr(prog + 1, *prog);
14438     } else {
14439         q = strchr(prog, ' ');
14440     }
14441     if (q == NULL) q = prog + strlen(prog);
14442     p = q;
14443     while (p >= prog && *p != '/' && *p != '\\') p--;
14444     p++;
14445     if(p == prog && *p == '"') p++;
14446     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14447     memcpy(buf, p, q - p);
14448     buf[q - p] = NULLCHAR;
14449     if (!local) {
14450         strcat(buf, "@");
14451         strcat(buf, host);
14452     }
14453 }
14454
14455 char *
14456 TimeControlTagValue()
14457 {
14458     char buf[MSG_SIZ];
14459     if (!appData.clockMode) {
14460       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14461     } else if (movesPerSession > 0) {
14462       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14463     } else if (timeIncrement == 0) {
14464       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14465     } else {
14466       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14467     }
14468     return StrSave(buf);
14469 }
14470
14471 void
14472 SetGameInfo()
14473 {
14474     /* This routine is used only for certain modes */
14475     VariantClass v = gameInfo.variant;
14476     ChessMove r = GameUnfinished;
14477     char *p = NULL;
14478
14479     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14480         r = gameInfo.result;
14481         p = gameInfo.resultDetails;
14482         gameInfo.resultDetails = NULL;
14483     }
14484     ClearGameInfo(&gameInfo);
14485     gameInfo.variant = v;
14486
14487     switch (gameMode) {
14488       case MachinePlaysWhite:
14489         gameInfo.event = StrSave( appData.pgnEventHeader );
14490         gameInfo.site = StrSave(HostName());
14491         gameInfo.date = PGNDate();
14492         gameInfo.round = StrSave("-");
14493         gameInfo.white = StrSave(first.tidy);
14494         gameInfo.black = StrSave(UserName());
14495         gameInfo.timeControl = TimeControlTagValue();
14496         break;
14497
14498       case MachinePlaysBlack:
14499         gameInfo.event = StrSave( appData.pgnEventHeader );
14500         gameInfo.site = StrSave(HostName());
14501         gameInfo.date = PGNDate();
14502         gameInfo.round = StrSave("-");
14503         gameInfo.white = StrSave(UserName());
14504         gameInfo.black = StrSave(first.tidy);
14505         gameInfo.timeControl = TimeControlTagValue();
14506         break;
14507
14508       case TwoMachinesPlay:
14509         gameInfo.event = StrSave( appData.pgnEventHeader );
14510         gameInfo.site = StrSave(HostName());
14511         gameInfo.date = PGNDate();
14512         if (roundNr > 0) {
14513             char buf[MSG_SIZ];
14514             snprintf(buf, MSG_SIZ, "%d", roundNr);
14515             gameInfo.round = StrSave(buf);
14516         } else {
14517             gameInfo.round = StrSave("-");
14518         }
14519         if (first.twoMachinesColor[0] == 'w') {
14520             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14521             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14522         } else {
14523             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14524             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14525         }
14526         gameInfo.timeControl = TimeControlTagValue();
14527         break;
14528
14529       case EditGame:
14530         gameInfo.event = StrSave("Edited game");
14531         gameInfo.site = StrSave(HostName());
14532         gameInfo.date = PGNDate();
14533         gameInfo.round = StrSave("-");
14534         gameInfo.white = StrSave("-");
14535         gameInfo.black = StrSave("-");
14536         gameInfo.result = r;
14537         gameInfo.resultDetails = p;
14538         break;
14539
14540       case EditPosition:
14541         gameInfo.event = StrSave("Edited position");
14542         gameInfo.site = StrSave(HostName());
14543         gameInfo.date = PGNDate();
14544         gameInfo.round = StrSave("-");
14545         gameInfo.white = StrSave("-");
14546         gameInfo.black = StrSave("-");
14547         break;
14548
14549       case IcsPlayingWhite:
14550       case IcsPlayingBlack:
14551       case IcsObserving:
14552       case IcsExamining:
14553         break;
14554
14555       case PlayFromGameFile:
14556         gameInfo.event = StrSave("Game from non-PGN file");
14557         gameInfo.site = StrSave(HostName());
14558         gameInfo.date = PGNDate();
14559         gameInfo.round = StrSave("-");
14560         gameInfo.white = StrSave("?");
14561         gameInfo.black = StrSave("?");
14562         break;
14563
14564       default:
14565         break;
14566     }
14567 }
14568
14569 void
14570 ReplaceComment(index, text)
14571      int index;
14572      char *text;
14573 {
14574     int len;
14575     char *p;
14576     float score;
14577
14578     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14579        pvInfoList[index-1].depth == len &&
14580        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14581        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14582     while (*text == '\n') text++;
14583     len = strlen(text);
14584     while (len > 0 && text[len - 1] == '\n') len--;
14585
14586     if (commentList[index] != NULL)
14587       free(commentList[index]);
14588
14589     if (len == 0) {
14590         commentList[index] = NULL;
14591         return;
14592     }
14593   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14594       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14595       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14596     commentList[index] = (char *) malloc(len + 2);
14597     strncpy(commentList[index], text, len);
14598     commentList[index][len] = '\n';
14599     commentList[index][len + 1] = NULLCHAR;
14600   } else {
14601     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14602     char *p;
14603     commentList[index] = (char *) malloc(len + 7);
14604     safeStrCpy(commentList[index], "{\n", 3);
14605     safeStrCpy(commentList[index]+2, text, len+1);
14606     commentList[index][len+2] = NULLCHAR;
14607     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14608     strcat(commentList[index], "\n}\n");
14609   }
14610 }
14611
14612 void
14613 CrushCRs(text)
14614      char *text;
14615 {
14616   char *p = text;
14617   char *q = text;
14618   char ch;
14619
14620   do {
14621     ch = *p++;
14622     if (ch == '\r') continue;
14623     *q++ = ch;
14624   } while (ch != '\0');
14625 }
14626
14627 void
14628 AppendComment(index, text, addBraces)
14629      int index;
14630      char *text;
14631      Boolean addBraces; // [HGM] braces: tells if we should add {}
14632 {
14633     int oldlen, len;
14634     char *old;
14635
14636 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14637     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14638
14639     CrushCRs(text);
14640     while (*text == '\n') text++;
14641     len = strlen(text);
14642     while (len > 0 && text[len - 1] == '\n') len--;
14643
14644     if (len == 0) return;
14645
14646     if (commentList[index] != NULL) {
14647       Boolean addClosingBrace = addBraces;
14648         old = commentList[index];
14649         oldlen = strlen(old);
14650         while(commentList[index][oldlen-1] ==  '\n')
14651           commentList[index][--oldlen] = NULLCHAR;
14652         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14653         safeStrCpy(commentList[index], old, oldlen + len + 6);
14654         free(old);
14655         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14656         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14657           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14658           while (*text == '\n') { text++; len--; }
14659           commentList[index][--oldlen] = NULLCHAR;
14660       }
14661         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14662         else          strcat(commentList[index], "\n");
14663         strcat(commentList[index], text);
14664         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14665         else          strcat(commentList[index], "\n");
14666     } else {
14667         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14668         if(addBraces)
14669           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14670         else commentList[index][0] = NULLCHAR;
14671         strcat(commentList[index], text);
14672         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14673         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14674     }
14675 }
14676
14677 static char * FindStr( char * text, char * sub_text )
14678 {
14679     char * result = strstr( text, sub_text );
14680
14681     if( result != NULL ) {
14682         result += strlen( sub_text );
14683     }
14684
14685     return result;
14686 }
14687
14688 /* [AS] Try to extract PV info from PGN comment */
14689 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14690 char *GetInfoFromComment( int index, char * text )
14691 {
14692     char * sep = text, *p;
14693
14694     if( text != NULL && index > 0 ) {
14695         int score = 0;
14696         int depth = 0;
14697         int time = -1, sec = 0, deci;
14698         char * s_eval = FindStr( text, "[%eval " );
14699         char * s_emt = FindStr( text, "[%emt " );
14700
14701         if( s_eval != NULL || s_emt != NULL ) {
14702             /* New style */
14703             char delim;
14704
14705             if( s_eval != NULL ) {
14706                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14707                     return text;
14708                 }
14709
14710                 if( delim != ']' ) {
14711                     return text;
14712                 }
14713             }
14714
14715             if( s_emt != NULL ) {
14716             }
14717                 return text;
14718         }
14719         else {
14720             /* We expect something like: [+|-]nnn.nn/dd */
14721             int score_lo = 0;
14722
14723             if(*text != '{') return text; // [HGM] braces: must be normal comment
14724
14725             sep = strchr( text, '/' );
14726             if( sep == NULL || sep < (text+4) ) {
14727                 return text;
14728             }
14729
14730             p = text;
14731             if(p[1] == '(') { // comment starts with PV
14732                p = strchr(p, ')'); // locate end of PV
14733                if(p == NULL || sep < p+5) return text;
14734                // at this point we have something like "{(.*) +0.23/6 ..."
14735                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14736                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14737                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14738             }
14739             time = -1; sec = -1; deci = -1;
14740             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14741                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14742                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14743                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14744                 return text;
14745             }
14746
14747             if( score_lo < 0 || score_lo >= 100 ) {
14748                 return text;
14749             }
14750
14751             if(sec >= 0) time = 600*time + 10*sec; else
14752             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14753
14754             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14755
14756             /* [HGM] PV time: now locate end of PV info */
14757             while( *++sep >= '0' && *sep <= '9'); // strip depth
14758             if(time >= 0)
14759             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14760             if(sec >= 0)
14761             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14762             if(deci >= 0)
14763             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14764             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14765         }
14766
14767         if( depth <= 0 ) {
14768             return text;
14769         }
14770
14771         if( time < 0 ) {
14772             time = -1;
14773         }
14774
14775         pvInfoList[index-1].depth = depth;
14776         pvInfoList[index-1].score = score;
14777         pvInfoList[index-1].time  = 10*time; // centi-sec
14778         if(*sep == '}') *sep = 0; else *--sep = '{';
14779         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14780     }
14781     return sep;
14782 }
14783
14784 void
14785 SendToProgram(message, cps)
14786      char *message;
14787      ChessProgramState *cps;
14788 {
14789     int count, outCount, error;
14790     char buf[MSG_SIZ];
14791
14792     if (cps->pr == NULL) return;
14793     Attention(cps);
14794
14795     if (appData.debugMode) {
14796         TimeMark now;
14797         GetTimeMark(&now);
14798         fprintf(debugFP, "%ld >%-6s: %s",
14799                 SubtractTimeMarks(&now, &programStartTime),
14800                 cps->which, message);
14801     }
14802
14803     count = strlen(message);
14804     outCount = OutputToProcess(cps->pr, message, count, &error);
14805     if (outCount < count && !exiting
14806                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14807       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14808       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14809         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14810             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14811                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14812                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14813                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14814             } else {
14815                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14816                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14817                 gameInfo.result = res;
14818             }
14819             gameInfo.resultDetails = StrSave(buf);
14820         }
14821         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14822         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14823     }
14824 }
14825
14826 void
14827 ReceiveFromProgram(isr, closure, message, count, error)
14828      InputSourceRef isr;
14829      VOIDSTAR closure;
14830      char *message;
14831      int count;
14832      int error;
14833 {
14834     char *end_str;
14835     char buf[MSG_SIZ];
14836     ChessProgramState *cps = (ChessProgramState *)closure;
14837
14838     if (isr != cps->isr) return; /* Killed intentionally */
14839     if (count <= 0) {
14840         if (count == 0) {
14841             RemoveInputSource(cps->isr);
14842             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14843             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14844                     _(cps->which), cps->program);
14845         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14846                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14847                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14848                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14849                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14850                 } else {
14851                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14852                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14853                     gameInfo.result = res;
14854                 }
14855                 gameInfo.resultDetails = StrSave(buf);
14856             }
14857             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14858             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14859         } else {
14860             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14861                     _(cps->which), cps->program);
14862             RemoveInputSource(cps->isr);
14863
14864             /* [AS] Program is misbehaving badly... kill it */
14865             if( count == -2 ) {
14866                 DestroyChildProcess( cps->pr, 9 );
14867                 cps->pr = NoProc;
14868             }
14869
14870             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14871         }
14872         return;
14873     }
14874
14875     if ((end_str = strchr(message, '\r')) != NULL)
14876       *end_str = NULLCHAR;
14877     if ((end_str = strchr(message, '\n')) != NULL)
14878       *end_str = NULLCHAR;
14879
14880     if (appData.debugMode) {
14881         TimeMark now; int print = 1;
14882         char *quote = ""; char c; int i;
14883
14884         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14885                 char start = message[0];
14886                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14887                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14888                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14889                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14890                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14891                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14892                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14893                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14894                    sscanf(message, "hint: %c", &c)!=1 && 
14895                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14896                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14897                     print = (appData.engineComments >= 2);
14898                 }
14899                 message[0] = start; // restore original message
14900         }
14901         if(print) {
14902                 GetTimeMark(&now);
14903                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14904                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14905                         quote,
14906                         message);
14907         }
14908     }
14909
14910     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14911     if (appData.icsEngineAnalyze) {
14912         if (strstr(message, "whisper") != NULL ||
14913              strstr(message, "kibitz") != NULL ||
14914             strstr(message, "tellics") != NULL) return;
14915     }
14916
14917     HandleMachineMove(message, cps);
14918 }
14919
14920
14921 void
14922 SendTimeControl(cps, mps, tc, inc, sd, st)
14923      ChessProgramState *cps;
14924      int mps, inc, sd, st;
14925      long tc;
14926 {
14927     char buf[MSG_SIZ];
14928     int seconds;
14929
14930     if( timeControl_2 > 0 ) {
14931         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14932             tc = timeControl_2;
14933         }
14934     }
14935     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14936     inc /= cps->timeOdds;
14937     st  /= cps->timeOdds;
14938
14939     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14940
14941     if (st > 0) {
14942       /* Set exact time per move, normally using st command */
14943       if (cps->stKludge) {
14944         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14945         seconds = st % 60;
14946         if (seconds == 0) {
14947           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14948         } else {
14949           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14950         }
14951       } else {
14952         snprintf(buf, MSG_SIZ, "st %d\n", st);
14953       }
14954     } else {
14955       /* Set conventional or incremental time control, using level command */
14956       if (seconds == 0) {
14957         /* Note old gnuchess bug -- minutes:seconds used to not work.
14958            Fixed in later versions, but still avoid :seconds
14959            when seconds is 0. */
14960         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14961       } else {
14962         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14963                  seconds, inc/1000.);
14964       }
14965     }
14966     SendToProgram(buf, cps);
14967
14968     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14969     /* Orthogonally, limit search to given depth */
14970     if (sd > 0) {
14971       if (cps->sdKludge) {
14972         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14973       } else {
14974         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14975       }
14976       SendToProgram(buf, cps);
14977     }
14978
14979     if(cps->nps >= 0) { /* [HGM] nps */
14980         if(cps->supportsNPS == FALSE)
14981           cps->nps = -1; // don't use if engine explicitly says not supported!
14982         else {
14983           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14984           SendToProgram(buf, cps);
14985         }
14986     }
14987 }
14988
14989 ChessProgramState *WhitePlayer()
14990 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14991 {
14992     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14993        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14994         return &second;
14995     return &first;
14996 }
14997
14998 void
14999 SendTimeRemaining(cps, machineWhite)
15000      ChessProgramState *cps;
15001      int /*boolean*/ machineWhite;
15002 {
15003     char message[MSG_SIZ];
15004     long time, otime;
15005
15006     /* Note: this routine must be called when the clocks are stopped
15007        or when they have *just* been set or switched; otherwise
15008        it will be off by the time since the current tick started.
15009     */
15010     if (machineWhite) {
15011         time = whiteTimeRemaining / 10;
15012         otime = blackTimeRemaining / 10;
15013     } else {
15014         time = blackTimeRemaining / 10;
15015         otime = whiteTimeRemaining / 10;
15016     }
15017     /* [HGM] translate opponent's time by time-odds factor */
15018     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15019     if (appData.debugMode) {
15020         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15021     }
15022
15023     if (time <= 0) time = 1;
15024     if (otime <= 0) otime = 1;
15025
15026     snprintf(message, MSG_SIZ, "time %ld\n", time);
15027     SendToProgram(message, cps);
15028
15029     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15030     SendToProgram(message, cps);
15031 }
15032
15033 int
15034 BoolFeature(p, name, loc, cps)
15035      char **p;
15036      char *name;
15037      int *loc;
15038      ChessProgramState *cps;
15039 {
15040   char buf[MSG_SIZ];
15041   int len = strlen(name);
15042   int val;
15043
15044   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15045     (*p) += len + 1;
15046     sscanf(*p, "%d", &val);
15047     *loc = (val != 0);
15048     while (**p && **p != ' ')
15049       (*p)++;
15050     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15051     SendToProgram(buf, cps);
15052     return TRUE;
15053   }
15054   return FALSE;
15055 }
15056
15057 int
15058 IntFeature(p, name, loc, cps)
15059      char **p;
15060      char *name;
15061      int *loc;
15062      ChessProgramState *cps;
15063 {
15064   char buf[MSG_SIZ];
15065   int len = strlen(name);
15066   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15067     (*p) += len + 1;
15068     sscanf(*p, "%d", loc);
15069     while (**p && **p != ' ') (*p)++;
15070     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15071     SendToProgram(buf, cps);
15072     return TRUE;
15073   }
15074   return FALSE;
15075 }
15076
15077 int
15078 StringFeature(p, name, loc, cps)
15079      char **p;
15080      char *name;
15081      char loc[];
15082      ChessProgramState *cps;
15083 {
15084   char buf[MSG_SIZ];
15085   int len = strlen(name);
15086   if (strncmp((*p), name, len) == 0
15087       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15088     (*p) += len + 2;
15089     sscanf(*p, "%[^\"]", loc);
15090     while (**p && **p != '\"') (*p)++;
15091     if (**p == '\"') (*p)++;
15092     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15093     SendToProgram(buf, cps);
15094     return TRUE;
15095   }
15096   return FALSE;
15097 }
15098
15099 int
15100 ParseOption(Option *opt, ChessProgramState *cps)
15101 // [HGM] options: process the string that defines an engine option, and determine
15102 // name, type, default value, and allowed value range
15103 {
15104         char *p, *q, buf[MSG_SIZ];
15105         int n, min = (-1)<<31, max = 1<<31, def;
15106
15107         if(p = strstr(opt->name, " -spin ")) {
15108             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15109             if(max < min) max = min; // enforce consistency
15110             if(def < min) def = min;
15111             if(def > max) def = max;
15112             opt->value = def;
15113             opt->min = min;
15114             opt->max = max;
15115             opt->type = Spin;
15116         } else if((p = strstr(opt->name, " -slider "))) {
15117             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15118             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15119             if(max < min) max = min; // enforce consistency
15120             if(def < min) def = min;
15121             if(def > max) def = max;
15122             opt->value = def;
15123             opt->min = min;
15124             opt->max = max;
15125             opt->type = Spin; // Slider;
15126         } else if((p = strstr(opt->name, " -string "))) {
15127             opt->textValue = p+9;
15128             opt->type = TextBox;
15129         } else if((p = strstr(opt->name, " -file "))) {
15130             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15131             opt->textValue = p+7;
15132             opt->type = FileName; // FileName;
15133         } else if((p = strstr(opt->name, " -path "))) {
15134             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15135             opt->textValue = p+7;
15136             opt->type = PathName; // PathName;
15137         } else if(p = strstr(opt->name, " -check ")) {
15138             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15139             opt->value = (def != 0);
15140             opt->type = CheckBox;
15141         } else if(p = strstr(opt->name, " -combo ")) {
15142             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15143             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15144             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15145             opt->value = n = 0;
15146             while(q = StrStr(q, " /// ")) {
15147                 n++; *q = 0;    // count choices, and null-terminate each of them
15148                 q += 5;
15149                 if(*q == '*') { // remember default, which is marked with * prefix
15150                     q++;
15151                     opt->value = n;
15152                 }
15153                 cps->comboList[cps->comboCnt++] = q;
15154             }
15155             cps->comboList[cps->comboCnt++] = NULL;
15156             opt->max = n + 1;
15157             opt->type = ComboBox;
15158         } else if(p = strstr(opt->name, " -button")) {
15159             opt->type = Button;
15160         } else if(p = strstr(opt->name, " -save")) {
15161             opt->type = SaveButton;
15162         } else return FALSE;
15163         *p = 0; // terminate option name
15164         // now look if the command-line options define a setting for this engine option.
15165         if(cps->optionSettings && cps->optionSettings[0])
15166             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15167         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15168           snprintf(buf, MSG_SIZ, "option %s", p);
15169                 if(p = strstr(buf, ",")) *p = 0;
15170                 if(q = strchr(buf, '=')) switch(opt->type) {
15171                     case ComboBox:
15172                         for(n=0; n<opt->max; n++)
15173                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15174                         break;
15175                     case TextBox:
15176                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15177                         break;
15178                     case Spin:
15179                     case CheckBox:
15180                         opt->value = atoi(q+1);
15181                     default:
15182                         break;
15183                 }
15184                 strcat(buf, "\n");
15185                 SendToProgram(buf, cps);
15186         }
15187         return TRUE;
15188 }
15189
15190 void
15191 FeatureDone(cps, val)
15192      ChessProgramState* cps;
15193      int val;
15194 {
15195   DelayedEventCallback cb = GetDelayedEvent();
15196   if ((cb == InitBackEnd3 && cps == &first) ||
15197       (cb == SettingsMenuIfReady && cps == &second) ||
15198       (cb == LoadEngine) ||
15199       (cb == TwoMachinesEventIfReady)) {
15200     CancelDelayedEvent();
15201     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15202   }
15203   cps->initDone = val;
15204 }
15205
15206 /* Parse feature command from engine */
15207 void
15208 ParseFeatures(args, cps)
15209      char* args;
15210      ChessProgramState *cps;
15211 {
15212   char *p = args;
15213   char *q;
15214   int val;
15215   char buf[MSG_SIZ];
15216
15217   for (;;) {
15218     while (*p == ' ') p++;
15219     if (*p == NULLCHAR) return;
15220
15221     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15222     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15223     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15224     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15225     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15226     if (BoolFeature(&p, "reuse", &val, cps)) {
15227       /* Engine can disable reuse, but can't enable it if user said no */
15228       if (!val) cps->reuse = FALSE;
15229       continue;
15230     }
15231     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15232     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15233       if (gameMode == TwoMachinesPlay) {
15234         DisplayTwoMachinesTitle();
15235       } else {
15236         DisplayTitle("");
15237       }
15238       continue;
15239     }
15240     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15241     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15242     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15243     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15244     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15245     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15246     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15247     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15248     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15249     if (IntFeature(&p, "done", &val, cps)) {
15250       FeatureDone(cps, val);
15251       continue;
15252     }
15253     /* Added by Tord: */
15254     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15255     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15256     /* End of additions by Tord */
15257
15258     /* [HGM] added features: */
15259     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15260     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15261     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15262     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15263     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15264     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15265     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15266         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15267           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15268             SendToProgram(buf, cps);
15269             continue;
15270         }
15271         if(cps->nrOptions >= MAX_OPTIONS) {
15272             cps->nrOptions--;
15273             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15274             DisplayError(buf, 0);
15275         }
15276         continue;
15277     }
15278     /* End of additions by HGM */
15279
15280     /* unknown feature: complain and skip */
15281     q = p;
15282     while (*q && *q != '=') q++;
15283     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15284     SendToProgram(buf, cps);
15285     p = q;
15286     if (*p == '=') {
15287       p++;
15288       if (*p == '\"') {
15289         p++;
15290         while (*p && *p != '\"') p++;
15291         if (*p == '\"') p++;
15292       } else {
15293         while (*p && *p != ' ') p++;
15294       }
15295     }
15296   }
15297
15298 }
15299
15300 void
15301 PeriodicUpdatesEvent(newState)
15302      int newState;
15303 {
15304     if (newState == appData.periodicUpdates)
15305       return;
15306
15307     appData.periodicUpdates=newState;
15308
15309     /* Display type changes, so update it now */
15310 //    DisplayAnalysis();
15311
15312     /* Get the ball rolling again... */
15313     if (newState) {
15314         AnalysisPeriodicEvent(1);
15315         StartAnalysisClock();
15316     }
15317 }
15318
15319 void
15320 PonderNextMoveEvent(newState)
15321      int newState;
15322 {
15323     if (newState == appData.ponderNextMove) return;
15324     if (gameMode == EditPosition) EditPositionDone(TRUE);
15325     if (newState) {
15326         SendToProgram("hard\n", &first);
15327         if (gameMode == TwoMachinesPlay) {
15328             SendToProgram("hard\n", &second);
15329         }
15330     } else {
15331         SendToProgram("easy\n", &first);
15332         thinkOutput[0] = NULLCHAR;
15333         if (gameMode == TwoMachinesPlay) {
15334             SendToProgram("easy\n", &second);
15335         }
15336     }
15337     appData.ponderNextMove = newState;
15338 }
15339
15340 void
15341 NewSettingEvent(option, feature, command, value)
15342      char *command;
15343      int option, value, *feature;
15344 {
15345     char buf[MSG_SIZ];
15346
15347     if (gameMode == EditPosition) EditPositionDone(TRUE);
15348     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15349     if(feature == NULL || *feature) SendToProgram(buf, &first);
15350     if (gameMode == TwoMachinesPlay) {
15351         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15352     }
15353 }
15354
15355 void
15356 ShowThinkingEvent()
15357 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15358 {
15359     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15360     int newState = appData.showThinking
15361         // [HGM] thinking: other features now need thinking output as well
15362         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15363
15364     if (oldState == newState) return;
15365     oldState = newState;
15366     if (gameMode == EditPosition) EditPositionDone(TRUE);
15367     if (oldState) {
15368         SendToProgram("post\n", &first);
15369         if (gameMode == TwoMachinesPlay) {
15370             SendToProgram("post\n", &second);
15371         }
15372     } else {
15373         SendToProgram("nopost\n", &first);
15374         thinkOutput[0] = NULLCHAR;
15375         if (gameMode == TwoMachinesPlay) {
15376             SendToProgram("nopost\n", &second);
15377         }
15378     }
15379 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15380 }
15381
15382 void
15383 AskQuestionEvent(title, question, replyPrefix, which)
15384      char *title; char *question; char *replyPrefix; char *which;
15385 {
15386   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15387   if (pr == NoProc) return;
15388   AskQuestion(title, question, replyPrefix, pr);
15389 }
15390
15391 void
15392 TypeInEvent(char firstChar)
15393 {
15394     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15395         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15396         gameMode == AnalyzeMode || gameMode == EditGame || 
15397         gameMode == EditPosition || gameMode == IcsExamining ||
15398         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15399         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15400                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15401                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15402         gameMode == Training) PopUpMoveDialog(firstChar);
15403 }
15404
15405 void
15406 TypeInDoneEvent(char *move)
15407 {
15408         Board board;
15409         int n, fromX, fromY, toX, toY;
15410         char promoChar;
15411         ChessMove moveType;
15412
15413         // [HGM] FENedit
15414         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15415                 EditPositionPasteFEN(move);
15416                 return;
15417         }
15418         // [HGM] movenum: allow move number to be typed in any mode
15419         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15420           ToNrEvent(2*n-1);
15421           return;
15422         }
15423
15424       if (gameMode != EditGame && currentMove != forwardMostMove && 
15425         gameMode != Training) {
15426         DisplayMoveError(_("Displayed move is not current"));
15427       } else {
15428         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15429           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15430         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15431         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15432           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15433           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15434         } else {
15435           DisplayMoveError(_("Could not parse move"));
15436         }
15437       }
15438 }
15439
15440 void
15441 DisplayMove(moveNumber)
15442      int moveNumber;
15443 {
15444     char message[MSG_SIZ];
15445     char res[MSG_SIZ];
15446     char cpThinkOutput[MSG_SIZ];
15447
15448     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15449
15450     if (moveNumber == forwardMostMove - 1 ||
15451         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15452
15453         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15454
15455         if (strchr(cpThinkOutput, '\n')) {
15456             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15457         }
15458     } else {
15459         *cpThinkOutput = NULLCHAR;
15460     }
15461
15462     /* [AS] Hide thinking from human user */
15463     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15464         *cpThinkOutput = NULLCHAR;
15465         if( thinkOutput[0] != NULLCHAR ) {
15466             int i;
15467
15468             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15469                 cpThinkOutput[i] = '.';
15470             }
15471             cpThinkOutput[i] = NULLCHAR;
15472             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15473         }
15474     }
15475
15476     if (moveNumber == forwardMostMove - 1 &&
15477         gameInfo.resultDetails != NULL) {
15478         if (gameInfo.resultDetails[0] == NULLCHAR) {
15479           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15480         } else {
15481           snprintf(res, MSG_SIZ, " {%s} %s",
15482                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15483         }
15484     } else {
15485         res[0] = NULLCHAR;
15486     }
15487
15488     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15489         DisplayMessage(res, cpThinkOutput);
15490     } else {
15491       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15492                 WhiteOnMove(moveNumber) ? " " : ".. ",
15493                 parseList[moveNumber], res);
15494         DisplayMessage(message, cpThinkOutput);
15495     }
15496 }
15497
15498 void
15499 DisplayComment(moveNumber, text)
15500      int moveNumber;
15501      char *text;
15502 {
15503     char title[MSG_SIZ];
15504
15505     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15506       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15507     } else {
15508       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15509               WhiteOnMove(moveNumber) ? " " : ".. ",
15510               parseList[moveNumber]);
15511     }
15512     if (text != NULL && (appData.autoDisplayComment || commentUp))
15513         CommentPopUp(title, text);
15514 }
15515
15516 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15517  * might be busy thinking or pondering.  It can be omitted if your
15518  * gnuchess is configured to stop thinking immediately on any user
15519  * input.  However, that gnuchess feature depends on the FIONREAD
15520  * ioctl, which does not work properly on some flavors of Unix.
15521  */
15522 void
15523 Attention(cps)
15524      ChessProgramState *cps;
15525 {
15526 #if ATTENTION
15527     if (!cps->useSigint) return;
15528     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15529     switch (gameMode) {
15530       case MachinePlaysWhite:
15531       case MachinePlaysBlack:
15532       case TwoMachinesPlay:
15533       case IcsPlayingWhite:
15534       case IcsPlayingBlack:
15535       case AnalyzeMode:
15536       case AnalyzeFile:
15537         /* Skip if we know it isn't thinking */
15538         if (!cps->maybeThinking) return;
15539         if (appData.debugMode)
15540           fprintf(debugFP, "Interrupting %s\n", cps->which);
15541         InterruptChildProcess(cps->pr);
15542         cps->maybeThinking = FALSE;
15543         break;
15544       default:
15545         break;
15546     }
15547 #endif /*ATTENTION*/
15548 }
15549
15550 int
15551 CheckFlags()
15552 {
15553     if (whiteTimeRemaining <= 0) {
15554         if (!whiteFlag) {
15555             whiteFlag = TRUE;
15556             if (appData.icsActive) {
15557                 if (appData.autoCallFlag &&
15558                     gameMode == IcsPlayingBlack && !blackFlag) {
15559                   SendToICS(ics_prefix);
15560                   SendToICS("flag\n");
15561                 }
15562             } else {
15563                 if (blackFlag) {
15564                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15565                 } else {
15566                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15567                     if (appData.autoCallFlag) {
15568                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15569                         return TRUE;
15570                     }
15571                 }
15572             }
15573         }
15574     }
15575     if (blackTimeRemaining <= 0) {
15576         if (!blackFlag) {
15577             blackFlag = TRUE;
15578             if (appData.icsActive) {
15579                 if (appData.autoCallFlag &&
15580                     gameMode == IcsPlayingWhite && !whiteFlag) {
15581                   SendToICS(ics_prefix);
15582                   SendToICS("flag\n");
15583                 }
15584             } else {
15585                 if (whiteFlag) {
15586                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15587                 } else {
15588                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15589                     if (appData.autoCallFlag) {
15590                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15591                         return TRUE;
15592                     }
15593                 }
15594             }
15595         }
15596     }
15597     return FALSE;
15598 }
15599
15600 void
15601 CheckTimeControl()
15602 {
15603     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15604         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15605
15606     /*
15607      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15608      */
15609     if ( !WhiteOnMove(forwardMostMove) ) {
15610         /* White made time control */
15611         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15612         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15613         /* [HGM] time odds: correct new time quota for time odds! */
15614                                             / WhitePlayer()->timeOdds;
15615         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15616     } else {
15617         lastBlack -= blackTimeRemaining;
15618         /* Black made time control */
15619         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15620                                             / WhitePlayer()->other->timeOdds;
15621         lastWhite = whiteTimeRemaining;
15622     }
15623 }
15624
15625 void
15626 DisplayBothClocks()
15627 {
15628     int wom = gameMode == EditPosition ?
15629       !blackPlaysFirst : WhiteOnMove(currentMove);
15630     DisplayWhiteClock(whiteTimeRemaining, wom);
15631     DisplayBlackClock(blackTimeRemaining, !wom);
15632 }
15633
15634
15635 /* Timekeeping seems to be a portability nightmare.  I think everyone
15636    has ftime(), but I'm really not sure, so I'm including some ifdefs
15637    to use other calls if you don't.  Clocks will be less accurate if
15638    you have neither ftime nor gettimeofday.
15639 */
15640
15641 /* VS 2008 requires the #include outside of the function */
15642 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15643 #include <sys/timeb.h>
15644 #endif
15645
15646 /* Get the current time as a TimeMark */
15647 void
15648 GetTimeMark(tm)
15649      TimeMark *tm;
15650 {
15651 #if HAVE_GETTIMEOFDAY
15652
15653     struct timeval timeVal;
15654     struct timezone timeZone;
15655
15656     gettimeofday(&timeVal, &timeZone);
15657     tm->sec = (long) timeVal.tv_sec;
15658     tm->ms = (int) (timeVal.tv_usec / 1000L);
15659
15660 #else /*!HAVE_GETTIMEOFDAY*/
15661 #if HAVE_FTIME
15662
15663 // include <sys/timeb.h> / moved to just above start of function
15664     struct timeb timeB;
15665
15666     ftime(&timeB);
15667     tm->sec = (long) timeB.time;
15668     tm->ms = (int) timeB.millitm;
15669
15670 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15671     tm->sec = (long) time(NULL);
15672     tm->ms = 0;
15673 #endif
15674 #endif
15675 }
15676
15677 /* Return the difference in milliseconds between two
15678    time marks.  We assume the difference will fit in a long!
15679 */
15680 long
15681 SubtractTimeMarks(tm2, tm1)
15682      TimeMark *tm2, *tm1;
15683 {
15684     return 1000L*(tm2->sec - tm1->sec) +
15685            (long) (tm2->ms - tm1->ms);
15686 }
15687
15688
15689 /*
15690  * Code to manage the game clocks.
15691  *
15692  * In tournament play, black starts the clock and then white makes a move.
15693  * We give the human user a slight advantage if he is playing white---the
15694  * clocks don't run until he makes his first move, so it takes zero time.
15695  * Also, we don't account for network lag, so we could get out of sync
15696  * with GNU Chess's clock -- but then, referees are always right.
15697  */
15698
15699 static TimeMark tickStartTM;
15700 static long intendedTickLength;
15701
15702 long
15703 NextTickLength(timeRemaining)
15704      long timeRemaining;
15705 {
15706     long nominalTickLength, nextTickLength;
15707
15708     if (timeRemaining > 0L && timeRemaining <= 10000L)
15709       nominalTickLength = 100L;
15710     else
15711       nominalTickLength = 1000L;
15712     nextTickLength = timeRemaining % nominalTickLength;
15713     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15714
15715     return nextTickLength;
15716 }
15717
15718 /* Adjust clock one minute up or down */
15719 void
15720 AdjustClock(Boolean which, int dir)
15721 {
15722     if(which) blackTimeRemaining += 60000*dir;
15723     else      whiteTimeRemaining += 60000*dir;
15724     DisplayBothClocks();
15725 }
15726
15727 /* Stop clocks and reset to a fresh time control */
15728 void
15729 ResetClocks()
15730 {
15731     (void) StopClockTimer();
15732     if (appData.icsActive) {
15733         whiteTimeRemaining = blackTimeRemaining = 0;
15734     } else if (searchTime) {
15735         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15736         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15737     } else { /* [HGM] correct new time quote for time odds */
15738         whiteTC = blackTC = fullTimeControlString;
15739         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15740         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15741     }
15742     if (whiteFlag || blackFlag) {
15743         DisplayTitle("");
15744         whiteFlag = blackFlag = FALSE;
15745     }
15746     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15747     DisplayBothClocks();
15748 }
15749
15750 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15751
15752 /* Decrement running clock by amount of time that has passed */
15753 void
15754 DecrementClocks()
15755 {
15756     long timeRemaining;
15757     long lastTickLength, fudge;
15758     TimeMark now;
15759
15760     if (!appData.clockMode) return;
15761     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15762
15763     GetTimeMark(&now);
15764
15765     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15766
15767     /* Fudge if we woke up a little too soon */
15768     fudge = intendedTickLength - lastTickLength;
15769     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15770
15771     if (WhiteOnMove(forwardMostMove)) {
15772         if(whiteNPS >= 0) lastTickLength = 0;
15773         timeRemaining = whiteTimeRemaining -= lastTickLength;
15774         if(timeRemaining < 0 && !appData.icsActive) {
15775             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15776             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15777                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15778                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15779             }
15780         }
15781         DisplayWhiteClock(whiteTimeRemaining - fudge,
15782                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15783     } else {
15784         if(blackNPS >= 0) lastTickLength = 0;
15785         timeRemaining = blackTimeRemaining -= lastTickLength;
15786         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15787             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15788             if(suddenDeath) {
15789                 blackStartMove = forwardMostMove;
15790                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15791             }
15792         }
15793         DisplayBlackClock(blackTimeRemaining - fudge,
15794                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15795     }
15796     if (CheckFlags()) return;
15797
15798     tickStartTM = now;
15799     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15800     StartClockTimer(intendedTickLength);
15801
15802     /* if the time remaining has fallen below the alarm threshold, sound the
15803      * alarm. if the alarm has sounded and (due to a takeback or time control
15804      * with increment) the time remaining has increased to a level above the
15805      * threshold, reset the alarm so it can sound again.
15806      */
15807
15808     if (appData.icsActive && appData.icsAlarm) {
15809
15810         /* make sure we are dealing with the user's clock */
15811         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15812                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15813            )) return;
15814
15815         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15816             alarmSounded = FALSE;
15817         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15818             PlayAlarmSound();
15819             alarmSounded = TRUE;
15820         }
15821     }
15822 }
15823
15824
15825 /* A player has just moved, so stop the previously running
15826    clock and (if in clock mode) start the other one.
15827    We redisplay both clocks in case we're in ICS mode, because
15828    ICS gives us an update to both clocks after every move.
15829    Note that this routine is called *after* forwardMostMove
15830    is updated, so the last fractional tick must be subtracted
15831    from the color that is *not* on move now.
15832 */
15833 void
15834 SwitchClocks(int newMoveNr)
15835 {
15836     long lastTickLength;
15837     TimeMark now;
15838     int flagged = FALSE;
15839
15840     GetTimeMark(&now);
15841
15842     if (StopClockTimer() && appData.clockMode) {
15843         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15844         if (!WhiteOnMove(forwardMostMove)) {
15845             if(blackNPS >= 0) lastTickLength = 0;
15846             blackTimeRemaining -= lastTickLength;
15847            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15848 //         if(pvInfoList[forwardMostMove].time == -1)
15849                  pvInfoList[forwardMostMove].time =               // use GUI time
15850                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15851         } else {
15852            if(whiteNPS >= 0) lastTickLength = 0;
15853            whiteTimeRemaining -= lastTickLength;
15854            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15855 //         if(pvInfoList[forwardMostMove].time == -1)
15856                  pvInfoList[forwardMostMove].time =
15857                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15858         }
15859         flagged = CheckFlags();
15860     }
15861     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15862     CheckTimeControl();
15863
15864     if (flagged || !appData.clockMode) return;
15865
15866     switch (gameMode) {
15867       case MachinePlaysBlack:
15868       case MachinePlaysWhite:
15869       case BeginningOfGame:
15870         if (pausing) return;
15871         break;
15872
15873       case EditGame:
15874       case PlayFromGameFile:
15875       case IcsExamining:
15876         return;
15877
15878       default:
15879         break;
15880     }
15881
15882     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15883         if(WhiteOnMove(forwardMostMove))
15884              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15885         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15886     }
15887
15888     tickStartTM = now;
15889     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15890       whiteTimeRemaining : blackTimeRemaining);
15891     StartClockTimer(intendedTickLength);
15892 }
15893
15894
15895 /* Stop both clocks */
15896 void
15897 StopClocks()
15898 {
15899     long lastTickLength;
15900     TimeMark now;
15901
15902     if (!StopClockTimer()) return;
15903     if (!appData.clockMode) return;
15904
15905     GetTimeMark(&now);
15906
15907     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15908     if (WhiteOnMove(forwardMostMove)) {
15909         if(whiteNPS >= 0) lastTickLength = 0;
15910         whiteTimeRemaining -= lastTickLength;
15911         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15912     } else {
15913         if(blackNPS >= 0) lastTickLength = 0;
15914         blackTimeRemaining -= lastTickLength;
15915         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15916     }
15917     CheckFlags();
15918 }
15919
15920 /* Start clock of player on move.  Time may have been reset, so
15921    if clock is already running, stop and restart it. */
15922 void
15923 StartClocks()
15924 {
15925     (void) StopClockTimer(); /* in case it was running already */
15926     DisplayBothClocks();
15927     if (CheckFlags()) return;
15928
15929     if (!appData.clockMode) return;
15930     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15931
15932     GetTimeMark(&tickStartTM);
15933     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15934       whiteTimeRemaining : blackTimeRemaining);
15935
15936    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15937     whiteNPS = blackNPS = -1;
15938     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15939        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15940         whiteNPS = first.nps;
15941     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15942        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15943         blackNPS = first.nps;
15944     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15945         whiteNPS = second.nps;
15946     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15947         blackNPS = second.nps;
15948     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15949
15950     StartClockTimer(intendedTickLength);
15951 }
15952
15953 char *
15954 TimeString(ms)
15955      long ms;
15956 {
15957     long second, minute, hour, day;
15958     char *sign = "";
15959     static char buf[32];
15960
15961     if (ms > 0 && ms <= 9900) {
15962       /* convert milliseconds to tenths, rounding up */
15963       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15964
15965       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15966       return buf;
15967     }
15968
15969     /* convert milliseconds to seconds, rounding up */
15970     /* use floating point to avoid strangeness of integer division
15971        with negative dividends on many machines */
15972     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15973
15974     if (second < 0) {
15975         sign = "-";
15976         second = -second;
15977     }
15978
15979     day = second / (60 * 60 * 24);
15980     second = second % (60 * 60 * 24);
15981     hour = second / (60 * 60);
15982     second = second % (60 * 60);
15983     minute = second / 60;
15984     second = second % 60;
15985
15986     if (day > 0)
15987       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15988               sign, day, hour, minute, second);
15989     else if (hour > 0)
15990       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15991     else
15992       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15993
15994     return buf;
15995 }
15996
15997
15998 /*
15999  * This is necessary because some C libraries aren't ANSI C compliant yet.
16000  */
16001 char *
16002 StrStr(string, match)
16003      char *string, *match;
16004 {
16005     int i, length;
16006
16007     length = strlen(match);
16008
16009     for (i = strlen(string) - length; i >= 0; i--, string++)
16010       if (!strncmp(match, string, length))
16011         return string;
16012
16013     return NULL;
16014 }
16015
16016 char *
16017 StrCaseStr(string, match)
16018      char *string, *match;
16019 {
16020     int i, j, length;
16021
16022     length = strlen(match);
16023
16024     for (i = strlen(string) - length; i >= 0; i--, string++) {
16025         for (j = 0; j < length; j++) {
16026             if (ToLower(match[j]) != ToLower(string[j]))
16027               break;
16028         }
16029         if (j == length) return string;
16030     }
16031
16032     return NULL;
16033 }
16034
16035 #ifndef _amigados
16036 int
16037 StrCaseCmp(s1, s2)
16038      char *s1, *s2;
16039 {
16040     char c1, c2;
16041
16042     for (;;) {
16043         c1 = ToLower(*s1++);
16044         c2 = ToLower(*s2++);
16045         if (c1 > c2) return 1;
16046         if (c1 < c2) return -1;
16047         if (c1 == NULLCHAR) return 0;
16048     }
16049 }
16050
16051
16052 int
16053 ToLower(c)
16054      int c;
16055 {
16056     return isupper(c) ? tolower(c) : c;
16057 }
16058
16059
16060 int
16061 ToUpper(c)
16062      int c;
16063 {
16064     return islower(c) ? toupper(c) : c;
16065 }
16066 #endif /* !_amigados    */
16067
16068 char *
16069 StrSave(s)
16070      char *s;
16071 {
16072   char *ret;
16073
16074   if ((ret = (char *) malloc(strlen(s) + 1)))
16075     {
16076       safeStrCpy(ret, s, strlen(s)+1);
16077     }
16078   return ret;
16079 }
16080
16081 char *
16082 StrSavePtr(s, savePtr)
16083      char *s, **savePtr;
16084 {
16085     if (*savePtr) {
16086         free(*savePtr);
16087     }
16088     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16089       safeStrCpy(*savePtr, s, strlen(s)+1);
16090     }
16091     return(*savePtr);
16092 }
16093
16094 char *
16095 PGNDate()
16096 {
16097     time_t clock;
16098     struct tm *tm;
16099     char buf[MSG_SIZ];
16100
16101     clock = time((time_t *)NULL);
16102     tm = localtime(&clock);
16103     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16104             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16105     return StrSave(buf);
16106 }
16107
16108
16109 char *
16110 PositionToFEN(move, overrideCastling)
16111      int move;
16112      char *overrideCastling;
16113 {
16114     int i, j, fromX, fromY, toX, toY;
16115     int whiteToPlay;
16116     char buf[MSG_SIZ];
16117     char *p, *q;
16118     int emptycount;
16119     ChessSquare piece;
16120
16121     whiteToPlay = (gameMode == EditPosition) ?
16122       !blackPlaysFirst : (move % 2 == 0);
16123     p = buf;
16124
16125     /* Piece placement data */
16126     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16127         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16128         emptycount = 0;
16129         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16130             if (boards[move][i][j] == EmptySquare) {
16131                 emptycount++;
16132             } else { ChessSquare piece = boards[move][i][j];
16133                 if (emptycount > 0) {
16134                     if(emptycount<10) /* [HGM] can be >= 10 */
16135                         *p++ = '0' + emptycount;
16136                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16137                     emptycount = 0;
16138                 }
16139                 if(PieceToChar(piece) == '+') {
16140                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16141                     *p++ = '+';
16142                     piece = (ChessSquare)(DEMOTED piece);
16143                 }
16144                 *p++ = PieceToChar(piece);
16145                 if(p[-1] == '~') {
16146                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16147                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16148                     *p++ = '~';
16149                 }
16150             }
16151         }
16152         if (emptycount > 0) {
16153             if(emptycount<10) /* [HGM] can be >= 10 */
16154                 *p++ = '0' + emptycount;
16155             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16156             emptycount = 0;
16157         }
16158         *p++ = '/';
16159     }
16160     *(p - 1) = ' ';
16161
16162     /* [HGM] print Crazyhouse or Shogi holdings */
16163     if( gameInfo.holdingsWidth ) {
16164         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16165         q = p;
16166         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16167             piece = boards[move][i][BOARD_WIDTH-1];
16168             if( piece != EmptySquare )
16169               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16170                   *p++ = PieceToChar(piece);
16171         }
16172         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16173             piece = boards[move][BOARD_HEIGHT-i-1][0];
16174             if( piece != EmptySquare )
16175               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16176                   *p++ = PieceToChar(piece);
16177         }
16178
16179         if( q == p ) *p++ = '-';
16180         *p++ = ']';
16181         *p++ = ' ';
16182     }
16183
16184     /* Active color */
16185     *p++ = whiteToPlay ? 'w' : 'b';
16186     *p++ = ' ';
16187
16188   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16189     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16190   } else {
16191   if(nrCastlingRights) {
16192      q = p;
16193      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16194        /* [HGM] write directly from rights */
16195            if(boards[move][CASTLING][2] != NoRights &&
16196               boards[move][CASTLING][0] != NoRights   )
16197                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16198            if(boards[move][CASTLING][2] != NoRights &&
16199               boards[move][CASTLING][1] != NoRights   )
16200                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16201            if(boards[move][CASTLING][5] != NoRights &&
16202               boards[move][CASTLING][3] != NoRights   )
16203                 *p++ = boards[move][CASTLING][3] + AAA;
16204            if(boards[move][CASTLING][5] != NoRights &&
16205               boards[move][CASTLING][4] != NoRights   )
16206                 *p++ = boards[move][CASTLING][4] + AAA;
16207      } else {
16208
16209         /* [HGM] write true castling rights */
16210         if( nrCastlingRights == 6 ) {
16211             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16212                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16213             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16214                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16215             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16216                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16217             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16218                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16219         }
16220      }
16221      if (q == p) *p++ = '-'; /* No castling rights */
16222      *p++ = ' ';
16223   }
16224
16225   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16226      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16227     /* En passant target square */
16228     if (move > backwardMostMove) {
16229         fromX = moveList[move - 1][0] - AAA;
16230         fromY = moveList[move - 1][1] - ONE;
16231         toX = moveList[move - 1][2] - AAA;
16232         toY = moveList[move - 1][3] - ONE;
16233         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16234             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16235             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16236             fromX == toX) {
16237             /* 2-square pawn move just happened */
16238             *p++ = toX + AAA;
16239             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16240         } else {
16241             *p++ = '-';
16242         }
16243     } else if(move == backwardMostMove) {
16244         // [HGM] perhaps we should always do it like this, and forget the above?
16245         if((signed char)boards[move][EP_STATUS] >= 0) {
16246             *p++ = boards[move][EP_STATUS] + AAA;
16247             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16248         } else {
16249             *p++ = '-';
16250         }
16251     } else {
16252         *p++ = '-';
16253     }
16254     *p++ = ' ';
16255   }
16256   }
16257
16258     /* [HGM] find reversible plies */
16259     {   int i = 0, j=move;
16260
16261         if (appData.debugMode) { int k;
16262             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16263             for(k=backwardMostMove; k<=forwardMostMove; k++)
16264                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16265
16266         }
16267
16268         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16269         if( j == backwardMostMove ) i += initialRulePlies;
16270         sprintf(p, "%d ", i);
16271         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16272     }
16273     /* Fullmove number */
16274     sprintf(p, "%d", (move / 2) + 1);
16275
16276     return StrSave(buf);
16277 }
16278
16279 Boolean
16280 ParseFEN(board, blackPlaysFirst, fen)
16281     Board board;
16282      int *blackPlaysFirst;
16283      char *fen;
16284 {
16285     int i, j;
16286     char *p, c;
16287     int emptycount;
16288     ChessSquare piece;
16289
16290     p = fen;
16291
16292     /* [HGM] by default clear Crazyhouse holdings, if present */
16293     if(gameInfo.holdingsWidth) {
16294        for(i=0; i<BOARD_HEIGHT; i++) {
16295            board[i][0]             = EmptySquare; /* black holdings */
16296            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16297            board[i][1]             = (ChessSquare) 0; /* black counts */
16298            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16299        }
16300     }
16301
16302     /* Piece placement data */
16303     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16304         j = 0;
16305         for (;;) {
16306             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16307                 if (*p == '/') p++;
16308                 emptycount = gameInfo.boardWidth - j;
16309                 while (emptycount--)
16310                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16311                 break;
16312 #if(BOARD_FILES >= 10)
16313             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16314                 p++; emptycount=10;
16315                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16316                 while (emptycount--)
16317                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16318 #endif
16319             } else if (isdigit(*p)) {
16320                 emptycount = *p++ - '0';
16321                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16322                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16323                 while (emptycount--)
16324                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16325             } else if (*p == '+' || isalpha(*p)) {
16326                 if (j >= gameInfo.boardWidth) return FALSE;
16327                 if(*p=='+') {
16328                     piece = CharToPiece(*++p);
16329                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16330                     piece = (ChessSquare) (PROMOTED piece ); p++;
16331                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16332                 } else piece = CharToPiece(*p++);
16333
16334                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16335                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16336                     piece = (ChessSquare) (PROMOTED piece);
16337                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16338                     p++;
16339                 }
16340                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16341             } else {
16342                 return FALSE;
16343             }
16344         }
16345     }
16346     while (*p == '/' || *p == ' ') p++;
16347
16348     /* [HGM] look for Crazyhouse holdings here */
16349     while(*p==' ') p++;
16350     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16351         if(*p == '[') p++;
16352         if(*p == '-' ) p++; /* empty holdings */ else {
16353             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16354             /* if we would allow FEN reading to set board size, we would   */
16355             /* have to add holdings and shift the board read so far here   */
16356             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16357                 p++;
16358                 if((int) piece >= (int) BlackPawn ) {
16359                     i = (int)piece - (int)BlackPawn;
16360                     i = PieceToNumber((ChessSquare)i);
16361                     if( i >= gameInfo.holdingsSize ) return FALSE;
16362                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16363                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16364                 } else {
16365                     i = (int)piece - (int)WhitePawn;
16366                     i = PieceToNumber((ChessSquare)i);
16367                     if( i >= gameInfo.holdingsSize ) return FALSE;
16368                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16369                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16370                 }
16371             }
16372         }
16373         if(*p == ']') p++;
16374     }
16375
16376     while(*p == ' ') p++;
16377
16378     /* Active color */
16379     c = *p++;
16380     if(appData.colorNickNames) {
16381       if( c == appData.colorNickNames[0] ) c = 'w'; else
16382       if( c == appData.colorNickNames[1] ) c = 'b';
16383     }
16384     switch (c) {
16385       case 'w':
16386         *blackPlaysFirst = FALSE;
16387         break;
16388       case 'b':
16389         *blackPlaysFirst = TRUE;
16390         break;
16391       default:
16392         return FALSE;
16393     }
16394
16395     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16396     /* return the extra info in global variiables             */
16397
16398     /* set defaults in case FEN is incomplete */
16399     board[EP_STATUS] = EP_UNKNOWN;
16400     for(i=0; i<nrCastlingRights; i++ ) {
16401         board[CASTLING][i] =
16402             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16403     }   /* assume possible unless obviously impossible */
16404     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16405     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16406     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16407                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16408     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16409     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16410     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16411                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16412     FENrulePlies = 0;
16413
16414     while(*p==' ') p++;
16415     if(nrCastlingRights) {
16416       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16417           /* castling indicator present, so default becomes no castlings */
16418           for(i=0; i<nrCastlingRights; i++ ) {
16419                  board[CASTLING][i] = NoRights;
16420           }
16421       }
16422       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16423              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16424              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16425              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16426         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16427
16428         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16429             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16430             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16431         }
16432         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16433             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16434         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16435                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16436         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16437                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16438         switch(c) {
16439           case'K':
16440               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16441               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16442               board[CASTLING][2] = whiteKingFile;
16443               break;
16444           case'Q':
16445               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16446               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16447               board[CASTLING][2] = whiteKingFile;
16448               break;
16449           case'k':
16450               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16451               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16452               board[CASTLING][5] = blackKingFile;
16453               break;
16454           case'q':
16455               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16456               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16457               board[CASTLING][5] = blackKingFile;
16458           case '-':
16459               break;
16460           default: /* FRC castlings */
16461               if(c >= 'a') { /* black rights */
16462                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16463                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16464                   if(i == BOARD_RGHT) break;
16465                   board[CASTLING][5] = i;
16466                   c -= AAA;
16467                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16468                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16469                   if(c > i)
16470                       board[CASTLING][3] = c;
16471                   else
16472                       board[CASTLING][4] = c;
16473               } else { /* white rights */
16474                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16475                     if(board[0][i] == WhiteKing) break;
16476                   if(i == BOARD_RGHT) break;
16477                   board[CASTLING][2] = i;
16478                   c -= AAA - 'a' + 'A';
16479                   if(board[0][c] >= WhiteKing) break;
16480                   if(c > i)
16481                       board[CASTLING][0] = c;
16482                   else
16483                       board[CASTLING][1] = c;
16484               }
16485         }
16486       }
16487       for(i=0; i<nrCastlingRights; i++)
16488         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16489     if (appData.debugMode) {
16490         fprintf(debugFP, "FEN castling rights:");
16491         for(i=0; i<nrCastlingRights; i++)
16492         fprintf(debugFP, " %d", board[CASTLING][i]);
16493         fprintf(debugFP, "\n");
16494     }
16495
16496       while(*p==' ') p++;
16497     }
16498
16499     /* read e.p. field in games that know e.p. capture */
16500     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16501        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16502       if(*p=='-') {
16503         p++; board[EP_STATUS] = EP_NONE;
16504       } else {
16505          char c = *p++ - AAA;
16506
16507          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16508          if(*p >= '0' && *p <='9') p++;
16509          board[EP_STATUS] = c;
16510       }
16511     }
16512
16513
16514     if(sscanf(p, "%d", &i) == 1) {
16515         FENrulePlies = i; /* 50-move ply counter */
16516         /* (The move number is still ignored)    */
16517     }
16518
16519     return TRUE;
16520 }
16521
16522 void
16523 EditPositionPasteFEN(char *fen)
16524 {
16525   if (fen != NULL) {
16526     Board initial_position;
16527
16528     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16529       DisplayError(_("Bad FEN position in clipboard"), 0);
16530       return ;
16531     } else {
16532       int savedBlackPlaysFirst = blackPlaysFirst;
16533       EditPositionEvent();
16534       blackPlaysFirst = savedBlackPlaysFirst;
16535       CopyBoard(boards[0], initial_position);
16536       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16537       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16538       DisplayBothClocks();
16539       DrawPosition(FALSE, boards[currentMove]);
16540     }
16541   }
16542 }
16543
16544 static char cseq[12] = "\\   ";
16545
16546 Boolean set_cont_sequence(char *new_seq)
16547 {
16548     int len;
16549     Boolean ret;
16550
16551     // handle bad attempts to set the sequence
16552         if (!new_seq)
16553                 return 0; // acceptable error - no debug
16554
16555     len = strlen(new_seq);
16556     ret = (len > 0) && (len < sizeof(cseq));
16557     if (ret)
16558       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16559     else if (appData.debugMode)
16560       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16561     return ret;
16562 }
16563
16564 /*
16565     reformat a source message so words don't cross the width boundary.  internal
16566     newlines are not removed.  returns the wrapped size (no null character unless
16567     included in source message).  If dest is NULL, only calculate the size required
16568     for the dest buffer.  lp argument indicats line position upon entry, and it's
16569     passed back upon exit.
16570 */
16571 int wrap(char *dest, char *src, int count, int width, int *lp)
16572 {
16573     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16574
16575     cseq_len = strlen(cseq);
16576     old_line = line = *lp;
16577     ansi = len = clen = 0;
16578
16579     for (i=0; i < count; i++)
16580     {
16581         if (src[i] == '\033')
16582             ansi = 1;
16583
16584         // if we hit the width, back up
16585         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16586         {
16587             // store i & len in case the word is too long
16588             old_i = i, old_len = len;
16589
16590             // find the end of the last word
16591             while (i && src[i] != ' ' && src[i] != '\n')
16592             {
16593                 i--;
16594                 len--;
16595             }
16596
16597             // word too long?  restore i & len before splitting it
16598             if ((old_i-i+clen) >= width)
16599             {
16600                 i = old_i;
16601                 len = old_len;
16602             }
16603
16604             // extra space?
16605             if (i && src[i-1] == ' ')
16606                 len--;
16607
16608             if (src[i] != ' ' && src[i] != '\n')
16609             {
16610                 i--;
16611                 if (len)
16612                     len--;
16613             }
16614
16615             // now append the newline and continuation sequence
16616             if (dest)
16617                 dest[len] = '\n';
16618             len++;
16619             if (dest)
16620                 strncpy(dest+len, cseq, cseq_len);
16621             len += cseq_len;
16622             line = cseq_len;
16623             clen = cseq_len;
16624             continue;
16625         }
16626
16627         if (dest)
16628             dest[len] = src[i];
16629         len++;
16630         if (!ansi)
16631             line++;
16632         if (src[i] == '\n')
16633             line = 0;
16634         if (src[i] == 'm')
16635             ansi = 0;
16636     }
16637     if (dest && appData.debugMode)
16638     {
16639         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16640             count, width, line, len, *lp);
16641         show_bytes(debugFP, src, count);
16642         fprintf(debugFP, "\ndest: ");
16643         show_bytes(debugFP, dest, len);
16644         fprintf(debugFP, "\n");
16645     }
16646     *lp = dest ? line : old_line;
16647
16648     return len;
16649 }
16650
16651 // [HGM] vari: routines for shelving variations
16652 Boolean modeRestore = FALSE;
16653
16654 void
16655 PushInner(int firstMove, int lastMove)
16656 {
16657         int i, j, nrMoves = lastMove - firstMove;
16658
16659         // push current tail of game on stack
16660         savedResult[storedGames] = gameInfo.result;
16661         savedDetails[storedGames] = gameInfo.resultDetails;
16662         gameInfo.resultDetails = NULL;
16663         savedFirst[storedGames] = firstMove;
16664         savedLast [storedGames] = lastMove;
16665         savedFramePtr[storedGames] = framePtr;
16666         framePtr -= nrMoves; // reserve space for the boards
16667         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16668             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16669             for(j=0; j<MOVE_LEN; j++)
16670                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16671             for(j=0; j<2*MOVE_LEN; j++)
16672                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16673             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16674             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16675             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16676             pvInfoList[firstMove+i-1].depth = 0;
16677             commentList[framePtr+i] = commentList[firstMove+i];
16678             commentList[firstMove+i] = NULL;
16679         }
16680
16681         storedGames++;
16682         forwardMostMove = firstMove; // truncate game so we can start variation
16683 }
16684
16685 void
16686 PushTail(int firstMove, int lastMove)
16687 {
16688         if(appData.icsActive) { // only in local mode
16689                 forwardMostMove = currentMove; // mimic old ICS behavior
16690                 return;
16691         }
16692         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16693
16694         PushInner(firstMove, lastMove);
16695         if(storedGames == 1) GreyRevert(FALSE);
16696         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16697 }
16698
16699 void
16700 PopInner(Boolean annotate)
16701 {
16702         int i, j, nrMoves;
16703         char buf[8000], moveBuf[20];
16704
16705         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16706         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16707         nrMoves = savedLast[storedGames] - currentMove;
16708         if(annotate) {
16709                 int cnt = 10;
16710                 if(!WhiteOnMove(currentMove))
16711                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16712                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16713                 for(i=currentMove; i<forwardMostMove; i++) {
16714                         if(WhiteOnMove(i))
16715                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16716                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16717                         strcat(buf, moveBuf);
16718                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16719                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16720                 }
16721                 strcat(buf, ")");
16722         }
16723         for(i=1; i<=nrMoves; i++) { // copy last variation back
16724             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16725             for(j=0; j<MOVE_LEN; j++)
16726                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16727             for(j=0; j<2*MOVE_LEN; j++)
16728                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16729             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16730             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16731             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16732             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16733             commentList[currentMove+i] = commentList[framePtr+i];
16734             commentList[framePtr+i] = NULL;
16735         }
16736         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16737         framePtr = savedFramePtr[storedGames];
16738         gameInfo.result = savedResult[storedGames];
16739         if(gameInfo.resultDetails != NULL) {
16740             free(gameInfo.resultDetails);
16741       }
16742         gameInfo.resultDetails = savedDetails[storedGames];
16743         forwardMostMove = currentMove + nrMoves;
16744 }
16745
16746 Boolean
16747 PopTail(Boolean annotate)
16748 {
16749         if(appData.icsActive) return FALSE; // only in local mode
16750         if(!storedGames) return FALSE; // sanity
16751         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16752
16753         PopInner(annotate);
16754         if(currentMove < forwardMostMove) ForwardEvent(); else
16755         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16756
16757         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16758         return TRUE;
16759 }
16760
16761 void
16762 CleanupTail()
16763 {       // remove all shelved variations
16764         int i;
16765         for(i=0; i<storedGames; i++) {
16766             if(savedDetails[i])
16767                 free(savedDetails[i]);
16768             savedDetails[i] = NULL;
16769         }
16770         for(i=framePtr; i<MAX_MOVES; i++) {
16771                 if(commentList[i]) free(commentList[i]);
16772                 commentList[i] = NULL;
16773         }
16774         framePtr = MAX_MOVES-1;
16775         storedGames = 0;
16776 }
16777
16778 void
16779 LoadVariation(int index, char *text)
16780 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16781         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16782         int level = 0, move;
16783
16784         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16785         // first find outermost bracketing variation
16786         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16787             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16788                 if(*p == '{') wait = '}'; else
16789                 if(*p == '[') wait = ']'; else
16790                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16791                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16792             }
16793             if(*p == wait) wait = NULLCHAR; // closing ]} found
16794             p++;
16795         }
16796         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16797         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16798         end[1] = NULLCHAR; // clip off comment beyond variation
16799         ToNrEvent(currentMove-1);
16800         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16801         // kludge: use ParsePV() to append variation to game
16802         move = currentMove;
16803         ParsePV(start, TRUE, TRUE);
16804         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16805         ClearPremoveHighlights();
16806         CommentPopDown();
16807         ToNrEvent(currentMove+1);
16808 }
16809