Ignore ICS game starts when already in game
[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 MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
172 void ShowMove P((int fromX, int fromY, int toX, int toY));
173 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
174                    /*char*/int promoChar));
175 void BackwardInner P((int target));
176 void ForwardInner P((int target));
177 int Adjudicate P((ChessProgramState *cps));
178 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
179 void EditPositionDone P((Boolean fakeRights));
180 void PrintOpponents P((FILE *fp));
181 void PrintPosition P((FILE *fp, int move));
182 void StartChessProgram P((ChessProgramState *cps));
183 void SendToProgram P((char *message, ChessProgramState *cps));
184 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
185 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
186                            char *buf, int count, int error));
187 void SendTimeControl P((ChessProgramState *cps,
188                         int mps, long tc, int inc, int sd, int st));
189 char *TimeControlTagValue P((void));
190 void Attention P((ChessProgramState *cps));
191 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
192 int ResurrectChessProgram P((void));
193 void DisplayComment P((int moveNumber, char *text));
194 void DisplayMove P((int moveNumber));
195
196 void ParseGameHistory P((char *game));
197 void ParseBoard12 P((char *string));
198 void KeepAlive P((void));
199 void StartClocks P((void));
200 void SwitchClocks P((int nr));
201 void StopClocks P((void));
202 void ResetClocks P((void));
203 char *PGNDate P((void));
204 void SetGameInfo P((void));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228 void NextMatchGame P((void));
229 int NextTourneyGame P((int nr, int *swap));
230 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
231 FILE *WriteTourneyFile P((char *results, FILE *f));
232 void DisplayTwoMachinesTitle P(());
233
234 #ifdef WIN32
235        extern void ConsoleCreate();
236 #endif
237
238 ChessProgramState *WhitePlayer();
239 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
240 int VerifyDisplayMode P(());
241
242 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
243 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
244 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
245 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
246 void ics_update_width P((int new_width));
247 extern char installDir[MSG_SIZ];
248 VariantClass startVariant; /* [HGM] nicks: initial variant */
249 Boolean abortMatch;
250
251 extern int tinyLayout, smallLayout;
252 ChessProgramStats programStats;
253 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
254 int endPV = -1;
255 static int exiting = 0; /* [HGM] moved to top */
256 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
257 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
258 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
259 int partnerHighlight[2];
260 Boolean partnerBoardValid = 0;
261 char partnerStatus[MSG_SIZ];
262 Boolean partnerUp;
263 Boolean originalFlip;
264 Boolean twoBoards = 0;
265 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
266 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
267 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
268 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
269 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
270 int opponentKibitzes;
271 int lastSavedGame; /* [HGM] save: ID of game */
272 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
273 extern int chatCount;
274 int chattingPartner;
275 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
276 char lastMsg[MSG_SIZ];
277 ChessSquare pieceSweep = EmptySquare;
278 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
279 int promoDefaultAltered;
280
281 /* States for ics_getting_history */
282 #define H_FALSE 0
283 #define H_REQUESTED 1
284 #define H_GOT_REQ_HEADER 2
285 #define H_GOT_UNREQ_HEADER 3
286 #define H_GETTING_MOVES 4
287 #define H_GOT_UNWANTED_HEADER 5
288
289 /* whosays values for GameEnds */
290 #define GE_ICS 0
291 #define GE_ENGINE 1
292 #define GE_PLAYER 2
293 #define GE_FILE 3
294 #define GE_XBOARD 4
295 #define GE_ENGINE1 5
296 #define GE_ENGINE2 6
297
298 /* Maximum number of games in a cmail message */
299 #define CMAIL_MAX_GAMES 20
300
301 /* Different types of move when calling RegisterMove */
302 #define CMAIL_MOVE   0
303 #define CMAIL_RESIGN 1
304 #define CMAIL_DRAW   2
305 #define CMAIL_ACCEPT 3
306
307 /* Different types of result to remember for each game */
308 #define CMAIL_NOT_RESULT 0
309 #define CMAIL_OLD_RESULT 1
310 #define CMAIL_NEW_RESULT 2
311
312 /* Telnet protocol constants */
313 #define TN_WILL 0373
314 #define TN_WONT 0374
315 #define TN_DO   0375
316 #define TN_DONT 0376
317 #define TN_IAC  0377
318 #define TN_ECHO 0001
319 #define TN_SGA  0003
320 #define TN_PORT 23
321
322 char*
323 safeStrCpy( char *dst, const char *src, size_t count )
324 { // [HGM] made safe
325   int i;
326   assert( dst != NULL );
327   assert( src != NULL );
328   assert( count > 0 );
329
330   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
331   if(  i == count && dst[count-1] != NULLCHAR)
332     {
333       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
334       if(appData.debugMode)
335       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
336     }
337
338   return dst;
339 }
340
341 /* Some compiler can't cast u64 to double
342  * This function do the job for us:
343
344  * We use the highest bit for cast, this only
345  * works if the highest bit is not
346  * in use (This should not happen)
347  *
348  * We used this for all compiler
349  */
350 double
351 u64ToDouble(u64 value)
352 {
353   double r;
354   u64 tmp = value & u64Const(0x7fffffffffffffff);
355   r = (double)(s64)tmp;
356   if (value & u64Const(0x8000000000000000))
357        r +=  9.2233720368547758080e18; /* 2^63 */
358  return r;
359 }
360
361 /* Fake up flags for now, as we aren't keeping track of castling
362    availability yet. [HGM] Change of logic: the flag now only
363    indicates the type of castlings allowed by the rule of the game.
364    The actual rights themselves are maintained in the array
365    castlingRights, as part of the game history, and are not probed
366    by this function.
367  */
368 int
369 PosFlags(index)
370 {
371   int flags = F_ALL_CASTLE_OK;
372   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
373   switch (gameInfo.variant) {
374   case VariantSuicide:
375     flags &= ~F_ALL_CASTLE_OK;
376   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
377     flags |= F_IGNORE_CHECK;
378   case VariantLosers:
379     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
380     break;
381   case VariantAtomic:
382     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
383     break;
384   case VariantKriegspiel:
385     flags |= F_KRIEGSPIEL_CAPTURE;
386     break;
387   case VariantCapaRandom:
388   case VariantFischeRandom:
389     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
390   case VariantNoCastle:
391   case VariantShatranj:
392   case VariantCourier:
393   case VariantMakruk:
394   case VariantGrand:
395     flags &= ~F_ALL_CASTLE_OK;
396     break;
397   default:
398     break;
399   }
400   return flags;
401 }
402
403 FILE *gameFileFP, *debugFP;
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second, pairing;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 int ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562
563 #if (BOARD_FILES>=10)
564 ChessSquare ShogiArray[2][BOARD_FILES] = {
565     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
566         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
567     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
568         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
569 };
570
571 ChessSquare XiangqiArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
573         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
575         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
576 };
577
578 ChessSquare CapablancaArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
580         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
582         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
583 };
584
585 ChessSquare GreatArray[2][BOARD_FILES] = {
586     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
587         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
588     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
589         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
590 };
591
592 ChessSquare JanusArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
594         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
595     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
596         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
597 };
598
599 ChessSquare GrandArray[2][BOARD_FILES] = {
600     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
601         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
602     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
603         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
604 };
605
606 #ifdef GOTHIC
607 ChessSquare GothicArray[2][BOARD_FILES] = {
608     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
609         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
610     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
611         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
612 };
613 #else // !GOTHIC
614 #define GothicArray CapablancaArray
615 #endif // !GOTHIC
616
617 #ifdef FALCON
618 ChessSquare FalconArray[2][BOARD_FILES] = {
619     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
620         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
621     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
622         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
623 };
624 #else // !FALCON
625 #define FalconArray CapablancaArray
626 #endif // !FALCON
627
628 #else // !(BOARD_FILES>=10)
629 #define XiangqiPosition FIDEArray
630 #define CapablancaArray FIDEArray
631 #define GothicArray FIDEArray
632 #define GreatArray FIDEArray
633 #endif // !(BOARD_FILES>=10)
634
635 #if (BOARD_FILES>=12)
636 ChessSquare CourierArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
638         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
639     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
640         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
641 };
642 #else // !(BOARD_FILES>=12)
643 #define CourierArray CapablancaArray
644 #endif // !(BOARD_FILES>=12)
645
646
647 Board initialPosition;
648
649
650 /* Convert str to a rating. Checks for special cases of "----",
651
652    "++++", etc. Also strips ()'s */
653 int
654 string_to_rating(str)
655   char *str;
656 {
657   while(*str && !isdigit(*str)) ++str;
658   if (!*str)
659     return 0;   /* One of the special "no rating" cases */
660   else
661     return atoi(str);
662 }
663
664 void
665 ClearProgramStats()
666 {
667     /* Init programStats */
668     programStats.movelist[0] = 0;
669     programStats.depth = 0;
670     programStats.nr_moves = 0;
671     programStats.moves_left = 0;
672     programStats.nodes = 0;
673     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
674     programStats.score = 0;
675     programStats.got_only_move = 0;
676     programStats.got_fail = 0;
677     programStats.line_is_book = 0;
678 }
679
680 void
681 CommonEngineInit()
682 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
683     if (appData.firstPlaysBlack) {
684         first.twoMachinesColor = "black\n";
685         second.twoMachinesColor = "white\n";
686     } else {
687         first.twoMachinesColor = "white\n";
688         second.twoMachinesColor = "black\n";
689     }
690
691     first.other = &second;
692     second.other = &first;
693
694     { float norm = 1;
695         if(appData.timeOddsMode) {
696             norm = appData.timeOdds[0];
697             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
698         }
699         first.timeOdds  = appData.timeOdds[0]/norm;
700         second.timeOdds = appData.timeOdds[1]/norm;
701     }
702
703     if(programVersion) free(programVersion);
704     if (appData.noChessProgram) {
705         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
706         sprintf(programVersion, "%s", PACKAGE_STRING);
707     } else {
708       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
709       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
710       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
711     }
712 }
713
714 void
715 UnloadEngine(ChessProgramState *cps)
716 {
717         /* Kill off first chess program */
718         if (cps->isr != NULL)
719           RemoveInputSource(cps->isr);
720         cps->isr = NULL;
721
722         if (cps->pr != NoProc) {
723             ExitAnalyzeMode();
724             DoSleep( appData.delayBeforeQuit );
725             SendToProgram("quit\n", cps);
726             DoSleep( appData.delayAfterQuit );
727             DestroyChildProcess(cps->pr, cps->useSigterm);
728         }
729         cps->pr = NoProc;
730         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
731 }
732
733 void
734 ClearOptions(ChessProgramState *cps)
735 {
736     int i;
737     cps->nrOptions = cps->comboCnt = 0;
738     for(i=0; i<MAX_OPTIONS; i++) {
739         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
740         cps->option[i].textValue = 0;
741     }
742 }
743
744 char *engineNames[] = {
745 "first",
746 "second"
747 };
748
749 void
750 InitEngine(ChessProgramState *cps, int n)
751 {   // [HGM] all engine initialiation put in a function that does one engine
752
753     ClearOptions(cps);
754
755     cps->which = engineNames[n];
756     cps->maybeThinking = FALSE;
757     cps->pr = NoProc;
758     cps->isr = NULL;
759     cps->sendTime = 2;
760     cps->sendDrawOffers = 1;
761
762     cps->program = appData.chessProgram[n];
763     cps->host = appData.host[n];
764     cps->dir = appData.directory[n];
765     cps->initString = appData.engInitString[n];
766     cps->computerString = appData.computerString[n];
767     cps->useSigint  = TRUE;
768     cps->useSigterm = TRUE;
769     cps->reuse = appData.reuse[n];
770     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
771     cps->useSetboard = FALSE;
772     cps->useSAN = FALSE;
773     cps->usePing = FALSE;
774     cps->lastPing = 0;
775     cps->lastPong = 0;
776     cps->usePlayother = FALSE;
777     cps->useColors = TRUE;
778     cps->useUsermove = FALSE;
779     cps->sendICS = FALSE;
780     cps->sendName = appData.icsActive;
781     cps->sdKludge = FALSE;
782     cps->stKludge = FALSE;
783     TidyProgramName(cps->program, cps->host, cps->tidy);
784     cps->matchWins = 0;
785     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786     cps->analysisSupport = 2; /* detect */
787     cps->analyzing = FALSE;
788     cps->initDone = FALSE;
789
790     /* New features added by Tord: */
791     cps->useFEN960 = FALSE;
792     cps->useOOCastle = TRUE;
793     /* End of new features added by Tord. */
794     cps->fenOverride  = appData.fenOverride[n];
795
796     /* [HGM] time odds: set factor for each machine */
797     cps->timeOdds  = appData.timeOdds[n];
798
799     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
800     cps->accumulateTC = appData.accumulateTC[n];
801     cps->maxNrOfSessions = 1;
802
803     /* [HGM] debug */
804     cps->debug = FALSE;
805
806     cps->supportsNPS = UNKNOWN;
807     cps->memSize = FALSE;
808     cps->maxCores = FALSE;
809     cps->egtFormats[0] = NULLCHAR;
810
811     /* [HGM] options */
812     cps->optionSettings  = appData.engOptions[n];
813
814     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
815     cps->isUCI = appData.isUCI[n]; /* [AS] */
816     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
817
818     if (appData.protocolVersion[n] > PROTOVER
819         || appData.protocolVersion[n] < 1)
820       {
821         char buf[MSG_SIZ];
822         int len;
823
824         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
825                        appData.protocolVersion[n]);
826         if( (len > MSG_SIZ) && appData.debugMode )
827           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
828
829         DisplayFatalError(buf, 0, 2);
830       }
831     else
832       {
833         cps->protocolVersion = appData.protocolVersion[n];
834       }
835
836     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
837     ParseFeatures(appData.featureDefaults, cps);
838 }
839
840 ChessProgramState *savCps;
841
842 void
843 LoadEngine()
844 {
845     int i;
846     if(WaitForEngine(savCps, LoadEngine)) return;
847     CommonEngineInit(); // recalculate time odds
848     if(gameInfo.variant != StringToVariant(appData.variant)) {
849         // we changed variant when loading the engine; this forces us to reset
850         Reset(TRUE, savCps != &first);
851         EditGameEvent(); // for consistency with other path, as Reset changes mode
852     }
853     InitChessProgram(savCps, FALSE);
854     SendToProgram("force\n", savCps);
855     DisplayMessage("", "");
856     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
857     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
858     ThawUI();
859     SetGNUMode();
860 }
861
862 void
863 ReplaceEngine(ChessProgramState *cps, int n)
864 {
865     EditGameEvent();
866     UnloadEngine(cps);
867     appData.noChessProgram = FALSE;
868     appData.clockMode = TRUE;
869     InitEngine(cps, n);
870     UpdateLogos(TRUE);
871     if(n) return; // only startup first engine immediately; second can wait
872     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
873     LoadEngine();
874 }
875
876 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
877 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
878
879 static char resetOptions[] = 
880         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
881         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
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; appData.pvSAN[0] = FALSE;
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 void
1666 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1667 {
1668     DisplayBook(current+1);
1669
1670     MoveHistorySet( movelist, first, last, current, pvInfoList );
1671
1672     EvalGraphSet( first, last, current, pvInfoList );
1673
1674     MakeEngineOutputTitle();
1675 }
1676
1677 /*
1678  * Establish will establish a contact to a remote host.port.
1679  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1680  *  used to talk to the host.
1681  * Returns 0 if okay, error code if not.
1682  */
1683 int
1684 establish()
1685 {
1686     char buf[MSG_SIZ];
1687
1688     if (*appData.icsCommPort != NULLCHAR) {
1689         /* Talk to the host through a serial comm port */
1690         return OpenCommPort(appData.icsCommPort, &icsPR);
1691
1692     } else if (*appData.gateway != NULLCHAR) {
1693         if (*appData.remoteShell == NULLCHAR) {
1694             /* Use the rcmd protocol to run telnet program on a gateway host */
1695             snprintf(buf, sizeof(buf), "%s %s %s",
1696                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1697             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1698
1699         } else {
1700             /* Use the rsh program to run telnet program on a gateway host */
1701             if (*appData.remoteUser == NULLCHAR) {
1702                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1703                         appData.gateway, appData.telnetProgram,
1704                         appData.icsHost, appData.icsPort);
1705             } else {
1706                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1707                         appData.remoteShell, appData.gateway,
1708                         appData.remoteUser, appData.telnetProgram,
1709                         appData.icsHost, appData.icsPort);
1710             }
1711             return StartChildProcess(buf, "", &icsPR);
1712
1713         }
1714     } else if (appData.useTelnet) {
1715         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1716
1717     } else {
1718         /* TCP socket interface differs somewhat between
1719            Unix and NT; handle details in the front end.
1720            */
1721         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1722     }
1723 }
1724
1725 void EscapeExpand(char *p, char *q)
1726 {       // [HGM] initstring: routine to shape up string arguments
1727         while(*p++ = *q++) if(p[-1] == '\\')
1728             switch(*q++) {
1729                 case 'n': p[-1] = '\n'; break;
1730                 case 'r': p[-1] = '\r'; break;
1731                 case 't': p[-1] = '\t'; break;
1732                 case '\\': p[-1] = '\\'; break;
1733                 case 0: *p = 0; return;
1734                 default: p[-1] = q[-1]; break;
1735             }
1736 }
1737
1738 void
1739 show_bytes(fp, buf, count)
1740      FILE *fp;
1741      char *buf;
1742      int count;
1743 {
1744     while (count--) {
1745         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1746             fprintf(fp, "\\%03o", *buf & 0xff);
1747         } else {
1748             putc(*buf, fp);
1749         }
1750         buf++;
1751     }
1752     fflush(fp);
1753 }
1754
1755 /* Returns an errno value */
1756 int
1757 OutputMaybeTelnet(pr, message, count, outError)
1758      ProcRef pr;
1759      char *message;
1760      int count;
1761      int *outError;
1762 {
1763     char buf[8192], *p, *q, *buflim;
1764     int left, newcount, outcount;
1765
1766     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1767         *appData.gateway != NULLCHAR) {
1768         if (appData.debugMode) {
1769             fprintf(debugFP, ">ICS: ");
1770             show_bytes(debugFP, message, count);
1771             fprintf(debugFP, "\n");
1772         }
1773         return OutputToProcess(pr, message, count, outError);
1774     }
1775
1776     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1777     p = message;
1778     q = buf;
1779     left = count;
1780     newcount = 0;
1781     while (left) {
1782         if (q >= buflim) {
1783             if (appData.debugMode) {
1784                 fprintf(debugFP, ">ICS: ");
1785                 show_bytes(debugFP, buf, newcount);
1786                 fprintf(debugFP, "\n");
1787             }
1788             outcount = OutputToProcess(pr, buf, newcount, outError);
1789             if (outcount < newcount) return -1; /* to be sure */
1790             q = buf;
1791             newcount = 0;
1792         }
1793         if (*p == '\n') {
1794             *q++ = '\r';
1795             newcount++;
1796         } else if (((unsigned char) *p) == TN_IAC) {
1797             *q++ = (char) TN_IAC;
1798             newcount ++;
1799         }
1800         *q++ = *p++;
1801         newcount++;
1802         left--;
1803     }
1804     if (appData.debugMode) {
1805         fprintf(debugFP, ">ICS: ");
1806         show_bytes(debugFP, buf, newcount);
1807         fprintf(debugFP, "\n");
1808     }
1809     outcount = OutputToProcess(pr, buf, newcount, outError);
1810     if (outcount < newcount) return -1; /* to be sure */
1811     return count;
1812 }
1813
1814 void
1815 read_from_player(isr, closure, message, count, error)
1816      InputSourceRef isr;
1817      VOIDSTAR closure;
1818      char *message;
1819      int count;
1820      int error;
1821 {
1822     int outError, outCount;
1823     static int gotEof = 0;
1824
1825     /* Pass data read from player on to ICS */
1826     if (count > 0) {
1827         gotEof = 0;
1828         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1829         if (outCount < count) {
1830             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1831         }
1832     } else if (count < 0) {
1833         RemoveInputSource(isr);
1834         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1835     } else if (gotEof++ > 0) {
1836         RemoveInputSource(isr);
1837         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1838     }
1839 }
1840
1841 void
1842 KeepAlive()
1843 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1844     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1845     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1846     SendToICS("date\n");
1847     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1848 }
1849
1850 /* added routine for printf style output to ics */
1851 void ics_printf(char *format, ...)
1852 {
1853     char buffer[MSG_SIZ];
1854     va_list args;
1855
1856     va_start(args, format);
1857     vsnprintf(buffer, sizeof(buffer), format, args);
1858     buffer[sizeof(buffer)-1] = '\0';
1859     SendToICS(buffer);
1860     va_end(args);
1861 }
1862
1863 void
1864 SendToICS(s)
1865      char *s;
1866 {
1867     int count, outCount, outError;
1868
1869     if (icsPR == NoProc) return;
1870
1871     count = strlen(s);
1872     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1873     if (outCount < count) {
1874         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1875     }
1876 }
1877
1878 /* This is used for sending logon scripts to the ICS. Sending
1879    without a delay causes problems when using timestamp on ICC
1880    (at least on my machine). */
1881 void
1882 SendToICSDelayed(s,msdelay)
1883      char *s;
1884      long msdelay;
1885 {
1886     int count, outCount, outError;
1887
1888     if (icsPR == NoProc) return;
1889
1890     count = strlen(s);
1891     if (appData.debugMode) {
1892         fprintf(debugFP, ">ICS: ");
1893         show_bytes(debugFP, s, count);
1894         fprintf(debugFP, "\n");
1895     }
1896     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1897                                       msdelay);
1898     if (outCount < count) {
1899         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900     }
1901 }
1902
1903
1904 /* Remove all highlighting escape sequences in s
1905    Also deletes any suffix starting with '('
1906    */
1907 char *
1908 StripHighlightAndTitle(s)
1909      char *s;
1910 {
1911     static char retbuf[MSG_SIZ];
1912     char *p = retbuf;
1913
1914     while (*s != NULLCHAR) {
1915         while (*s == '\033') {
1916             while (*s != NULLCHAR && !isalpha(*s)) s++;
1917             if (*s != NULLCHAR) s++;
1918         }
1919         while (*s != NULLCHAR && *s != '\033') {
1920             if (*s == '(' || *s == '[') {
1921                 *p = NULLCHAR;
1922                 return retbuf;
1923             }
1924             *p++ = *s++;
1925         }
1926     }
1927     *p = NULLCHAR;
1928     return retbuf;
1929 }
1930
1931 /* Remove all highlighting escape sequences in s */
1932 char *
1933 StripHighlight(s)
1934      char *s;
1935 {
1936     static char retbuf[MSG_SIZ];
1937     char *p = retbuf;
1938
1939     while (*s != NULLCHAR) {
1940         while (*s == '\033') {
1941             while (*s != NULLCHAR && !isalpha(*s)) s++;
1942             if (*s != NULLCHAR) s++;
1943         }
1944         while (*s != NULLCHAR && *s != '\033') {
1945             *p++ = *s++;
1946         }
1947     }
1948     *p = NULLCHAR;
1949     return retbuf;
1950 }
1951
1952 char *variantNames[] = VARIANT_NAMES;
1953 char *
1954 VariantName(v)
1955      VariantClass v;
1956 {
1957     return variantNames[v];
1958 }
1959
1960
1961 /* Identify a variant from the strings the chess servers use or the
1962    PGN Variant tag names we use. */
1963 VariantClass
1964 StringToVariant(e)
1965      char *e;
1966 {
1967     char *p;
1968     int wnum = -1;
1969     VariantClass v = VariantNormal;
1970     int i, found = FALSE;
1971     char buf[MSG_SIZ];
1972     int len;
1973
1974     if (!e) return v;
1975
1976     /* [HGM] skip over optional board-size prefixes */
1977     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1978         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1979         while( *e++ != '_');
1980     }
1981
1982     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1983         v = VariantNormal;
1984         found = TRUE;
1985     } else
1986     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1987       if (StrCaseStr(e, variantNames[i])) {
1988         v = (VariantClass) i;
1989         found = TRUE;
1990         break;
1991       }
1992     }
1993
1994     if (!found) {
1995       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1996           || StrCaseStr(e, "wild/fr")
1997           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1998         v = VariantFischeRandom;
1999       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2000                  (i = 1, p = StrCaseStr(e, "w"))) {
2001         p += i;
2002         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2003         if (isdigit(*p)) {
2004           wnum = atoi(p);
2005         } else {
2006           wnum = -1;
2007         }
2008         switch (wnum) {
2009         case 0: /* FICS only, actually */
2010         case 1:
2011           /* Castling legal even if K starts on d-file */
2012           v = VariantWildCastle;
2013           break;
2014         case 2:
2015         case 3:
2016         case 4:
2017           /* Castling illegal even if K & R happen to start in
2018              normal positions. */
2019           v = VariantNoCastle;
2020           break;
2021         case 5:
2022         case 7:
2023         case 8:
2024         case 10:
2025         case 11:
2026         case 12:
2027         case 13:
2028         case 14:
2029         case 15:
2030         case 18:
2031         case 19:
2032           /* Castling legal iff K & R start in normal positions */
2033           v = VariantNormal;
2034           break;
2035         case 6:
2036         case 20:
2037         case 21:
2038           /* Special wilds for position setup; unclear what to do here */
2039           v = VariantLoadable;
2040           break;
2041         case 9:
2042           /* Bizarre ICC game */
2043           v = VariantTwoKings;
2044           break;
2045         case 16:
2046           v = VariantKriegspiel;
2047           break;
2048         case 17:
2049           v = VariantLosers;
2050           break;
2051         case 22:
2052           v = VariantFischeRandom;
2053           break;
2054         case 23:
2055           v = VariantCrazyhouse;
2056           break;
2057         case 24:
2058           v = VariantBughouse;
2059           break;
2060         case 25:
2061           v = Variant3Check;
2062           break;
2063         case 26:
2064           /* Not quite the same as FICS suicide! */
2065           v = VariantGiveaway;
2066           break;
2067         case 27:
2068           v = VariantAtomic;
2069           break;
2070         case 28:
2071           v = VariantShatranj;
2072           break;
2073
2074         /* Temporary names for future ICC types.  The name *will* change in
2075            the next xboard/WinBoard release after ICC defines it. */
2076         case 29:
2077           v = Variant29;
2078           break;
2079         case 30:
2080           v = Variant30;
2081           break;
2082         case 31:
2083           v = Variant31;
2084           break;
2085         case 32:
2086           v = Variant32;
2087           break;
2088         case 33:
2089           v = Variant33;
2090           break;
2091         case 34:
2092           v = Variant34;
2093           break;
2094         case 35:
2095           v = Variant35;
2096           break;
2097         case 36:
2098           v = Variant36;
2099           break;
2100         case 37:
2101           v = VariantShogi;
2102           break;
2103         case 38:
2104           v = VariantXiangqi;
2105           break;
2106         case 39:
2107           v = VariantCourier;
2108           break;
2109         case 40:
2110           v = VariantGothic;
2111           break;
2112         case 41:
2113           v = VariantCapablanca;
2114           break;
2115         case 42:
2116           v = VariantKnightmate;
2117           break;
2118         case 43:
2119           v = VariantFairy;
2120           break;
2121         case 44:
2122           v = VariantCylinder;
2123           break;
2124         case 45:
2125           v = VariantFalcon;
2126           break;
2127         case 46:
2128           v = VariantCapaRandom;
2129           break;
2130         case 47:
2131           v = VariantBerolina;
2132           break;
2133         case 48:
2134           v = VariantJanus;
2135           break;
2136         case 49:
2137           v = VariantSuper;
2138           break;
2139         case 50:
2140           v = VariantGreat;
2141           break;
2142         case -1:
2143           /* Found "wild" or "w" in the string but no number;
2144              must assume it's normal chess. */
2145           v = VariantNormal;
2146           break;
2147         default:
2148           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2149           if( (len > MSG_SIZ) && appData.debugMode )
2150             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2151
2152           DisplayError(buf, 0);
2153           v = VariantUnknown;
2154           break;
2155         }
2156       }
2157     }
2158     if (appData.debugMode) {
2159       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2160               e, wnum, VariantName(v));
2161     }
2162     return v;
2163 }
2164
2165 static int leftover_start = 0, leftover_len = 0;
2166 char star_match[STAR_MATCH_N][MSG_SIZ];
2167
2168 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2169    advance *index beyond it, and set leftover_start to the new value of
2170    *index; else return FALSE.  If pattern contains the character '*', it
2171    matches any sequence of characters not containing '\r', '\n', or the
2172    character following the '*' (if any), and the matched sequence(s) are
2173    copied into star_match.
2174    */
2175 int
2176 looking_at(buf, index, pattern)
2177      char *buf;
2178      int *index;
2179      char *pattern;
2180 {
2181     char *bufp = &buf[*index], *patternp = pattern;
2182     int star_count = 0;
2183     char *matchp = star_match[0];
2184
2185     for (;;) {
2186         if (*patternp == NULLCHAR) {
2187             *index = leftover_start = bufp - buf;
2188             *matchp = NULLCHAR;
2189             return TRUE;
2190         }
2191         if (*bufp == NULLCHAR) return FALSE;
2192         if (*patternp == '*') {
2193             if (*bufp == *(patternp + 1)) {
2194                 *matchp = NULLCHAR;
2195                 matchp = star_match[++star_count];
2196                 patternp += 2;
2197                 bufp++;
2198                 continue;
2199             } else if (*bufp == '\n' || *bufp == '\r') {
2200                 patternp++;
2201                 if (*patternp == NULLCHAR)
2202                   continue;
2203                 else
2204                   return FALSE;
2205             } else {
2206                 *matchp++ = *bufp++;
2207                 continue;
2208             }
2209         }
2210         if (*patternp != *bufp) return FALSE;
2211         patternp++;
2212         bufp++;
2213     }
2214 }
2215
2216 void
2217 SendToPlayer(data, length)
2218      char *data;
2219      int length;
2220 {
2221     int error, outCount;
2222     outCount = OutputToProcess(NoProc, data, length, &error);
2223     if (outCount < length) {
2224         DisplayFatalError(_("Error writing to display"), error, 1);
2225     }
2226 }
2227
2228 void
2229 PackHolding(packed, holding)
2230      char packed[];
2231      char *holding;
2232 {
2233     char *p = holding;
2234     char *q = packed;
2235     int runlength = 0;
2236     int curr = 9999;
2237     do {
2238         if (*p == curr) {
2239             runlength++;
2240         } else {
2241             switch (runlength) {
2242               case 0:
2243                 break;
2244               case 1:
2245                 *q++ = curr;
2246                 break;
2247               case 2:
2248                 *q++ = curr;
2249                 *q++ = curr;
2250                 break;
2251               default:
2252                 sprintf(q, "%d", runlength);
2253                 while (*q) q++;
2254                 *q++ = curr;
2255                 break;
2256             }
2257             runlength = 1;
2258             curr = *p;
2259         }
2260     } while (*p++);
2261     *q = NULLCHAR;
2262 }
2263
2264 /* Telnet protocol requests from the front end */
2265 void
2266 TelnetRequest(ddww, option)
2267      unsigned char ddww, option;
2268 {
2269     unsigned char msg[3];
2270     int outCount, outError;
2271
2272     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2273
2274     if (appData.debugMode) {
2275         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2276         switch (ddww) {
2277           case TN_DO:
2278             ddwwStr = "DO";
2279             break;
2280           case TN_DONT:
2281             ddwwStr = "DONT";
2282             break;
2283           case TN_WILL:
2284             ddwwStr = "WILL";
2285             break;
2286           case TN_WONT:
2287             ddwwStr = "WONT";
2288             break;
2289           default:
2290             ddwwStr = buf1;
2291             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2292             break;
2293         }
2294         switch (option) {
2295           case TN_ECHO:
2296             optionStr = "ECHO";
2297             break;
2298           default:
2299             optionStr = buf2;
2300             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2301             break;
2302         }
2303         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2304     }
2305     msg[0] = TN_IAC;
2306     msg[1] = ddww;
2307     msg[2] = option;
2308     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2309     if (outCount < 3) {
2310         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2311     }
2312 }
2313
2314 void
2315 DoEcho()
2316 {
2317     if (!appData.icsActive) return;
2318     TelnetRequest(TN_DO, TN_ECHO);
2319 }
2320
2321 void
2322 DontEcho()
2323 {
2324     if (!appData.icsActive) return;
2325     TelnetRequest(TN_DONT, TN_ECHO);
2326 }
2327
2328 void
2329 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2330 {
2331     /* put the holdings sent to us by the server on the board holdings area */
2332     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2333     char p;
2334     ChessSquare piece;
2335
2336     if(gameInfo.holdingsWidth < 2)  return;
2337     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2338         return; // prevent overwriting by pre-board holdings
2339
2340     if( (int)lowestPiece >= BlackPawn ) {
2341         holdingsColumn = 0;
2342         countsColumn = 1;
2343         holdingsStartRow = BOARD_HEIGHT-1;
2344         direction = -1;
2345     } else {
2346         holdingsColumn = BOARD_WIDTH-1;
2347         countsColumn = BOARD_WIDTH-2;
2348         holdingsStartRow = 0;
2349         direction = 1;
2350     }
2351
2352     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2353         board[i][holdingsColumn] = EmptySquare;
2354         board[i][countsColumn]   = (ChessSquare) 0;
2355     }
2356     while( (p=*holdings++) != NULLCHAR ) {
2357         piece = CharToPiece( ToUpper(p) );
2358         if(piece == EmptySquare) continue;
2359         /*j = (int) piece - (int) WhitePawn;*/
2360         j = PieceToNumber(piece);
2361         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2362         if(j < 0) continue;               /* should not happen */
2363         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2364         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2365         board[holdingsStartRow+j*direction][countsColumn]++;
2366     }
2367 }
2368
2369
2370 void
2371 VariantSwitch(Board board, VariantClass newVariant)
2372 {
2373    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2374    static Board oldBoard;
2375
2376    startedFromPositionFile = FALSE;
2377    if(gameInfo.variant == newVariant) return;
2378
2379    /* [HGM] This routine is called each time an assignment is made to
2380     * gameInfo.variant during a game, to make sure the board sizes
2381     * are set to match the new variant. If that means adding or deleting
2382     * holdings, we shift the playing board accordingly
2383     * This kludge is needed because in ICS observe mode, we get boards
2384     * of an ongoing game without knowing the variant, and learn about the
2385     * latter only later. This can be because of the move list we requested,
2386     * in which case the game history is refilled from the beginning anyway,
2387     * but also when receiving holdings of a crazyhouse game. In the latter
2388     * case we want to add those holdings to the already received position.
2389     */
2390
2391
2392    if (appData.debugMode) {
2393      fprintf(debugFP, "Switch board from %s to %s\n",
2394              VariantName(gameInfo.variant), VariantName(newVariant));
2395      setbuf(debugFP, NULL);
2396    }
2397    shuffleOpenings = 0;       /* [HGM] shuffle */
2398    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2399    switch(newVariant)
2400      {
2401      case VariantShogi:
2402        newWidth = 9;  newHeight = 9;
2403        gameInfo.holdingsSize = 7;
2404      case VariantBughouse:
2405      case VariantCrazyhouse:
2406        newHoldingsWidth = 2; break;
2407      case VariantGreat:
2408        newWidth = 10;
2409      case VariantSuper:
2410        newHoldingsWidth = 2;
2411        gameInfo.holdingsSize = 8;
2412        break;
2413      case VariantGothic:
2414      case VariantCapablanca:
2415      case VariantCapaRandom:
2416        newWidth = 10;
2417      default:
2418        newHoldingsWidth = gameInfo.holdingsSize = 0;
2419      };
2420
2421    if(newWidth  != gameInfo.boardWidth  ||
2422       newHeight != gameInfo.boardHeight ||
2423       newHoldingsWidth != gameInfo.holdingsWidth ) {
2424
2425      /* shift position to new playing area, if needed */
2426      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2427        for(i=0; i<BOARD_HEIGHT; i++)
2428          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2429            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430              board[i][j];
2431        for(i=0; i<newHeight; i++) {
2432          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2433          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2434        }
2435      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2436        for(i=0; i<BOARD_HEIGHT; i++)
2437          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2438            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439              board[i][j];
2440      }
2441      gameInfo.boardWidth  = newWidth;
2442      gameInfo.boardHeight = newHeight;
2443      gameInfo.holdingsWidth = newHoldingsWidth;
2444      gameInfo.variant = newVariant;
2445      InitDrawingSizes(-2, 0);
2446    } else gameInfo.variant = newVariant;
2447    CopyBoard(oldBoard, board);   // remember correctly formatted board
2448      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2449    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2450 }
2451
2452 static int loggedOn = FALSE;
2453
2454 /*-- Game start info cache: --*/
2455 int gs_gamenum;
2456 char gs_kind[MSG_SIZ];
2457 static char player1Name[128] = "";
2458 static char player2Name[128] = "";
2459 static char cont_seq[] = "\n\\   ";
2460 static int player1Rating = -1;
2461 static int player2Rating = -1;
2462 /*----------------------------*/
2463
2464 ColorClass curColor = ColorNormal;
2465 int suppressKibitz = 0;
2466
2467 // [HGM] seekgraph
2468 Boolean soughtPending = FALSE;
2469 Boolean seekGraphUp;
2470 #define MAX_SEEK_ADS 200
2471 #define SQUARE 0x80
2472 char *seekAdList[MAX_SEEK_ADS];
2473 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2474 float tcList[MAX_SEEK_ADS];
2475 char colorList[MAX_SEEK_ADS];
2476 int nrOfSeekAds = 0;
2477 int minRating = 1010, maxRating = 2800;
2478 int hMargin = 10, vMargin = 20, h, w;
2479 extern int squareSize, lineGap;
2480
2481 void
2482 PlotSeekAd(int i)
2483 {
2484         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2485         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2486         if(r < minRating+100 && r >=0 ) r = minRating+100;
2487         if(r > maxRating) r = maxRating;
2488         if(tc < 1.) tc = 1.;
2489         if(tc > 95.) tc = 95.;
2490         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2491         y = ((double)r - minRating)/(maxRating - minRating)
2492             * (h-vMargin-squareSize/8-1) + vMargin;
2493         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2494         if(strstr(seekAdList[i], " u ")) color = 1;
2495         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2496            !strstr(seekAdList[i], "bullet") &&
2497            !strstr(seekAdList[i], "blitz") &&
2498            !strstr(seekAdList[i], "standard") ) color = 2;
2499         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2500         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2501 }
2502
2503 void
2504 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2505 {
2506         char buf[MSG_SIZ], *ext = "";
2507         VariantClass v = StringToVariant(type);
2508         if(strstr(type, "wild")) {
2509             ext = type + 4; // append wild number
2510             if(v == VariantFischeRandom) type = "chess960"; else
2511             if(v == VariantLoadable) type = "setup"; else
2512             type = VariantName(v);
2513         }
2514         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2515         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2516             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2517             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2518             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2519             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2520             seekNrList[nrOfSeekAds] = nr;
2521             zList[nrOfSeekAds] = 0;
2522             seekAdList[nrOfSeekAds++] = StrSave(buf);
2523             if(plot) PlotSeekAd(nrOfSeekAds-1);
2524         }
2525 }
2526
2527 void
2528 EraseSeekDot(int i)
2529 {
2530     int x = xList[i], y = yList[i], d=squareSize/4, k;
2531     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2532     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2533     // now replot every dot that overlapped
2534     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2535         int xx = xList[k], yy = yList[k];
2536         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2537             DrawSeekDot(xx, yy, colorList[k]);
2538     }
2539 }
2540
2541 void
2542 RemoveSeekAd(int nr)
2543 {
2544         int i;
2545         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2546             EraseSeekDot(i);
2547             if(seekAdList[i]) free(seekAdList[i]);
2548             seekAdList[i] = seekAdList[--nrOfSeekAds];
2549             seekNrList[i] = seekNrList[nrOfSeekAds];
2550             ratingList[i] = ratingList[nrOfSeekAds];
2551             colorList[i]  = colorList[nrOfSeekAds];
2552             tcList[i] = tcList[nrOfSeekAds];
2553             xList[i]  = xList[nrOfSeekAds];
2554             yList[i]  = yList[nrOfSeekAds];
2555             zList[i]  = zList[nrOfSeekAds];
2556             seekAdList[nrOfSeekAds] = NULL;
2557             break;
2558         }
2559 }
2560
2561 Boolean
2562 MatchSoughtLine(char *line)
2563 {
2564     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2565     int nr, base, inc, u=0; char dummy;
2566
2567     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2568        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2569        (u=1) &&
2570        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2571         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2572         // match: compact and save the line
2573         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2574         return TRUE;
2575     }
2576     return FALSE;
2577 }
2578
2579 int
2580 DrawSeekGraph()
2581 {
2582     int i;
2583     if(!seekGraphUp) return FALSE;
2584     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2585     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2586
2587     DrawSeekBackground(0, 0, w, h);
2588     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2589     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2590     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2591         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2592         yy = h-1-yy;
2593         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2594         if(i%500 == 0) {
2595             char buf[MSG_SIZ];
2596             snprintf(buf, MSG_SIZ, "%d", i);
2597             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2598         }
2599     }
2600     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2601     for(i=1; i<100; i+=(i<10?1:5)) {
2602         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2603         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2604         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2605             char buf[MSG_SIZ];
2606             snprintf(buf, MSG_SIZ, "%d", i);
2607             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2608         }
2609     }
2610     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2611     return TRUE;
2612 }
2613
2614 int SeekGraphClick(ClickType click, int x, int y, int moving)
2615 {
2616     static int lastDown = 0, displayed = 0, lastSecond;
2617     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2618         if(click == Release || moving) return FALSE;
2619         nrOfSeekAds = 0;
2620         soughtPending = TRUE;
2621         SendToICS(ics_prefix);
2622         SendToICS("sought\n"); // should this be "sought all"?
2623     } else { // issue challenge based on clicked ad
2624         int dist = 10000; int i, closest = 0, second = 0;
2625         for(i=0; i<nrOfSeekAds; i++) {
2626             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2627             if(d < dist) { dist = d; closest = i; }
2628             second += (d - zList[i] < 120); // count in-range ads
2629             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2630         }
2631         if(dist < 120) {
2632             char buf[MSG_SIZ];
2633             second = (second > 1);
2634             if(displayed != closest || second != lastSecond) {
2635                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2636                 lastSecond = second; displayed = closest;
2637             }
2638             if(click == Press) {
2639                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2640                 lastDown = closest;
2641                 return TRUE;
2642             } // on press 'hit', only show info
2643             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2644             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2645             SendToICS(ics_prefix);
2646             SendToICS(buf);
2647             return TRUE; // let incoming board of started game pop down the graph
2648         } else if(click == Release) { // release 'miss' is ignored
2649             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2650             if(moving == 2) { // right up-click
2651                 nrOfSeekAds = 0; // refresh graph
2652                 soughtPending = TRUE;
2653                 SendToICS(ics_prefix);
2654                 SendToICS("sought\n"); // should this be "sought all"?
2655             }
2656             return TRUE;
2657         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2658         // press miss or release hit 'pop down' seek graph
2659         seekGraphUp = FALSE;
2660         DrawPosition(TRUE, NULL);
2661     }
2662     return TRUE;
2663 }
2664
2665 void
2666 read_from_ics(isr, closure, data, count, error)
2667      InputSourceRef isr;
2668      VOIDSTAR closure;
2669      char *data;
2670      int count;
2671      int error;
2672 {
2673 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2674 #define STARTED_NONE 0
2675 #define STARTED_MOVES 1
2676 #define STARTED_BOARD 2
2677 #define STARTED_OBSERVE 3
2678 #define STARTED_HOLDINGS 4
2679 #define STARTED_CHATTER 5
2680 #define STARTED_COMMENT 6
2681 #define STARTED_MOVES_NOHIDE 7
2682
2683     static int started = STARTED_NONE;
2684     static char parse[20000];
2685     static int parse_pos = 0;
2686     static char buf[BUF_SIZE + 1];
2687     static int firstTime = TRUE, intfSet = FALSE;
2688     static ColorClass prevColor = ColorNormal;
2689     static int savingComment = FALSE;
2690     static int cmatch = 0; // continuation sequence match
2691     char *bp;
2692     char str[MSG_SIZ];
2693     int i, oldi;
2694     int buf_len;
2695     int next_out;
2696     int tkind;
2697     int backup;    /* [DM] For zippy color lines */
2698     char *p;
2699     char talker[MSG_SIZ]; // [HGM] chat
2700     int channel;
2701
2702     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2703
2704     if (appData.debugMode) {
2705       if (!error) {
2706         fprintf(debugFP, "<ICS: ");
2707         show_bytes(debugFP, data, count);
2708         fprintf(debugFP, "\n");
2709       }
2710     }
2711
2712     if (appData.debugMode) { int f = forwardMostMove;
2713         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2714                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2715                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2716     }
2717     if (count > 0) {
2718         /* If last read ended with a partial line that we couldn't parse,
2719            prepend it to the new read and try again. */
2720         if (leftover_len > 0) {
2721             for (i=0; i<leftover_len; i++)
2722               buf[i] = buf[leftover_start + i];
2723         }
2724
2725     /* copy new characters into the buffer */
2726     bp = buf + leftover_len;
2727     buf_len=leftover_len;
2728     for (i=0; i<count; i++)
2729     {
2730         // ignore these
2731         if (data[i] == '\r')
2732             continue;
2733
2734         // join lines split by ICS?
2735         if (!appData.noJoin)
2736         {
2737             /*
2738                 Joining just consists of finding matches against the
2739                 continuation sequence, and discarding that sequence
2740                 if found instead of copying it.  So, until a match
2741                 fails, there's nothing to do since it might be the
2742                 complete sequence, and thus, something we don't want
2743                 copied.
2744             */
2745             if (data[i] == cont_seq[cmatch])
2746             {
2747                 cmatch++;
2748                 if (cmatch == strlen(cont_seq))
2749                 {
2750                     cmatch = 0; // complete match.  just reset the counter
2751
2752                     /*
2753                         it's possible for the ICS to not include the space
2754                         at the end of the last word, making our [correct]
2755                         join operation fuse two separate words.  the server
2756                         does this when the space occurs at the width setting.
2757                     */
2758                     if (!buf_len || buf[buf_len-1] != ' ')
2759                     {
2760                         *bp++ = ' ';
2761                         buf_len++;
2762                     }
2763                 }
2764                 continue;
2765             }
2766             else if (cmatch)
2767             {
2768                 /*
2769                     match failed, so we have to copy what matched before
2770                     falling through and copying this character.  In reality,
2771                     this will only ever be just the newline character, but
2772                     it doesn't hurt to be precise.
2773                 */
2774                 strncpy(bp, cont_seq, cmatch);
2775                 bp += cmatch;
2776                 buf_len += cmatch;
2777                 cmatch = 0;
2778             }
2779         }
2780
2781         // copy this char
2782         *bp++ = data[i];
2783         buf_len++;
2784     }
2785
2786         buf[buf_len] = NULLCHAR;
2787 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2788         next_out = 0;
2789         leftover_start = 0;
2790
2791         i = 0;
2792         while (i < buf_len) {
2793             /* Deal with part of the TELNET option negotiation
2794                protocol.  We refuse to do anything beyond the
2795                defaults, except that we allow the WILL ECHO option,
2796                which ICS uses to turn off password echoing when we are
2797                directly connected to it.  We reject this option
2798                if localLineEditing mode is on (always on in xboard)
2799                and we are talking to port 23, which might be a real
2800                telnet server that will try to keep WILL ECHO on permanently.
2801              */
2802             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2803                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2804                 unsigned char option;
2805                 oldi = i;
2806                 switch ((unsigned char) buf[++i]) {
2807                   case TN_WILL:
2808                     if (appData.debugMode)
2809                       fprintf(debugFP, "\n<WILL ");
2810                     switch (option = (unsigned char) buf[++i]) {
2811                       case TN_ECHO:
2812                         if (appData.debugMode)
2813                           fprintf(debugFP, "ECHO ");
2814                         /* Reply only if this is a change, according
2815                            to the protocol rules. */
2816                         if (remoteEchoOption) break;
2817                         if (appData.localLineEditing &&
2818                             atoi(appData.icsPort) == TN_PORT) {
2819                             TelnetRequest(TN_DONT, TN_ECHO);
2820                         } else {
2821                             EchoOff();
2822                             TelnetRequest(TN_DO, TN_ECHO);
2823                             remoteEchoOption = TRUE;
2824                         }
2825                         break;
2826                       default:
2827                         if (appData.debugMode)
2828                           fprintf(debugFP, "%d ", option);
2829                         /* Whatever this is, we don't want it. */
2830                         TelnetRequest(TN_DONT, option);
2831                         break;
2832                     }
2833                     break;
2834                   case TN_WONT:
2835                     if (appData.debugMode)
2836                       fprintf(debugFP, "\n<WONT ");
2837                     switch (option = (unsigned char) buf[++i]) {
2838                       case TN_ECHO:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "ECHO ");
2841                         /* Reply only if this is a change, according
2842                            to the protocol rules. */
2843                         if (!remoteEchoOption) break;
2844                         EchoOn();
2845                         TelnetRequest(TN_DONT, TN_ECHO);
2846                         remoteEchoOption = FALSE;
2847                         break;
2848                       default:
2849                         if (appData.debugMode)
2850                           fprintf(debugFP, "%d ", (unsigned char) option);
2851                         /* Whatever this is, it must already be turned
2852                            off, because we never agree to turn on
2853                            anything non-default, so according to the
2854                            protocol rules, we don't reply. */
2855                         break;
2856                     }
2857                     break;
2858                   case TN_DO:
2859                     if (appData.debugMode)
2860                       fprintf(debugFP, "\n<DO ");
2861                     switch (option = (unsigned char) buf[++i]) {
2862                       default:
2863                         /* Whatever this is, we refuse to do it. */
2864                         if (appData.debugMode)
2865                           fprintf(debugFP, "%d ", option);
2866                         TelnetRequest(TN_WONT, option);
2867                         break;
2868                     }
2869                     break;
2870                   case TN_DONT:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<DONT ");
2873                     switch (option = (unsigned char) buf[++i]) {
2874                       default:
2875                         if (appData.debugMode)
2876                           fprintf(debugFP, "%d ", option);
2877                         /* Whatever this is, we are already not doing
2878                            it, because we never agree to do anything
2879                            non-default, so according to the protocol
2880                            rules, we don't reply. */
2881                         break;
2882                     }
2883                     break;
2884                   case TN_IAC:
2885                     if (appData.debugMode)
2886                       fprintf(debugFP, "\n<IAC ");
2887                     /* Doubled IAC; pass it through */
2888                     i--;
2889                     break;
2890                   default:
2891                     if (appData.debugMode)
2892                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2893                     /* Drop all other telnet commands on the floor */
2894                     break;
2895                 }
2896                 if (oldi > next_out)
2897                   SendToPlayer(&buf[next_out], oldi - next_out);
2898                 if (++i > next_out)
2899                   next_out = i;
2900                 continue;
2901             }
2902
2903             /* OK, this at least will *usually* work */
2904             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2905                 loggedOn = TRUE;
2906             }
2907
2908             if (loggedOn && !intfSet) {
2909                 if (ics_type == ICS_ICC) {
2910                   snprintf(str, MSG_SIZ,
2911                           "/set-quietly interface %s\n/set-quietly style 12\n",
2912                           programVersion);
2913                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2914                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2915                 } else if (ics_type == ICS_CHESSNET) {
2916                   snprintf(str, MSG_SIZ, "/style 12\n");
2917                 } else {
2918                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2919                   strcat(str, programVersion);
2920                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2921                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2922                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2923 #ifdef WIN32
2924                   strcat(str, "$iset nohighlight 1\n");
2925 #endif
2926                   strcat(str, "$iset lock 1\n$style 12\n");
2927                 }
2928                 SendToICS(str);
2929                 NotifyFrontendLogin();
2930                 intfSet = TRUE;
2931             }
2932
2933             if (started == STARTED_COMMENT) {
2934                 /* Accumulate characters in comment */
2935                 parse[parse_pos++] = buf[i];
2936                 if (buf[i] == '\n') {
2937                     parse[parse_pos] = NULLCHAR;
2938                     if(chattingPartner>=0) {
2939                         char mess[MSG_SIZ];
2940                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2941                         OutputChatMessage(chattingPartner, mess);
2942                         chattingPartner = -1;
2943                         next_out = i+1; // [HGM] suppress printing in ICS window
2944                     } else
2945                     if(!suppressKibitz) // [HGM] kibitz
2946                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2947                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2948                         int nrDigit = 0, nrAlph = 0, j;
2949                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2950                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2951                         parse[parse_pos] = NULLCHAR;
2952                         // try to be smart: if it does not look like search info, it should go to
2953                         // ICS interaction window after all, not to engine-output window.
2954                         for(j=0; j<parse_pos; j++) { // count letters and digits
2955                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2956                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2957                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2958                         }
2959                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2960                             int depth=0; float score;
2961                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2962                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2963                                 pvInfoList[forwardMostMove-1].depth = depth;
2964                                 pvInfoList[forwardMostMove-1].score = 100*score;
2965                             }
2966                             OutputKibitz(suppressKibitz, parse);
2967                         } else {
2968                             char tmp[MSG_SIZ];
2969                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2970                             SendToPlayer(tmp, strlen(tmp));
2971                         }
2972                         next_out = i+1; // [HGM] suppress printing in ICS window
2973                     }
2974                     started = STARTED_NONE;
2975                 } else {
2976                     /* Don't match patterns against characters in comment */
2977                     i++;
2978                     continue;
2979                 }
2980             }
2981             if (started == STARTED_CHATTER) {
2982                 if (buf[i] != '\n') {
2983                     /* Don't match patterns against characters in chatter */
2984                     i++;
2985                     continue;
2986                 }
2987                 started = STARTED_NONE;
2988                 if(suppressKibitz) next_out = i+1;
2989             }
2990
2991             /* Kludge to deal with rcmd protocol */
2992             if (firstTime && looking_at(buf, &i, "\001*")) {
2993                 DisplayFatalError(&buf[1], 0, 1);
2994                 continue;
2995             } else {
2996                 firstTime = FALSE;
2997             }
2998
2999             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3000                 ics_type = ICS_ICC;
3001                 ics_prefix = "/";
3002                 if (appData.debugMode)
3003                   fprintf(debugFP, "ics_type %d\n", ics_type);
3004                 continue;
3005             }
3006             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3007                 ics_type = ICS_FICS;
3008                 ics_prefix = "$";
3009                 if (appData.debugMode)
3010                   fprintf(debugFP, "ics_type %d\n", ics_type);
3011                 continue;
3012             }
3013             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3014                 ics_type = ICS_CHESSNET;
3015                 ics_prefix = "/";
3016                 if (appData.debugMode)
3017                   fprintf(debugFP, "ics_type %d\n", ics_type);
3018                 continue;
3019             }
3020
3021             if (!loggedOn &&
3022                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3023                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3024                  looking_at(buf, &i, "will be \"*\""))) {
3025               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3026               continue;
3027             }
3028
3029             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3030               char buf[MSG_SIZ];
3031               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3032               DisplayIcsInteractionTitle(buf);
3033               have_set_title = TRUE;
3034             }
3035
3036             /* skip finger notes */
3037             if (started == STARTED_NONE &&
3038                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3039                  (buf[i] == '1' && buf[i+1] == '0')) &&
3040                 buf[i+2] == ':' && buf[i+3] == ' ') {
3041               started = STARTED_CHATTER;
3042               i += 3;
3043               continue;
3044             }
3045
3046             oldi = i;
3047             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3048             if(appData.seekGraph) {
3049                 if(soughtPending && MatchSoughtLine(buf+i)) {
3050                     i = strstr(buf+i, "rated") - buf;
3051                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3052                     next_out = leftover_start = i;
3053                     started = STARTED_CHATTER;
3054                     suppressKibitz = TRUE;
3055                     continue;
3056                 }
3057                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3058                         && looking_at(buf, &i, "* ads displayed")) {
3059                     soughtPending = FALSE;
3060                     seekGraphUp = TRUE;
3061                     DrawSeekGraph();
3062                     continue;
3063                 }
3064                 if(appData.autoRefresh) {
3065                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3066                         int s = (ics_type == ICS_ICC); // ICC format differs
3067                         if(seekGraphUp)
3068                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3069                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3070                         looking_at(buf, &i, "*% "); // eat prompt
3071                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3072                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3073                         next_out = i; // suppress
3074                         continue;
3075                     }
3076                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3077                         char *p = star_match[0];
3078                         while(*p) {
3079                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3080                             while(*p && *p++ != ' '); // next
3081                         }
3082                         looking_at(buf, &i, "*% "); // eat prompt
3083                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3084                         next_out = i;
3085                         continue;
3086                     }
3087                 }
3088             }
3089
3090             /* skip formula vars */
3091             if (started == STARTED_NONE &&
3092                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3093               started = STARTED_CHATTER;
3094               i += 3;
3095               continue;
3096             }
3097
3098             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3099             if (appData.autoKibitz && started == STARTED_NONE &&
3100                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3101                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3102                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3103                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3104                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3105                         suppressKibitz = TRUE;
3106                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3107                         next_out = i;
3108                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3109                                 && (gameMode == IcsPlayingWhite)) ||
3110                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3111                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3112                             started = STARTED_CHATTER; // own kibitz we simply discard
3113                         else {
3114                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3115                             parse_pos = 0; parse[0] = NULLCHAR;
3116                             savingComment = TRUE;
3117                             suppressKibitz = gameMode != IcsObserving ? 2 :
3118                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3119                         }
3120                         continue;
3121                 } else
3122                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3123                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3124                          && atoi(star_match[0])) {
3125                     // suppress the acknowledgements of our own autoKibitz
3126                     char *p;
3127                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3128                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3129                     SendToPlayer(star_match[0], strlen(star_match[0]));
3130                     if(looking_at(buf, &i, "*% ")) // eat prompt
3131                         suppressKibitz = FALSE;
3132                     next_out = i;
3133                     continue;
3134                 }
3135             } // [HGM] kibitz: end of patch
3136
3137             // [HGM] chat: intercept tells by users for which we have an open chat window
3138             channel = -1;
3139             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3140                                            looking_at(buf, &i, "* whispers:") ||
3141                                            looking_at(buf, &i, "* kibitzes:") ||
3142                                            looking_at(buf, &i, "* shouts:") ||
3143                                            looking_at(buf, &i, "* c-shouts:") ||
3144                                            looking_at(buf, &i, "--> * ") ||
3145                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3146                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3148                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3149                 int p;
3150                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3151                 chattingPartner = -1;
3152
3153                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3154                 for(p=0; p<MAX_CHAT; p++) {
3155                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3156                     talker[0] = '['; strcat(talker, "] ");
3157                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3158                     chattingPartner = p; break;
3159                     }
3160                 } else
3161                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3162                 for(p=0; p<MAX_CHAT; p++) {
3163                     if(!strcmp("kibitzes", chatPartner[p])) {
3164                         talker[0] = '['; strcat(talker, "] ");
3165                         chattingPartner = p; break;
3166                     }
3167                 } else
3168                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3169                 for(p=0; p<MAX_CHAT; p++) {
3170                     if(!strcmp("whispers", chatPartner[p])) {
3171                         talker[0] = '['; strcat(talker, "] ");
3172                         chattingPartner = p; break;
3173                     }
3174                 } else
3175                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3176                   if(buf[i-8] == '-' && buf[i-3] == 't')
3177                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3178                     if(!strcmp("c-shouts", chatPartner[p])) {
3179                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3180                         chattingPartner = p; break;
3181                     }
3182                   }
3183                   if(chattingPartner < 0)
3184                   for(p=0; p<MAX_CHAT; p++) {
3185                     if(!strcmp("shouts", chatPartner[p])) {
3186                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3187                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3188                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3189                         chattingPartner = p; break;
3190                     }
3191                   }
3192                 }
3193                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3194                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3195                     talker[0] = 0; Colorize(ColorTell, FALSE);
3196                     chattingPartner = p; break;
3197                 }
3198                 if(chattingPartner<0) i = oldi; else {
3199                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3200                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3201                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3202                     started = STARTED_COMMENT;
3203                     parse_pos = 0; parse[0] = NULLCHAR;
3204                     savingComment = 3 + chattingPartner; // counts as TRUE
3205                     suppressKibitz = TRUE;
3206                     continue;
3207                 }
3208             } // [HGM] chat: end of patch
3209
3210           backup = i;
3211             if (appData.zippyTalk || appData.zippyPlay) {
3212                 /* [DM] Backup address for color zippy lines */
3213 #if ZIPPY
3214                if (loggedOn == TRUE)
3215                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3216                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3217 #endif
3218             } // [DM] 'else { ' deleted
3219                 if (
3220                     /* Regular tells and says */
3221                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3222                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3223                     looking_at(buf, &i, "* says: ") ||
3224                     /* Don't color "message" or "messages" output */
3225                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3226                     looking_at(buf, &i, "*. * at *:*: ") ||
3227                     looking_at(buf, &i, "--* (*:*): ") ||
3228                     /* Message notifications (same color as tells) */
3229                     looking_at(buf, &i, "* has left a message ") ||
3230                     looking_at(buf, &i, "* just sent you a message:\n") ||
3231                     /* Whispers and kibitzes */
3232                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3233                     looking_at(buf, &i, "* kibitzes: ") ||
3234                     /* Channel tells */
3235                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3236
3237                   if (tkind == 1 && strchr(star_match[0], ':')) {
3238                       /* Avoid "tells you:" spoofs in channels */
3239                      tkind = 3;
3240                   }
3241                   if (star_match[0][0] == NULLCHAR ||
3242                       strchr(star_match[0], ' ') ||
3243                       (tkind == 3 && strchr(star_match[1], ' '))) {
3244                     /* Reject bogus matches */
3245                     i = oldi;
3246                   } else {
3247                     if (appData.colorize) {
3248                       if (oldi > next_out) {
3249                         SendToPlayer(&buf[next_out], oldi - next_out);
3250                         next_out = oldi;
3251                       }
3252                       switch (tkind) {
3253                       case 1:
3254                         Colorize(ColorTell, FALSE);
3255                         curColor = ColorTell;
3256                         break;
3257                       case 2:
3258                         Colorize(ColorKibitz, FALSE);
3259                         curColor = ColorKibitz;
3260                         break;
3261                       case 3:
3262                         p = strrchr(star_match[1], '(');
3263                         if (p == NULL) {
3264                           p = star_match[1];
3265                         } else {
3266                           p++;
3267                         }
3268                         if (atoi(p) == 1) {
3269                           Colorize(ColorChannel1, FALSE);
3270                           curColor = ColorChannel1;
3271                         } else {
3272                           Colorize(ColorChannel, FALSE);
3273                           curColor = ColorChannel;
3274                         }
3275                         break;
3276                       case 5:
3277                         curColor = ColorNormal;
3278                         break;
3279                       }
3280                     }
3281                     if (started == STARTED_NONE && appData.autoComment &&
3282                         (gameMode == IcsObserving ||
3283                          gameMode == IcsPlayingWhite ||
3284                          gameMode == IcsPlayingBlack)) {
3285                       parse_pos = i - oldi;
3286                       memcpy(parse, &buf[oldi], parse_pos);
3287                       parse[parse_pos] = NULLCHAR;
3288                       started = STARTED_COMMENT;
3289                       savingComment = TRUE;
3290                     } else {
3291                       started = STARTED_CHATTER;
3292                       savingComment = FALSE;
3293                     }
3294                     loggedOn = TRUE;
3295                     continue;
3296                   }
3297                 }
3298
3299                 if (looking_at(buf, &i, "* s-shouts: ") ||
3300                     looking_at(buf, &i, "* c-shouts: ")) {
3301                     if (appData.colorize) {
3302                         if (oldi > next_out) {
3303                             SendToPlayer(&buf[next_out], oldi - next_out);
3304                             next_out = oldi;
3305                         }
3306                         Colorize(ColorSShout, FALSE);
3307                         curColor = ColorSShout;
3308                     }
3309                     loggedOn = TRUE;
3310                     started = STARTED_CHATTER;
3311                     continue;
3312                 }
3313
3314                 if (looking_at(buf, &i, "--->")) {
3315                     loggedOn = TRUE;
3316                     continue;
3317                 }
3318
3319                 if (looking_at(buf, &i, "* shouts: ") ||
3320                     looking_at(buf, &i, "--> ")) {
3321                     if (appData.colorize) {
3322                         if (oldi > next_out) {
3323                             SendToPlayer(&buf[next_out], oldi - next_out);
3324                             next_out = oldi;
3325                         }
3326                         Colorize(ColorShout, FALSE);
3327                         curColor = ColorShout;
3328                     }
3329                     loggedOn = TRUE;
3330                     started = STARTED_CHATTER;
3331                     continue;
3332                 }
3333
3334                 if (looking_at( buf, &i, "Challenge:")) {
3335                     if (appData.colorize) {
3336                         if (oldi > next_out) {
3337                             SendToPlayer(&buf[next_out], oldi - next_out);
3338                             next_out = oldi;
3339                         }
3340                         Colorize(ColorChallenge, FALSE);
3341                         curColor = ColorChallenge;
3342                     }
3343                     loggedOn = TRUE;
3344                     continue;
3345                 }
3346
3347                 if (looking_at(buf, &i, "* offers you") ||
3348                     looking_at(buf, &i, "* offers to be") ||
3349                     looking_at(buf, &i, "* would like to") ||
3350                     looking_at(buf, &i, "* requests to") ||
3351                     looking_at(buf, &i, "Your opponent offers") ||
3352                     looking_at(buf, &i, "Your opponent requests")) {
3353
3354                     if (appData.colorize) {
3355                         if (oldi > next_out) {
3356                             SendToPlayer(&buf[next_out], oldi - next_out);
3357                             next_out = oldi;
3358                         }
3359                         Colorize(ColorRequest, FALSE);
3360                         curColor = ColorRequest;
3361                     }
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "* (*) seeking")) {
3366                     if (appData.colorize) {
3367                         if (oldi > next_out) {
3368                             SendToPlayer(&buf[next_out], oldi - next_out);
3369                             next_out = oldi;
3370                         }
3371                         Colorize(ColorSeek, FALSE);
3372                         curColor = ColorSeek;
3373                     }
3374                     continue;
3375             }
3376
3377           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3378
3379             if (looking_at(buf, &i, "\\   ")) {
3380                 if (prevColor != ColorNormal) {
3381                     if (oldi > next_out) {
3382                         SendToPlayer(&buf[next_out], oldi - next_out);
3383                         next_out = oldi;
3384                     }
3385                     Colorize(prevColor, TRUE);
3386                     curColor = prevColor;
3387                 }
3388                 if (savingComment) {
3389                     parse_pos = i - oldi;
3390                     memcpy(parse, &buf[oldi], parse_pos);
3391                     parse[parse_pos] = NULLCHAR;
3392                     started = STARTED_COMMENT;
3393                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3394                         chattingPartner = savingComment - 3; // kludge to remember the box
3395                 } else {
3396                     started = STARTED_CHATTER;
3397                 }
3398                 continue;
3399             }
3400
3401             if (looking_at(buf, &i, "Black Strength :") ||
3402                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3403                 looking_at(buf, &i, "<10>") ||
3404                 looking_at(buf, &i, "#@#")) {
3405                 /* Wrong board style */
3406                 loggedOn = TRUE;
3407                 SendToICS(ics_prefix);
3408                 SendToICS("set style 12\n");
3409                 SendToICS(ics_prefix);
3410                 SendToICS("refresh\n");
3411                 continue;
3412             }
3413
3414             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3415                 ICSInitScript();
3416                 have_sent_ICS_logon = 1;
3417                 continue;
3418             }
3419
3420             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3421                 (looking_at(buf, &i, "\n<12> ") ||
3422                  looking_at(buf, &i, "<12> "))) {
3423                 loggedOn = TRUE;
3424                 if (oldi > next_out) {
3425                     SendToPlayer(&buf[next_out], oldi - next_out);
3426                 }
3427                 next_out = i;
3428                 started = STARTED_BOARD;
3429                 parse_pos = 0;
3430                 continue;
3431             }
3432
3433             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3434                 looking_at(buf, &i, "<b1> ")) {
3435                 if (oldi > next_out) {
3436                     SendToPlayer(&buf[next_out], oldi - next_out);
3437                 }
3438                 next_out = i;
3439                 started = STARTED_HOLDINGS;
3440                 parse_pos = 0;
3441                 continue;
3442             }
3443
3444             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3445                 loggedOn = TRUE;
3446                 /* Header for a move list -- first line */
3447
3448                 switch (ics_getting_history) {
3449                   case H_FALSE:
3450                     switch (gameMode) {
3451                       case IcsIdle:
3452                       case BeginningOfGame:
3453                         /* User typed "moves" or "oldmoves" while we
3454                            were idle.  Pretend we asked for these
3455                            moves and soak them up so user can step
3456                            through them and/or save them.
3457                            */
3458                         Reset(FALSE, TRUE);
3459                         gameMode = IcsObserving;
3460                         ModeHighlight();
3461                         ics_gamenum = -1;
3462                         ics_getting_history = H_GOT_UNREQ_HEADER;
3463                         break;
3464                       case EditGame: /*?*/
3465                       case EditPosition: /*?*/
3466                         /* Should above feature work in these modes too? */
3467                         /* For now it doesn't */
3468                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3469                         break;
3470                       default:
3471                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3472                         break;
3473                     }
3474                     break;
3475                   case H_REQUESTED:
3476                     /* Is this the right one? */
3477                     if (gameInfo.white && gameInfo.black &&
3478                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3479                         strcmp(gameInfo.black, star_match[2]) == 0) {
3480                         /* All is well */
3481                         ics_getting_history = H_GOT_REQ_HEADER;
3482                     }
3483                     break;
3484                   case H_GOT_REQ_HEADER:
3485                   case H_GOT_UNREQ_HEADER:
3486                   case H_GOT_UNWANTED_HEADER:
3487                   case H_GETTING_MOVES:
3488                     /* Should not happen */
3489                     DisplayError(_("Error gathering move list: two headers"), 0);
3490                     ics_getting_history = H_FALSE;
3491                     break;
3492                 }
3493
3494                 /* Save player ratings into gameInfo if needed */
3495                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3496                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3497                     (gameInfo.whiteRating == -1 ||
3498                      gameInfo.blackRating == -1)) {
3499
3500                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3501                     gameInfo.blackRating = string_to_rating(star_match[3]);
3502                     if (appData.debugMode)
3503                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3504                               gameInfo.whiteRating, gameInfo.blackRating);
3505                 }
3506                 continue;
3507             }
3508
3509             if (looking_at(buf, &i,
3510               "* * match, initial time: * minute*, increment: * second")) {
3511                 /* Header for a move list -- second line */
3512                 /* Initial board will follow if this is a wild game */
3513                 if (gameInfo.event != NULL) free(gameInfo.event);
3514                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3515                 gameInfo.event = StrSave(str);
3516                 /* [HGM] we switched variant. Translate boards if needed. */
3517                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3518                 continue;
3519             }
3520
3521             if (looking_at(buf, &i, "Move  ")) {
3522                 /* Beginning of a move list */
3523                 switch (ics_getting_history) {
3524                   case H_FALSE:
3525                     /* Normally should not happen */
3526                     /* Maybe user hit reset while we were parsing */
3527                     break;
3528                   case H_REQUESTED:
3529                     /* Happens if we are ignoring a move list that is not
3530                      * the one we just requested.  Common if the user
3531                      * tries to observe two games without turning off
3532                      * getMoveList */
3533                     break;
3534                   case H_GETTING_MOVES:
3535                     /* Should not happen */
3536                     DisplayError(_("Error gathering move list: nested"), 0);
3537                     ics_getting_history = H_FALSE;
3538                     break;
3539                   case H_GOT_REQ_HEADER:
3540                     ics_getting_history = H_GETTING_MOVES;
3541                     started = STARTED_MOVES;
3542                     parse_pos = 0;
3543                     if (oldi > next_out) {
3544                         SendToPlayer(&buf[next_out], oldi - next_out);
3545                     }
3546                     break;
3547                   case H_GOT_UNREQ_HEADER:
3548                     ics_getting_history = H_GETTING_MOVES;
3549                     started = STARTED_MOVES_NOHIDE;
3550                     parse_pos = 0;
3551                     break;
3552                   case H_GOT_UNWANTED_HEADER:
3553                     ics_getting_history = H_FALSE;
3554                     break;
3555                 }
3556                 continue;
3557             }
3558
3559             if (looking_at(buf, &i, "% ") ||
3560                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3561                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3562                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3563                     soughtPending = FALSE;
3564                     seekGraphUp = TRUE;
3565                     DrawSeekGraph();
3566                 }
3567                 if(suppressKibitz) next_out = i;
3568                 savingComment = FALSE;
3569                 suppressKibitz = 0;
3570                 switch (started) {
3571                   case STARTED_MOVES:
3572                   case STARTED_MOVES_NOHIDE:
3573                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3574                     parse[parse_pos + i - oldi] = NULLCHAR;
3575                     ParseGameHistory(parse);
3576 #if ZIPPY
3577                     if (appData.zippyPlay && first.initDone) {
3578                         FeedMovesToProgram(&first, forwardMostMove);
3579                         if (gameMode == IcsPlayingWhite) {
3580                             if (WhiteOnMove(forwardMostMove)) {
3581                                 if (first.sendTime) {
3582                                   if (first.useColors) {
3583                                     SendToProgram("black\n", &first);
3584                                   }
3585                                   SendTimeRemaining(&first, TRUE);
3586                                 }
3587                                 if (first.useColors) {
3588                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3589                                 }
3590                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3591                                 first.maybeThinking = TRUE;
3592                             } else {
3593                                 if (first.usePlayother) {
3594                                   if (first.sendTime) {
3595                                     SendTimeRemaining(&first, TRUE);
3596                                   }
3597                                   SendToProgram("playother\n", &first);
3598                                   firstMove = FALSE;
3599                                 } else {
3600                                   firstMove = TRUE;
3601                                 }
3602                             }
3603                         } else if (gameMode == IcsPlayingBlack) {
3604                             if (!WhiteOnMove(forwardMostMove)) {
3605                                 if (first.sendTime) {
3606                                   if (first.useColors) {
3607                                     SendToProgram("white\n", &first);
3608                                   }
3609                                   SendTimeRemaining(&first, FALSE);
3610                                 }
3611                                 if (first.useColors) {
3612                                   SendToProgram("black\n", &first);
3613                                 }
3614                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3615                                 first.maybeThinking = TRUE;
3616                             } else {
3617                                 if (first.usePlayother) {
3618                                   if (first.sendTime) {
3619                                     SendTimeRemaining(&first, FALSE);
3620                                   }
3621                                   SendToProgram("playother\n", &first);
3622                                   firstMove = FALSE;
3623                                 } else {
3624                                   firstMove = TRUE;
3625                                 }
3626                             }
3627                         }
3628                     }
3629 #endif
3630                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3631                         /* Moves came from oldmoves or moves command
3632                            while we weren't doing anything else.
3633                            */
3634                         currentMove = forwardMostMove;
3635                         ClearHighlights();/*!!could figure this out*/
3636                         flipView = appData.flipView;
3637                         DrawPosition(TRUE, boards[currentMove]);
3638                         DisplayBothClocks();
3639                         snprintf(str, MSG_SIZ, "%s vs. %s",
3640                                 gameInfo.white, gameInfo.black);
3641                         DisplayTitle(str);
3642                         gameMode = IcsIdle;
3643                     } else {
3644                         /* Moves were history of an active game */
3645                         if (gameInfo.resultDetails != NULL) {
3646                             free(gameInfo.resultDetails);
3647                             gameInfo.resultDetails = NULL;
3648                         }
3649                     }
3650                     HistorySet(parseList, backwardMostMove,
3651                                forwardMostMove, currentMove-1);
3652                     DisplayMove(currentMove - 1);
3653                     if (started == STARTED_MOVES) next_out = i;
3654                     started = STARTED_NONE;
3655                     ics_getting_history = H_FALSE;
3656                     break;
3657
3658                   case STARTED_OBSERVE:
3659                     started = STARTED_NONE;
3660                     SendToICS(ics_prefix);
3661                     SendToICS("refresh\n");
3662                     break;
3663
3664                   default:
3665                     break;
3666                 }
3667                 if(bookHit) { // [HGM] book: simulate book reply
3668                     static char bookMove[MSG_SIZ]; // a bit generous?
3669
3670                     programStats.nodes = programStats.depth = programStats.time =
3671                     programStats.score = programStats.got_only_move = 0;
3672                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3673
3674                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3675                     strcat(bookMove, bookHit);
3676                     HandleMachineMove(bookMove, &first);
3677                 }
3678                 continue;
3679             }
3680
3681             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3682                  started == STARTED_HOLDINGS ||
3683                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3684                 /* Accumulate characters in move list or board */
3685                 parse[parse_pos++] = buf[i];
3686             }
3687
3688             /* Start of game messages.  Mostly we detect start of game
3689                when the first board image arrives.  On some versions
3690                of the ICS, though, we need to do a "refresh" after starting
3691                to observe in order to get the current board right away. */
3692             if (looking_at(buf, &i, "Adding game * to observation list")) {
3693                 started = STARTED_OBSERVE;
3694                 continue;
3695             }
3696
3697             /* Handle auto-observe */
3698             if (appData.autoObserve &&
3699                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3700                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3701                 char *player;
3702                 /* Choose the player that was highlighted, if any. */
3703                 if (star_match[0][0] == '\033' ||
3704                     star_match[1][0] != '\033') {
3705                     player = star_match[0];
3706                 } else {
3707                     player = star_match[2];
3708                 }
3709                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3710                         ics_prefix, StripHighlightAndTitle(player));
3711                 SendToICS(str);
3712
3713                 /* Save ratings from notify string */
3714                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3715                 player1Rating = string_to_rating(star_match[1]);
3716                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3717                 player2Rating = string_to_rating(star_match[3]);
3718
3719                 if (appData.debugMode)
3720                   fprintf(debugFP,
3721                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3722                           player1Name, player1Rating,
3723                           player2Name, player2Rating);
3724
3725                 continue;
3726             }
3727
3728             /* Deal with automatic examine mode after a game,
3729                and with IcsObserving -> IcsExamining transition */
3730             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3731                 looking_at(buf, &i, "has made you an examiner of game *")) {
3732
3733                 int gamenum = atoi(star_match[0]);
3734                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3735                     gamenum == ics_gamenum) {
3736                     /* We were already playing or observing this game;
3737                        no need to refetch history */
3738                     gameMode = IcsExamining;
3739                     if (pausing) {
3740                         pauseExamForwardMostMove = forwardMostMove;
3741                     } else if (currentMove < forwardMostMove) {
3742                         ForwardInner(forwardMostMove);
3743                     }
3744                 } else {
3745                     /* I don't think this case really can happen */
3746                     SendToICS(ics_prefix);
3747                     SendToICS("refresh\n");
3748                 }
3749                 continue;
3750             }
3751
3752             /* Error messages */
3753 //          if (ics_user_moved) {
3754             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3755                 if (looking_at(buf, &i, "Illegal move") ||
3756                     looking_at(buf, &i, "Not a legal move") ||
3757                     looking_at(buf, &i, "Your king is in check") ||
3758                     looking_at(buf, &i, "It isn't your turn") ||
3759                     looking_at(buf, &i, "It is not your move")) {
3760                     /* Illegal move */
3761                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3762                         currentMove = forwardMostMove-1;
3763                         DisplayMove(currentMove - 1); /* before DMError */
3764                         DrawPosition(FALSE, boards[currentMove]);
3765                         SwitchClocks(forwardMostMove-1); // [HGM] race
3766                         DisplayBothClocks();
3767                     }
3768                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3769                     ics_user_moved = 0;
3770                     continue;
3771                 }
3772             }
3773
3774             if (looking_at(buf, &i, "still have time") ||
3775                 looking_at(buf, &i, "not out of time") ||
3776                 looking_at(buf, &i, "either player is out of time") ||
3777                 looking_at(buf, &i, "has timeseal; checking")) {
3778                 /* We must have called his flag a little too soon */
3779                 whiteFlag = blackFlag = FALSE;
3780                 continue;
3781             }
3782
3783             if (looking_at(buf, &i, "added * seconds to") ||
3784                 looking_at(buf, &i, "seconds were added to")) {
3785                 /* Update the clocks */
3786                 SendToICS(ics_prefix);
3787                 SendToICS("refresh\n");
3788                 continue;
3789             }
3790
3791             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3792                 ics_clock_paused = TRUE;
3793                 StopClocks();
3794                 continue;
3795             }
3796
3797             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3798                 ics_clock_paused = FALSE;
3799                 StartClocks();
3800                 continue;
3801             }
3802
3803             /* Grab player ratings from the Creating: message.
3804                Note we have to check for the special case when
3805                the ICS inserts things like [white] or [black]. */
3806             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3807                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3808                 /* star_matches:
3809                    0    player 1 name (not necessarily white)
3810                    1    player 1 rating
3811                    2    empty, white, or black (IGNORED)
3812                    3    player 2 name (not necessarily black)
3813                    4    player 2 rating
3814
3815                    The names/ratings are sorted out when the game
3816                    actually starts (below).
3817                 */
3818                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3819                 player1Rating = string_to_rating(star_match[1]);
3820                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3821                 player2Rating = string_to_rating(star_match[4]);
3822
3823                 if (appData.debugMode)
3824                   fprintf(debugFP,
3825                           "Ratings from 'Creating:' %s %d, %s %d\n",
3826                           player1Name, player1Rating,
3827                           player2Name, player2Rating);
3828
3829                 continue;
3830             }
3831
3832             /* Improved generic start/end-of-game messages */
3833             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3834                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3835                 /* If tkind == 0: */
3836                 /* star_match[0] is the game number */
3837                 /*           [1] is the white player's name */
3838                 /*           [2] is the black player's name */
3839                 /* For end-of-game: */
3840                 /*           [3] is the reason for the game end */
3841                 /*           [4] is a PGN end game-token, preceded by " " */
3842                 /* For start-of-game: */
3843                 /*           [3] begins with "Creating" or "Continuing" */
3844                 /*           [4] is " *" or empty (don't care). */
3845                 int gamenum = atoi(star_match[0]);
3846                 char *whitename, *blackname, *why, *endtoken;
3847                 ChessMove endtype = EndOfFile;
3848
3849                 if (tkind == 0) {
3850                   whitename = star_match[1];
3851                   blackname = star_match[2];
3852                   why = star_match[3];
3853                   endtoken = star_match[4];
3854                 } else {
3855                   whitename = star_match[1];
3856                   blackname = star_match[3];
3857                   why = star_match[5];
3858                   endtoken = star_match[6];
3859                 }
3860
3861                 /* Game start messages */
3862                 if (strncmp(why, "Creating ", 9) == 0 ||
3863                     strncmp(why, "Continuing ", 11) == 0) {
3864                     gs_gamenum = gamenum;
3865                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3866                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3867                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3868 #if ZIPPY
3869                     if (appData.zippyPlay) {
3870                         ZippyGameStart(whitename, blackname);
3871                     }
3872 #endif /*ZIPPY*/
3873                     partnerBoardValid = FALSE; // [HGM] bughouse
3874                     continue;
3875                 }
3876
3877                 /* Game end messages */
3878                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3879                     ics_gamenum != gamenum) {
3880                     continue;
3881                 }
3882                 while (endtoken[0] == ' ') endtoken++;
3883                 switch (endtoken[0]) {
3884                   case '*':
3885                   default:
3886                     endtype = GameUnfinished;
3887                     break;
3888                   case '0':
3889                     endtype = BlackWins;
3890                     break;
3891                   case '1':
3892                     if (endtoken[1] == '/')
3893                       endtype = GameIsDrawn;
3894                     else
3895                       endtype = WhiteWins;
3896                     break;
3897                 }
3898                 GameEnds(endtype, why, GE_ICS);
3899 #if ZIPPY
3900                 if (appData.zippyPlay && first.initDone) {
3901                     ZippyGameEnd(endtype, why);
3902                     if (first.pr == NoProc) {
3903                       /* Start the next process early so that we'll
3904                          be ready for the next challenge */
3905                       StartChessProgram(&first);
3906                     }
3907                     /* Send "new" early, in case this command takes
3908                        a long time to finish, so that we'll be ready
3909                        for the next challenge. */
3910                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3911                     Reset(TRUE, TRUE);
3912                 }
3913 #endif /*ZIPPY*/
3914                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3915                 continue;
3916             }
3917
3918             if (looking_at(buf, &i, "Removing game * from observation") ||
3919                 looking_at(buf, &i, "no longer observing game *") ||
3920                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3921                 if (gameMode == IcsObserving &&
3922                     atoi(star_match[0]) == ics_gamenum)
3923                   {
3924                       /* icsEngineAnalyze */
3925                       if (appData.icsEngineAnalyze) {
3926                             ExitAnalyzeMode();
3927                             ModeHighlight();
3928                       }
3929                       StopClocks();
3930                       gameMode = IcsIdle;
3931                       ics_gamenum = -1;
3932                       ics_user_moved = FALSE;
3933                   }
3934                 continue;
3935             }
3936
3937             if (looking_at(buf, &i, "no longer examining game *")) {
3938                 if (gameMode == IcsExamining &&
3939                     atoi(star_match[0]) == ics_gamenum)
3940                   {
3941                       gameMode = IcsIdle;
3942                       ics_gamenum = -1;
3943                       ics_user_moved = FALSE;
3944                   }
3945                 continue;
3946             }
3947
3948             /* Advance leftover_start past any newlines we find,
3949                so only partial lines can get reparsed */
3950             if (looking_at(buf, &i, "\n")) {
3951                 prevColor = curColor;
3952                 if (curColor != ColorNormal) {
3953                     if (oldi > next_out) {
3954                         SendToPlayer(&buf[next_out], oldi - next_out);
3955                         next_out = oldi;
3956                     }
3957                     Colorize(ColorNormal, FALSE);
3958                     curColor = ColorNormal;
3959                 }
3960                 if (started == STARTED_BOARD) {
3961                     started = STARTED_NONE;
3962                     parse[parse_pos] = NULLCHAR;
3963                     ParseBoard12(parse);
3964                     ics_user_moved = 0;
3965
3966                     /* Send premove here */
3967                     if (appData.premove) {
3968                       char str[MSG_SIZ];
3969                       if (currentMove == 0 &&
3970                           gameMode == IcsPlayingWhite &&
3971                           appData.premoveWhite) {
3972                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3973                         if (appData.debugMode)
3974                           fprintf(debugFP, "Sending premove:\n");
3975                         SendToICS(str);
3976                       } else if (currentMove == 1 &&
3977                                  gameMode == IcsPlayingBlack &&
3978                                  appData.premoveBlack) {
3979                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3980                         if (appData.debugMode)
3981                           fprintf(debugFP, "Sending premove:\n");
3982                         SendToICS(str);
3983                       } else if (gotPremove) {
3984                         gotPremove = 0;
3985                         ClearPremoveHighlights();
3986                         if (appData.debugMode)
3987                           fprintf(debugFP, "Sending premove:\n");
3988                           UserMoveEvent(premoveFromX, premoveFromY,
3989                                         premoveToX, premoveToY,
3990                                         premovePromoChar);
3991                       }
3992                     }
3993
3994                     /* Usually suppress following prompt */
3995                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3996                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3997                         if (looking_at(buf, &i, "*% ")) {
3998                             savingComment = FALSE;
3999                             suppressKibitz = 0;
4000                         }
4001                     }
4002                     next_out = i;
4003                 } else if (started == STARTED_HOLDINGS) {
4004                     int gamenum;
4005                     char new_piece[MSG_SIZ];
4006                     started = STARTED_NONE;
4007                     parse[parse_pos] = NULLCHAR;
4008                     if (appData.debugMode)
4009                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4010                                                         parse, currentMove);
4011                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4012                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4013                         if (gameInfo.variant == VariantNormal) {
4014                           /* [HGM] We seem to switch variant during a game!
4015                            * Presumably no holdings were displayed, so we have
4016                            * to move the position two files to the right to
4017                            * create room for them!
4018                            */
4019                           VariantClass newVariant;
4020                           switch(gameInfo.boardWidth) { // base guess on board width
4021                                 case 9:  newVariant = VariantShogi; break;
4022                                 case 10: newVariant = VariantGreat; break;
4023                                 default: newVariant = VariantCrazyhouse; break;
4024                           }
4025                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4026                           /* Get a move list just to see the header, which
4027                              will tell us whether this is really bug or zh */
4028                           if (ics_getting_history == H_FALSE) {
4029                             ics_getting_history = H_REQUESTED;
4030                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4031                             SendToICS(str);
4032                           }
4033                         }
4034                         new_piece[0] = NULLCHAR;
4035                         sscanf(parse, "game %d white [%s black [%s <- %s",
4036                                &gamenum, white_holding, black_holding,
4037                                new_piece);
4038                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4039                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4040                         /* [HGM] copy holdings to board holdings area */
4041                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4042                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4043                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4044 #if ZIPPY
4045                         if (appData.zippyPlay && first.initDone) {
4046                             ZippyHoldings(white_holding, black_holding,
4047                                           new_piece);
4048                         }
4049 #endif /*ZIPPY*/
4050                         if (tinyLayout || smallLayout) {
4051                             char wh[16], bh[16];
4052                             PackHolding(wh, white_holding);
4053                             PackHolding(bh, black_holding);
4054                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4055                                     gameInfo.white, gameInfo.black);
4056                         } else {
4057                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4058                                     gameInfo.white, white_holding,
4059                                     gameInfo.black, black_holding);
4060                         }
4061                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4062                         DrawPosition(FALSE, boards[currentMove]);
4063                         DisplayTitle(str);
4064                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4065                         sscanf(parse, "game %d white [%s black [%s <- %s",
4066                                &gamenum, white_holding, black_holding,
4067                                new_piece);
4068                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4069                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4070                         /* [HGM] copy holdings to partner-board holdings area */
4071                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4072                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4073                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4074                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4075                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4076                       }
4077                     }
4078                     /* Suppress following prompt */
4079                     if (looking_at(buf, &i, "*% ")) {
4080                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4081                         savingComment = FALSE;
4082                         suppressKibitz = 0;
4083                     }
4084                     next_out = i;
4085                 }
4086                 continue;
4087             }
4088
4089             i++;                /* skip unparsed character and loop back */
4090         }
4091
4092         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4093 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4094 //          SendToPlayer(&buf[next_out], i - next_out);
4095             started != STARTED_HOLDINGS && leftover_start > next_out) {
4096             SendToPlayer(&buf[next_out], leftover_start - next_out);
4097             next_out = i;
4098         }
4099
4100         leftover_len = buf_len - leftover_start;
4101         /* if buffer ends with something we couldn't parse,
4102            reparse it after appending the next read */
4103
4104     } else if (count == 0) {
4105         RemoveInputSource(isr);
4106         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4107     } else {
4108         DisplayFatalError(_("Error reading from ICS"), error, 1);
4109     }
4110 }
4111
4112
4113 /* Board style 12 looks like this:
4114
4115    <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
4116
4117  * The "<12> " is stripped before it gets to this routine.  The two
4118  * trailing 0's (flip state and clock ticking) are later addition, and
4119  * some chess servers may not have them, or may have only the first.
4120  * Additional trailing fields may be added in the future.
4121  */
4122
4123 #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"
4124
4125 #define RELATION_OBSERVING_PLAYED    0
4126 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4127 #define RELATION_PLAYING_MYMOVE      1
4128 #define RELATION_PLAYING_NOTMYMOVE  -1
4129 #define RELATION_EXAMINING           2
4130 #define RELATION_ISOLATED_BOARD     -3
4131 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4132
4133 void
4134 ParseBoard12(string)
4135      char *string;
4136 {
4137     GameMode newGameMode;
4138     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4139     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4140     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4141     char to_play, board_chars[200];
4142     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4143     char black[32], white[32];
4144     Board board;
4145     int prevMove = currentMove;
4146     int ticking = 2;
4147     ChessMove moveType;
4148     int fromX, fromY, toX, toY;
4149     char promoChar;
4150     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4151     char *bookHit = NULL; // [HGM] book
4152     Boolean weird = FALSE, reqFlag = FALSE;
4153
4154     fromX = fromY = toX = toY = -1;
4155
4156     newGame = FALSE;
4157
4158     if (appData.debugMode)
4159       fprintf(debugFP, _("Parsing board: %s\n"), string);
4160
4161     move_str[0] = NULLCHAR;
4162     elapsed_time[0] = NULLCHAR;
4163     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4164         int  i = 0, j;
4165         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4166             if(string[i] == ' ') { ranks++; files = 0; }
4167             else files++;
4168             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4169             i++;
4170         }
4171         for(j = 0; j <i; j++) board_chars[j] = string[j];
4172         board_chars[i] = '\0';
4173         string += i + 1;
4174     }
4175     n = sscanf(string, PATTERN, &to_play, &double_push,
4176                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4177                &gamenum, white, black, &relation, &basetime, &increment,
4178                &white_stren, &black_stren, &white_time, &black_time,
4179                &moveNum, str, elapsed_time, move_str, &ics_flip,
4180                &ticking);
4181
4182     if (n < 21) {
4183         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4184         DisplayError(str, 0);
4185         return;
4186     }
4187
4188     /* Convert the move number to internal form */
4189     moveNum = (moveNum - 1) * 2;
4190     if (to_play == 'B') moveNum++;
4191     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4192       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4193                         0, 1);
4194       return;
4195     }
4196
4197     switch (relation) {
4198       case RELATION_OBSERVING_PLAYED:
4199       case RELATION_OBSERVING_STATIC:
4200         if (gamenum == -1) {
4201             /* Old ICC buglet */
4202             relation = RELATION_OBSERVING_STATIC;
4203         }
4204         newGameMode = IcsObserving;
4205         break;
4206       case RELATION_PLAYING_MYMOVE:
4207       case RELATION_PLAYING_NOTMYMOVE:
4208         newGameMode =
4209           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4210             IcsPlayingWhite : IcsPlayingBlack;
4211         break;
4212       case RELATION_EXAMINING:
4213         newGameMode = IcsExamining;
4214         break;
4215       case RELATION_ISOLATED_BOARD:
4216       default:
4217         /* Just display this board.  If user was doing something else,
4218            we will forget about it until the next board comes. */
4219         newGameMode = IcsIdle;
4220         break;
4221       case RELATION_STARTING_POSITION:
4222         newGameMode = gameMode;
4223         break;
4224     }
4225
4226     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4227          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4228       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4229       char *toSqr;
4230       for (k = 0; k < ranks; k++) {
4231         for (j = 0; j < files; j++)
4232           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4233         if(gameInfo.holdingsWidth > 1) {
4234              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4235              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4236         }
4237       }
4238       CopyBoard(partnerBoard, board);
4239       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4240         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4241         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4242       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4243       if(toSqr = strchr(str, '-')) {
4244         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4245         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4246       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4247       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4248       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4249       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4250       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4251       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4252                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4253       DisplayMessage(partnerStatus, "");
4254         partnerBoardValid = TRUE;
4255       return;
4256     }
4257
4258     /* Modify behavior for initial board display on move listing
4259        of wild games.
4260        */
4261     switch (ics_getting_history) {
4262       case H_FALSE:
4263       case H_REQUESTED:
4264         break;
4265       case H_GOT_REQ_HEADER:
4266       case H_GOT_UNREQ_HEADER:
4267         /* This is the initial position of the current game */
4268         gamenum = ics_gamenum;
4269         moveNum = 0;            /* old ICS bug workaround */
4270         if (to_play == 'B') {
4271           startedFromSetupPosition = TRUE;
4272           blackPlaysFirst = TRUE;
4273           moveNum = 1;
4274           if (forwardMostMove == 0) forwardMostMove = 1;
4275           if (backwardMostMove == 0) backwardMostMove = 1;
4276           if (currentMove == 0) currentMove = 1;
4277         }
4278         newGameMode = gameMode;
4279         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4280         break;
4281       case H_GOT_UNWANTED_HEADER:
4282         /* This is an initial board that we don't want */
4283         return;
4284       case H_GETTING_MOVES:
4285         /* Should not happen */
4286         DisplayError(_("Error gathering move list: extra board"), 0);
4287         ics_getting_history = H_FALSE;
4288         return;
4289     }
4290
4291    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4292                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4293      /* [HGM] We seem to have switched variant unexpectedly
4294       * Try to guess new variant from board size
4295       */
4296           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4297           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4298           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4299           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4300           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4301           if(!weird) newVariant = VariantNormal;
4302           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4303           /* Get a move list just to see the header, which
4304              will tell us whether this is really bug or zh */
4305           if (ics_getting_history == H_FALSE) {
4306             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4307             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4308             SendToICS(str);
4309           }
4310     }
4311
4312     /* Take action if this is the first board of a new game, or of a
4313        different game than is currently being displayed.  */
4314     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4315         relation == RELATION_ISOLATED_BOARD) {
4316
4317         /* Forget the old game and get the history (if any) of the new one */
4318         if (gameMode != BeginningOfGame) {
4319           Reset(TRUE, TRUE);
4320         }
4321         newGame = TRUE;
4322         if (appData.autoRaiseBoard) BoardToTop();
4323         prevMove = -3;
4324         if (gamenum == -1) {
4325             newGameMode = IcsIdle;
4326         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4327                    appData.getMoveList && !reqFlag) {
4328             /* Need to get game history */
4329             ics_getting_history = H_REQUESTED;
4330             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4331             SendToICS(str);
4332         }
4333
4334         /* Initially flip the board to have black on the bottom if playing
4335            black or if the ICS flip flag is set, but let the user change
4336            it with the Flip View button. */
4337         flipView = appData.autoFlipView ?
4338           (newGameMode == IcsPlayingBlack) || ics_flip :
4339           appData.flipView;
4340
4341         /* Done with values from previous mode; copy in new ones */
4342         gameMode = newGameMode;
4343         ModeHighlight();
4344         ics_gamenum = gamenum;
4345         if (gamenum == gs_gamenum) {
4346             int klen = strlen(gs_kind);
4347             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4348             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4349             gameInfo.event = StrSave(str);
4350         } else {
4351             gameInfo.event = StrSave("ICS game");
4352         }
4353         gameInfo.site = StrSave(appData.icsHost);
4354         gameInfo.date = PGNDate();
4355         gameInfo.round = StrSave("-");
4356         gameInfo.white = StrSave(white);
4357         gameInfo.black = StrSave(black);
4358         timeControl = basetime * 60 * 1000;
4359         timeControl_2 = 0;
4360         timeIncrement = increment * 1000;
4361         movesPerSession = 0;
4362         gameInfo.timeControl = TimeControlTagValue();
4363         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4364   if (appData.debugMode) {
4365     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4366     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4367     setbuf(debugFP, NULL);
4368   }
4369
4370         gameInfo.outOfBook = NULL;
4371
4372         /* Do we have the ratings? */
4373         if (strcmp(player1Name, white) == 0 &&
4374             strcmp(player2Name, black) == 0) {
4375             if (appData.debugMode)
4376               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4377                       player1Rating, player2Rating);
4378             gameInfo.whiteRating = player1Rating;
4379             gameInfo.blackRating = player2Rating;
4380         } else if (strcmp(player2Name, white) == 0 &&
4381                    strcmp(player1Name, black) == 0) {
4382             if (appData.debugMode)
4383               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4384                       player2Rating, player1Rating);
4385             gameInfo.whiteRating = player2Rating;
4386             gameInfo.blackRating = player1Rating;
4387         }
4388         player1Name[0] = player2Name[0] = NULLCHAR;
4389
4390         /* Silence shouts if requested */
4391         if (appData.quietPlay &&
4392             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4393             SendToICS(ics_prefix);
4394             SendToICS("set shout 0\n");
4395         }
4396     }
4397
4398     /* Deal with midgame name changes */
4399     if (!newGame) {
4400         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4401             if (gameInfo.white) free(gameInfo.white);
4402             gameInfo.white = StrSave(white);
4403         }
4404         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4405             if (gameInfo.black) free(gameInfo.black);
4406             gameInfo.black = StrSave(black);
4407         }
4408     }
4409
4410     /* Throw away game result if anything actually changes in examine mode */
4411     if (gameMode == IcsExamining && !newGame) {
4412         gameInfo.result = GameUnfinished;
4413         if (gameInfo.resultDetails != NULL) {
4414             free(gameInfo.resultDetails);
4415             gameInfo.resultDetails = NULL;
4416         }
4417     }
4418
4419     /* In pausing && IcsExamining mode, we ignore boards coming
4420        in if they are in a different variation than we are. */
4421     if (pauseExamInvalid) return;
4422     if (pausing && gameMode == IcsExamining) {
4423         if (moveNum <= pauseExamForwardMostMove) {
4424             pauseExamInvalid = TRUE;
4425             forwardMostMove = pauseExamForwardMostMove;
4426             return;
4427         }
4428     }
4429
4430   if (appData.debugMode) {
4431     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4432   }
4433     /* Parse the board */
4434     for (k = 0; k < ranks; k++) {
4435       for (j = 0; j < files; j++)
4436         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4437       if(gameInfo.holdingsWidth > 1) {
4438            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4439            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4440       }
4441     }
4442     CopyBoard(boards[moveNum], board);
4443     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4444     if (moveNum == 0) {
4445         startedFromSetupPosition =
4446           !CompareBoards(board, initialPosition);
4447         if(startedFromSetupPosition)
4448             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4449     }
4450
4451     /* [HGM] Set castling rights. Take the outermost Rooks,
4452        to make it also work for FRC opening positions. Note that board12
4453        is really defective for later FRC positions, as it has no way to
4454        indicate which Rook can castle if they are on the same side of King.
4455        For the initial position we grant rights to the outermost Rooks,
4456        and remember thos rights, and we then copy them on positions
4457        later in an FRC game. This means WB might not recognize castlings with
4458        Rooks that have moved back to their original position as illegal,
4459        but in ICS mode that is not its job anyway.
4460     */
4461     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4462     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4463
4464         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4465             if(board[0][i] == WhiteRook) j = i;
4466         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4467         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4468             if(board[0][i] == WhiteRook) j = i;
4469         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4470         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4471             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4472         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4473         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4474             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4475         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4476
4477         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4478         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4479             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4480         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4481             if(board[BOARD_HEIGHT-1][k] == bKing)
4482                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4483         if(gameInfo.variant == VariantTwoKings) {
4484             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4485             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4486             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4487         }
4488     } else { int r;
4489         r = boards[moveNum][CASTLING][0] = initialRights[0];
4490         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4491         r = boards[moveNum][CASTLING][1] = initialRights[1];
4492         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4493         r = boards[moveNum][CASTLING][3] = initialRights[3];
4494         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4495         r = boards[moveNum][CASTLING][4] = initialRights[4];
4496         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4497         /* wildcastle kludge: always assume King has rights */
4498         r = boards[moveNum][CASTLING][2] = initialRights[2];
4499         r = boards[moveNum][CASTLING][5] = initialRights[5];
4500     }
4501     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4502     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4503
4504
4505     if (ics_getting_history == H_GOT_REQ_HEADER ||
4506         ics_getting_history == H_GOT_UNREQ_HEADER) {
4507         /* This was an initial position from a move list, not
4508            the current position */
4509         return;
4510     }
4511
4512     /* Update currentMove and known move number limits */
4513     newMove = newGame || moveNum > forwardMostMove;
4514
4515     if (newGame) {
4516         forwardMostMove = backwardMostMove = currentMove = moveNum;
4517         if (gameMode == IcsExamining && moveNum == 0) {
4518           /* Workaround for ICS limitation: we are not told the wild
4519              type when starting to examine a game.  But if we ask for
4520              the move list, the move list header will tell us */
4521             ics_getting_history = H_REQUESTED;
4522             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4523             SendToICS(str);
4524         }
4525     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4526                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4527 #if ZIPPY
4528         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4529         /* [HGM] applied this also to an engine that is silently watching        */
4530         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4531             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4532             gameInfo.variant == currentlyInitializedVariant) {
4533           takeback = forwardMostMove - moveNum;
4534           for (i = 0; i < takeback; i++) {
4535             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4536             SendToProgram("undo\n", &first);
4537           }
4538         }
4539 #endif
4540
4541         forwardMostMove = moveNum;
4542         if (!pausing || currentMove > forwardMostMove)
4543           currentMove = forwardMostMove;
4544     } else {
4545         /* New part of history that is not contiguous with old part */
4546         if (pausing && gameMode == IcsExamining) {
4547             pauseExamInvalid = TRUE;
4548             forwardMostMove = pauseExamForwardMostMove;
4549             return;
4550         }
4551         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4552 #if ZIPPY
4553             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4554                 // [HGM] when we will receive the move list we now request, it will be
4555                 // fed to the engine from the first move on. So if the engine is not
4556                 // in the initial position now, bring it there.
4557                 InitChessProgram(&first, 0);
4558             }
4559 #endif
4560             ics_getting_history = H_REQUESTED;
4561             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4562             SendToICS(str);
4563         }
4564         forwardMostMove = backwardMostMove = currentMove = moveNum;
4565     }
4566
4567     /* Update the clocks */
4568     if (strchr(elapsed_time, '.')) {
4569       /* Time is in ms */
4570       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4571       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4572     } else {
4573       /* Time is in seconds */
4574       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4575       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4576     }
4577
4578
4579 #if ZIPPY
4580     if (appData.zippyPlay && newGame &&
4581         gameMode != IcsObserving && gameMode != IcsIdle &&
4582         gameMode != IcsExamining)
4583       ZippyFirstBoard(moveNum, basetime, increment);
4584 #endif
4585
4586     /* Put the move on the move list, first converting
4587        to canonical algebraic form. */
4588     if (moveNum > 0) {
4589   if (appData.debugMode) {
4590     if (appData.debugMode) { int f = forwardMostMove;
4591         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4592                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4593                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4594     }
4595     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4596     fprintf(debugFP, "moveNum = %d\n", moveNum);
4597     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4598     setbuf(debugFP, NULL);
4599   }
4600         if (moveNum <= backwardMostMove) {
4601             /* We don't know what the board looked like before
4602                this move.  Punt. */
4603           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4604             strcat(parseList[moveNum - 1], " ");
4605             strcat(parseList[moveNum - 1], elapsed_time);
4606             moveList[moveNum - 1][0] = NULLCHAR;
4607         } else if (strcmp(move_str, "none") == 0) {
4608             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4609             /* Again, we don't know what the board looked like;
4610                this is really the start of the game. */
4611             parseList[moveNum - 1][0] = NULLCHAR;
4612             moveList[moveNum - 1][0] = NULLCHAR;
4613             backwardMostMove = moveNum;
4614             startedFromSetupPosition = TRUE;
4615             fromX = fromY = toX = toY = -1;
4616         } else {
4617           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4618           //                 So we parse the long-algebraic move string in stead of the SAN move
4619           int valid; char buf[MSG_SIZ], *prom;
4620
4621           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4622                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4623           // str looks something like "Q/a1-a2"; kill the slash
4624           if(str[1] == '/')
4625             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4626           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4627           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4628                 strcat(buf, prom); // long move lacks promo specification!
4629           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4630                 if(appData.debugMode)
4631                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4632                 safeStrCpy(move_str, buf, MSG_SIZ);
4633           }
4634           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4635                                 &fromX, &fromY, &toX, &toY, &promoChar)
4636                || ParseOneMove(buf, moveNum - 1, &moveType,
4637                                 &fromX, &fromY, &toX, &toY, &promoChar);
4638           // end of long SAN patch
4639           if (valid) {
4640             (void) CoordsToAlgebraic(boards[moveNum - 1],
4641                                      PosFlags(moveNum - 1),
4642                                      fromY, fromX, toY, toX, promoChar,
4643                                      parseList[moveNum-1]);
4644             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4645               case MT_NONE:
4646               case MT_STALEMATE:
4647               default:
4648                 break;
4649               case MT_CHECK:
4650                 if(gameInfo.variant != VariantShogi)
4651                     strcat(parseList[moveNum - 1], "+");
4652                 break;
4653               case MT_CHECKMATE:
4654               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4655                 strcat(parseList[moveNum - 1], "#");
4656                 break;
4657             }
4658             strcat(parseList[moveNum - 1], " ");
4659             strcat(parseList[moveNum - 1], elapsed_time);
4660             /* currentMoveString is set as a side-effect of ParseOneMove */
4661             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4662             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4663             strcat(moveList[moveNum - 1], "\n");
4664
4665             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4666                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4667               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4668                 ChessSquare old, new = boards[moveNum][k][j];
4669                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4670                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4671                   if(old == new) continue;
4672                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4673                   else if(new == WhiteWazir || new == BlackWazir) {
4674                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4675                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4676                       else boards[moveNum][k][j] = old; // preserve type of Gold
4677                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4678                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4679               }
4680           } else {
4681             /* Move from ICS was illegal!?  Punt. */
4682             if (appData.debugMode) {
4683               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4684               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4685             }
4686             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4687             strcat(parseList[moveNum - 1], " ");
4688             strcat(parseList[moveNum - 1], elapsed_time);
4689             moveList[moveNum - 1][0] = NULLCHAR;
4690             fromX = fromY = toX = toY = -1;
4691           }
4692         }
4693   if (appData.debugMode) {
4694     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4695     setbuf(debugFP, NULL);
4696   }
4697
4698 #if ZIPPY
4699         /* Send move to chess program (BEFORE animating it). */
4700         if (appData.zippyPlay && !newGame && newMove &&
4701            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4702
4703             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4704                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4705                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4706                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4707                             move_str);
4708                     DisplayError(str, 0);
4709                 } else {
4710                     if (first.sendTime) {
4711                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4712                     }
4713                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4714                     if (firstMove && !bookHit) {
4715                         firstMove = FALSE;
4716                         if (first.useColors) {
4717                           SendToProgram(gameMode == IcsPlayingWhite ?
4718                                         "white\ngo\n" :
4719                                         "black\ngo\n", &first);
4720                         } else {
4721                           SendToProgram("go\n", &first);
4722                         }
4723                         first.maybeThinking = TRUE;
4724                     }
4725                 }
4726             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4727               if (moveList[moveNum - 1][0] == NULLCHAR) {
4728                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4729                 DisplayError(str, 0);
4730               } else {
4731                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4732                 SendMoveToProgram(moveNum - 1, &first);
4733               }
4734             }
4735         }
4736 #endif
4737     }
4738
4739     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4740         /* If move comes from a remote source, animate it.  If it
4741            isn't remote, it will have already been animated. */
4742         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4743             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4744         }
4745         if (!pausing && appData.highlightLastMove) {
4746             SetHighlights(fromX, fromY, toX, toY);
4747         }
4748     }
4749
4750     /* Start the clocks */
4751     whiteFlag = blackFlag = FALSE;
4752     appData.clockMode = !(basetime == 0 && increment == 0);
4753     if (ticking == 0) {
4754       ics_clock_paused = TRUE;
4755       StopClocks();
4756     } else if (ticking == 1) {
4757       ics_clock_paused = FALSE;
4758     }
4759     if (gameMode == IcsIdle ||
4760         relation == RELATION_OBSERVING_STATIC ||
4761         relation == RELATION_EXAMINING ||
4762         ics_clock_paused)
4763       DisplayBothClocks();
4764     else
4765       StartClocks();
4766
4767     /* Display opponents and material strengths */
4768     if (gameInfo.variant != VariantBughouse &&
4769         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4770         if (tinyLayout || smallLayout) {
4771             if(gameInfo.variant == VariantNormal)
4772               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4773                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4774                     basetime, increment);
4775             else
4776               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4777                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4778                     basetime, increment, (int) gameInfo.variant);
4779         } else {
4780             if(gameInfo.variant == VariantNormal)
4781               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4782                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4783                     basetime, increment);
4784             else
4785               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4786                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4787                     basetime, increment, VariantName(gameInfo.variant));
4788         }
4789         DisplayTitle(str);
4790   if (appData.debugMode) {
4791     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4792   }
4793     }
4794
4795
4796     /* Display the board */
4797     if (!pausing && !appData.noGUI) {
4798
4799       if (appData.premove)
4800           if (!gotPremove ||
4801              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4802              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4803               ClearPremoveHighlights();
4804
4805       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4806         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4807       DrawPosition(j, boards[currentMove]);
4808
4809       DisplayMove(moveNum - 1);
4810       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4811             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4812               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4813         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4814       }
4815     }
4816
4817     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4818 #if ZIPPY
4819     if(bookHit) { // [HGM] book: simulate book reply
4820         static char bookMove[MSG_SIZ]; // a bit generous?
4821
4822         programStats.nodes = programStats.depth = programStats.time =
4823         programStats.score = programStats.got_only_move = 0;
4824         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4825
4826         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4827         strcat(bookMove, bookHit);
4828         HandleMachineMove(bookMove, &first);
4829     }
4830 #endif
4831 }
4832
4833 void
4834 GetMoveListEvent()
4835 {
4836     char buf[MSG_SIZ];
4837     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4838         ics_getting_history = H_REQUESTED;
4839         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4840         SendToICS(buf);
4841     }
4842 }
4843
4844 void
4845 AnalysisPeriodicEvent(force)
4846      int force;
4847 {
4848     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4849          && !force) || !appData.periodicUpdates)
4850       return;
4851
4852     /* Send . command to Crafty to collect stats */
4853     SendToProgram(".\n", &first);
4854
4855     /* Don't send another until we get a response (this makes
4856        us stop sending to old Crafty's which don't understand
4857        the "." command (sending illegal cmds resets node count & time,
4858        which looks bad)) */
4859     programStats.ok_to_send = 0;
4860 }
4861
4862 void ics_update_width(new_width)
4863         int new_width;
4864 {
4865         ics_printf("set width %d\n", new_width);
4866 }
4867
4868 void
4869 SendMoveToProgram(moveNum, cps)
4870      int moveNum;
4871      ChessProgramState *cps;
4872 {
4873     char buf[MSG_SIZ];
4874
4875     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4876         // null move in variant where engine does not understand it (for analysis purposes)
4877         SendBoard(cps, moveNum + 1); // send position after move in stead.
4878         return;
4879     }
4880     if (cps->useUsermove) {
4881       SendToProgram("usermove ", cps);
4882     }
4883     if (cps->useSAN) {
4884       char *space;
4885       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4886         int len = space - parseList[moveNum];
4887         memcpy(buf, parseList[moveNum], len);
4888         buf[len++] = '\n';
4889         buf[len] = NULLCHAR;
4890       } else {
4891         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4892       }
4893       SendToProgram(buf, cps);
4894     } else {
4895       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4896         AlphaRank(moveList[moveNum], 4);
4897         SendToProgram(moveList[moveNum], cps);
4898         AlphaRank(moveList[moveNum], 4); // and back
4899       } else
4900       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4901        * the engine. It would be nice to have a better way to identify castle
4902        * moves here. */
4903       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4904                                                                          && cps->useOOCastle) {
4905         int fromX = moveList[moveNum][0] - AAA;
4906         int fromY = moveList[moveNum][1] - ONE;
4907         int toX = moveList[moveNum][2] - AAA;
4908         int toY = moveList[moveNum][3] - ONE;
4909         if((boards[moveNum][fromY][fromX] == WhiteKing
4910             && boards[moveNum][toY][toX] == WhiteRook)
4911            || (boards[moveNum][fromY][fromX] == BlackKing
4912                && boards[moveNum][toY][toX] == BlackRook)) {
4913           if(toX > fromX) SendToProgram("O-O\n", cps);
4914           else SendToProgram("O-O-O\n", cps);
4915         }
4916         else SendToProgram(moveList[moveNum], cps);
4917       } else
4918       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4919         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4920           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4921           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4922                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4923         } else
4924           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4925                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4926         SendToProgram(buf, cps);
4927       }
4928       else SendToProgram(moveList[moveNum], cps);
4929       /* End of additions by Tord */
4930     }
4931
4932     /* [HGM] setting up the opening has brought engine in force mode! */
4933     /*       Send 'go' if we are in a mode where machine should play. */
4934     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4935         (gameMode == TwoMachinesPlay   ||
4936 #if ZIPPY
4937          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4938 #endif
4939          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4940         SendToProgram("go\n", cps);
4941   if (appData.debugMode) {
4942     fprintf(debugFP, "(extra)\n");
4943   }
4944     }
4945     setboardSpoiledMachineBlack = 0;
4946 }
4947
4948 void
4949 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4950      ChessMove moveType;
4951      int fromX, fromY, toX, toY;
4952      char promoChar;
4953 {
4954     char user_move[MSG_SIZ];
4955
4956     switch (moveType) {
4957       default:
4958         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4959                 (int)moveType, fromX, fromY, toX, toY);
4960         DisplayError(user_move + strlen("say "), 0);
4961         break;
4962       case WhiteKingSideCastle:
4963       case BlackKingSideCastle:
4964       case WhiteQueenSideCastleWild:
4965       case BlackQueenSideCastleWild:
4966       /* PUSH Fabien */
4967       case WhiteHSideCastleFR:
4968       case BlackHSideCastleFR:
4969       /* POP Fabien */
4970         snprintf(user_move, MSG_SIZ, "o-o\n");
4971         break;
4972       case WhiteQueenSideCastle:
4973       case BlackQueenSideCastle:
4974       case WhiteKingSideCastleWild:
4975       case BlackKingSideCastleWild:
4976       /* PUSH Fabien */
4977       case WhiteASideCastleFR:
4978       case BlackASideCastleFR:
4979       /* POP Fabien */
4980         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4981         break;
4982       case WhiteNonPromotion:
4983       case BlackNonPromotion:
4984         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4985         break;
4986       case WhitePromotion:
4987       case BlackPromotion:
4988         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4989           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4990                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4991                 PieceToChar(WhiteFerz));
4992         else if(gameInfo.variant == VariantGreat)
4993           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4994                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4995                 PieceToChar(WhiteMan));
4996         else
4997           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4998                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4999                 promoChar);
5000         break;
5001       case WhiteDrop:
5002       case BlackDrop:
5003       drop:
5004         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5005                  ToUpper(PieceToChar((ChessSquare) fromX)),
5006                  AAA + toX, ONE + toY);
5007         break;
5008       case IllegalMove:  /* could be a variant we don't quite understand */
5009         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5010       case NormalMove:
5011       case WhiteCapturesEnPassant:
5012       case BlackCapturesEnPassant:
5013         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5014                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5015         break;
5016     }
5017     SendToICS(user_move);
5018     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5019         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5020 }
5021
5022 void
5023 UploadGameEvent()
5024 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5025     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5026     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5027     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5028         DisplayError("You cannot do this while you are playing or observing", 0);
5029         return;
5030     }
5031     if(gameMode != IcsExamining) { // is this ever not the case?
5032         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5033
5034         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5035           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5036         } else { // on FICS we must first go to general examine mode
5037           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5038         }
5039         if(gameInfo.variant != VariantNormal) {
5040             // try figure out wild number, as xboard names are not always valid on ICS
5041             for(i=1; i<=36; i++) {
5042               snprintf(buf, MSG_SIZ, "wild/%d", i);
5043                 if(StringToVariant(buf) == gameInfo.variant) break;
5044             }
5045             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5046             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5047             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5048         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5049         SendToICS(ics_prefix);
5050         SendToICS(buf);
5051         if(startedFromSetupPosition || backwardMostMove != 0) {
5052           fen = PositionToFEN(backwardMostMove, NULL);
5053           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5054             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5055             SendToICS(buf);
5056           } else { // FICS: everything has to set by separate bsetup commands
5057             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5058             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5059             SendToICS(buf);
5060             if(!WhiteOnMove(backwardMostMove)) {
5061                 SendToICS("bsetup tomove black\n");
5062             }
5063             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5064             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5065             SendToICS(buf);
5066             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5067             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5068             SendToICS(buf);
5069             i = boards[backwardMostMove][EP_STATUS];
5070             if(i >= 0) { // set e.p.
5071               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5072                 SendToICS(buf);
5073             }
5074             bsetup++;
5075           }
5076         }
5077       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5078             SendToICS("bsetup done\n"); // switch to normal examining.
5079     }
5080     for(i = backwardMostMove; i<last; i++) {
5081         char buf[20];
5082         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5083         SendToICS(buf);
5084     }
5085     SendToICS(ics_prefix);
5086     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5087 }
5088
5089 void
5090 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5091      int rf, ff, rt, ft;
5092      char promoChar;
5093      char move[7];
5094 {
5095     if (rf == DROP_RANK) {
5096       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5097       sprintf(move, "%c@%c%c\n",
5098                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5099     } else {
5100         if (promoChar == 'x' || promoChar == NULLCHAR) {
5101           sprintf(move, "%c%c%c%c\n",
5102                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5103         } else {
5104             sprintf(move, "%c%c%c%c%c\n",
5105                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5106         }
5107     }
5108 }
5109
5110 void
5111 ProcessICSInitScript(f)
5112      FILE *f;
5113 {
5114     char buf[MSG_SIZ];
5115
5116     while (fgets(buf, MSG_SIZ, f)) {
5117         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5118     }
5119
5120     fclose(f);
5121 }
5122
5123
5124 static int lastX, lastY, selectFlag, dragging;
5125
5126 void
5127 Sweep(int step)
5128 {
5129     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5130     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5131     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5132     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5133     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5134     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5135     do {
5136         promoSweep -= step;
5137         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5138         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5139         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5140         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5141         if(!step) step = -1;
5142     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5143             appData.testLegality && (promoSweep == king ||
5144             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5145     ChangeDragPiece(promoSweep);
5146 }
5147
5148 int PromoScroll(int x, int y)
5149 {
5150   int step = 0;
5151
5152   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5153   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5154   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5155   if(!step) return FALSE;
5156   lastX = x; lastY = y;
5157   if((promoSweep < BlackPawn) == flipView) step = -step;
5158   if(step > 0) selectFlag = 1;
5159   if(!selectFlag) Sweep(step);
5160   return FALSE;
5161 }
5162
5163 void
5164 NextPiece(int step)
5165 {
5166     ChessSquare piece = boards[currentMove][toY][toX];
5167     do {
5168         pieceSweep -= step;
5169         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5170         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5171         if(!step) step = -1;
5172     } while(PieceToChar(pieceSweep) == '.');
5173     boards[currentMove][toY][toX] = pieceSweep;
5174     DrawPosition(FALSE, boards[currentMove]);
5175     boards[currentMove][toY][toX] = piece;
5176 }
5177 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5178 void
5179 AlphaRank(char *move, int n)
5180 {
5181 //    char *p = move, c; int x, y;
5182
5183     if (appData.debugMode) {
5184         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5185     }
5186
5187     if(move[1]=='*' &&
5188        move[2]>='0' && move[2]<='9' &&
5189        move[3]>='a' && move[3]<='x'    ) {
5190         move[1] = '@';
5191         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5192         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5193     } else
5194     if(move[0]>='0' && move[0]<='9' &&
5195        move[1]>='a' && move[1]<='x' &&
5196        move[2]>='0' && move[2]<='9' &&
5197        move[3]>='a' && move[3]<='x'    ) {
5198         /* input move, Shogi -> normal */
5199         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5200         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5201         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5202         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5203     } else
5204     if(move[1]=='@' &&
5205        move[3]>='0' && move[3]<='9' &&
5206        move[2]>='a' && move[2]<='x'    ) {
5207         move[1] = '*';
5208         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5209         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5210     } else
5211     if(
5212        move[0]>='a' && move[0]<='x' &&
5213        move[3]>='0' && move[3]<='9' &&
5214        move[2]>='a' && move[2]<='x'    ) {
5215          /* output move, normal -> Shogi */
5216         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5217         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5218         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5219         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5220         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5221     }
5222     if (appData.debugMode) {
5223         fprintf(debugFP, "   out = '%s'\n", move);
5224     }
5225 }
5226
5227 char yy_textstr[8000];
5228
5229 /* Parser for moves from gnuchess, ICS, or user typein box */
5230 Boolean
5231 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5232      char *move;
5233      int moveNum;
5234      ChessMove *moveType;
5235      int *fromX, *fromY, *toX, *toY;
5236      char *promoChar;
5237 {
5238     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5239
5240     switch (*moveType) {
5241       case WhitePromotion:
5242       case BlackPromotion:
5243       case WhiteNonPromotion:
5244       case BlackNonPromotion:
5245       case NormalMove:
5246       case WhiteCapturesEnPassant:
5247       case BlackCapturesEnPassant:
5248       case WhiteKingSideCastle:
5249       case WhiteQueenSideCastle:
5250       case BlackKingSideCastle:
5251       case BlackQueenSideCastle:
5252       case WhiteKingSideCastleWild:
5253       case WhiteQueenSideCastleWild:
5254       case BlackKingSideCastleWild:
5255       case BlackQueenSideCastleWild:
5256       /* Code added by Tord: */
5257       case WhiteHSideCastleFR:
5258       case WhiteASideCastleFR:
5259       case BlackHSideCastleFR:
5260       case BlackASideCastleFR:
5261       /* End of code added by Tord */
5262       case IllegalMove:         /* bug or odd chess variant */
5263         *fromX = currentMoveString[0] - AAA;
5264         *fromY = currentMoveString[1] - ONE;
5265         *toX = currentMoveString[2] - AAA;
5266         *toY = currentMoveString[3] - ONE;
5267         *promoChar = currentMoveString[4];
5268         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5269             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5270     if (appData.debugMode) {
5271         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5272     }
5273             *fromX = *fromY = *toX = *toY = 0;
5274             return FALSE;
5275         }
5276         if (appData.testLegality) {
5277           return (*moveType != IllegalMove);
5278         } else {
5279           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5280                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5281         }
5282
5283       case WhiteDrop:
5284       case BlackDrop:
5285         *fromX = *moveType == WhiteDrop ?
5286           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5287           (int) CharToPiece(ToLower(currentMoveString[0]));
5288         *fromY = DROP_RANK;
5289         *toX = currentMoveString[2] - AAA;
5290         *toY = currentMoveString[3] - ONE;
5291         *promoChar = NULLCHAR;
5292         return TRUE;
5293
5294       case AmbiguousMove:
5295       case ImpossibleMove:
5296       case EndOfFile:
5297       case ElapsedTime:
5298       case Comment:
5299       case PGNTag:
5300       case NAG:
5301       case WhiteWins:
5302       case BlackWins:
5303       case GameIsDrawn:
5304       default:
5305     if (appData.debugMode) {
5306         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5307     }
5308         /* bug? */
5309         *fromX = *fromY = *toX = *toY = 0;
5310         *promoChar = NULLCHAR;
5311         return FALSE;
5312     }
5313 }
5314
5315 Boolean pushed = FALSE;
5316 char *lastParseAttempt;
5317
5318 void
5319 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5320 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5321   int fromX, fromY, toX, toY; char promoChar;
5322   ChessMove moveType;
5323   Boolean valid;
5324   int nr = 0;
5325
5326   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5327     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5328     pushed = TRUE;
5329   }
5330   endPV = forwardMostMove;
5331   do {
5332     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5333     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5334     lastParseAttempt = pv;
5335     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5336 if(appData.debugMode){
5337 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);
5338 }
5339     if(!valid && nr == 0 &&
5340        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5341         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5342         // Hande case where played move is different from leading PV move
5343         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5344         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5345         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5346         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5347           endPV += 2; // if position different, keep this
5348           moveList[endPV-1][0] = fromX + AAA;
5349           moveList[endPV-1][1] = fromY + ONE;
5350           moveList[endPV-1][2] = toX + AAA;
5351           moveList[endPV-1][3] = toY + ONE;
5352           parseList[endPV-1][0] = NULLCHAR;
5353           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5354         }
5355       }
5356     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5357     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5358     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5359     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5360         valid++; // allow comments in PV
5361         continue;
5362     }
5363     nr++;
5364     if(endPV+1 > framePtr) break; // no space, truncate
5365     if(!valid) break;
5366     endPV++;
5367     CopyBoard(boards[endPV], boards[endPV-1]);
5368     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5369     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5370     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5371     CoordsToAlgebraic(boards[endPV - 1],
5372                              PosFlags(endPV - 1),
5373                              fromY, fromX, toY, toX, promoChar,
5374                              parseList[endPV - 1]);
5375   } while(valid);
5376   if(atEnd == 2) return; // used hidden, for PV conversion
5377   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5378   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5379   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5380                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5381   DrawPosition(TRUE, boards[currentMove]);
5382 }
5383
5384 int
5385 MultiPV(ChessProgramState *cps)
5386 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5387         int i;
5388         for(i=0; i<cps->nrOptions; i++)
5389             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5390                 return i;
5391         return -1;
5392 }
5393
5394 Boolean
5395 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5396 {
5397         int startPV, multi, lineStart, origIndex = index;
5398         char *p, buf2[MSG_SIZ];
5399
5400         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5401         lastX = x; lastY = y;
5402         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5403         lineStart = startPV = index;
5404         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5405         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5406         index = startPV;
5407         do{ while(buf[index] && buf[index] != '\n') index++;
5408         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5409         buf[index] = 0;
5410         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5411                 int n = first.option[multi].value;
5412                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5413                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5414                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5415                 first.option[multi].value = n;
5416                 *start = *end = 0;
5417                 return FALSE;
5418         }
5419         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5420         *start = startPV; *end = index-1;
5421         return TRUE;
5422 }
5423
5424 char *
5425 PvToSAN(char *pv)
5426 {
5427         static char buf[10*MSG_SIZ];
5428         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5429         *buf = NULLCHAR;
5430         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5431         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5432         for(i = forwardMostMove; i<endPV; i++){
5433             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5434             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5435             k += strlen(buf+k);
5436         }
5437         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5438         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5439         endPV = savedEnd;
5440         return buf;
5441 }
5442
5443 Boolean
5444 LoadPV(int x, int y)
5445 { // called on right mouse click to load PV
5446   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5447   lastX = x; lastY = y;
5448   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5449   return TRUE;
5450 }
5451
5452 void
5453 UnLoadPV()
5454 {
5455   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5456   if(endPV < 0) return;
5457   endPV = -1;
5458   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5459         Boolean saveAnimate = appData.animate;
5460         if(pushed) {
5461             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5462                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5463             } else storedGames--; // abandon shelved tail of original game
5464         }
5465         pushed = FALSE;
5466         forwardMostMove = currentMove;
5467         currentMove = oldFMM;
5468         appData.animate = FALSE;
5469         ToNrEvent(forwardMostMove);
5470         appData.animate = saveAnimate;
5471   }
5472   currentMove = forwardMostMove;
5473   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5474   ClearPremoveHighlights();
5475   DrawPosition(TRUE, boards[currentMove]);
5476 }
5477
5478 void
5479 MovePV(int x, int y, int h)
5480 { // step through PV based on mouse coordinates (called on mouse move)
5481   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5482
5483   // we must somehow check if right button is still down (might be released off board!)
5484   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5485   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5486   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5487   if(!step) return;
5488   lastX = x; lastY = y;
5489
5490   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5491   if(endPV < 0) return;
5492   if(y < margin) step = 1; else
5493   if(y > h - margin) step = -1;
5494   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5495   currentMove += step;
5496   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5497   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5498                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5499   DrawPosition(FALSE, boards[currentMove]);
5500 }
5501
5502
5503 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5504 // All positions will have equal probability, but the current method will not provide a unique
5505 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5506 #define DARK 1
5507 #define LITE 2
5508 #define ANY 3
5509
5510 int squaresLeft[4];
5511 int piecesLeft[(int)BlackPawn];
5512 int seed, nrOfShuffles;
5513
5514 void GetPositionNumber()
5515 {       // sets global variable seed
5516         int i;
5517
5518         seed = appData.defaultFrcPosition;
5519         if(seed < 0) { // randomize based on time for negative FRC position numbers
5520                 for(i=0; i<50; i++) seed += random();
5521                 seed = random() ^ random() >> 8 ^ random() << 8;
5522                 if(seed<0) seed = -seed;
5523         }
5524 }
5525
5526 int put(Board board, int pieceType, int rank, int n, int shade)
5527 // put the piece on the (n-1)-th empty squares of the given shade
5528 {
5529         int i;
5530
5531         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5532                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5533                         board[rank][i] = (ChessSquare) pieceType;
5534                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5535                         squaresLeft[ANY]--;
5536                         piecesLeft[pieceType]--;
5537                         return i;
5538                 }
5539         }
5540         return -1;
5541 }
5542
5543
5544 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5545 // calculate where the next piece goes, (any empty square), and put it there
5546 {
5547         int i;
5548
5549         i = seed % squaresLeft[shade];
5550         nrOfShuffles *= squaresLeft[shade];
5551         seed /= squaresLeft[shade];
5552         put(board, pieceType, rank, i, shade);
5553 }
5554
5555 void AddTwoPieces(Board board, int pieceType, int rank)
5556 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5557 {
5558         int i, n=squaresLeft[ANY], j=n-1, k;
5559
5560         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5561         i = seed % k;  // pick one
5562         nrOfShuffles *= k;
5563         seed /= k;
5564         while(i >= j) i -= j--;
5565         j = n - 1 - j; i += j;
5566         put(board, pieceType, rank, j, ANY);
5567         put(board, pieceType, rank, i, ANY);
5568 }
5569
5570 void SetUpShuffle(Board board, int number)
5571 {
5572         int i, p, first=1;
5573
5574         GetPositionNumber(); nrOfShuffles = 1;
5575
5576         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5577         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5578         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5579
5580         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5581
5582         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5583             p = (int) board[0][i];
5584             if(p < (int) BlackPawn) piecesLeft[p] ++;
5585             board[0][i] = EmptySquare;
5586         }
5587
5588         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5589             // shuffles restricted to allow normal castling put KRR first
5590             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5591                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5592             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5593                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5594             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5595                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5596             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5597                 put(board, WhiteRook, 0, 0, ANY);
5598             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5599         }
5600
5601         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5602             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5603             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5604                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5605                 while(piecesLeft[p] >= 2) {
5606                     AddOnePiece(board, p, 0, LITE);
5607                     AddOnePiece(board, p, 0, DARK);
5608                 }
5609                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5610             }
5611
5612         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5613             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5614             // but we leave King and Rooks for last, to possibly obey FRC restriction
5615             if(p == (int)WhiteRook) continue;
5616             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5617             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5618         }
5619
5620         // now everything is placed, except perhaps King (Unicorn) and Rooks
5621
5622         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5623             // Last King gets castling rights
5624             while(piecesLeft[(int)WhiteUnicorn]) {
5625                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5626                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5627             }
5628
5629             while(piecesLeft[(int)WhiteKing]) {
5630                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5631                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5632             }
5633
5634
5635         } else {
5636             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5637             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5638         }
5639
5640         // Only Rooks can be left; simply place them all
5641         while(piecesLeft[(int)WhiteRook]) {
5642                 i = put(board, WhiteRook, 0, 0, ANY);
5643                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5644                         if(first) {
5645                                 first=0;
5646                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5647                         }
5648                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5649                 }
5650         }
5651         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5652             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5653         }
5654
5655         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5656 }
5657
5658 int SetCharTable( char *table, const char * map )
5659 /* [HGM] moved here from winboard.c because of its general usefulness */
5660 /*       Basically a safe strcpy that uses the last character as King */
5661 {
5662     int result = FALSE; int NrPieces;
5663
5664     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5665                     && NrPieces >= 12 && !(NrPieces&1)) {
5666         int i; /* [HGM] Accept even length from 12 to 34 */
5667
5668         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5669         for( i=0; i<NrPieces/2-1; i++ ) {
5670             table[i] = map[i];
5671             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5672         }
5673         table[(int) WhiteKing]  = map[NrPieces/2-1];
5674         table[(int) BlackKing]  = map[NrPieces-1];
5675
5676         result = TRUE;
5677     }
5678
5679     return result;
5680 }
5681
5682 void Prelude(Board board)
5683 {       // [HGM] superchess: random selection of exo-pieces
5684         int i, j, k; ChessSquare p;
5685         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5686
5687         GetPositionNumber(); // use FRC position number
5688
5689         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5690             SetCharTable(pieceToChar, appData.pieceToCharTable);
5691             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5692                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5693         }
5694
5695         j = seed%4;                 seed /= 4;
5696         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5697         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5698         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5699         j = seed%3 + (seed%3 >= j); seed /= 3;
5700         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5701         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5702         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5703         j = seed%3;                 seed /= 3;
5704         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5705         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5706         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5707         j = seed%2 + (seed%2 >= j); seed /= 2;
5708         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5709         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5710         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5711         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5712         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5713         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5714         put(board, exoPieces[0],    0, 0, ANY);
5715         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5716 }
5717
5718 void
5719 InitPosition(redraw)
5720      int redraw;
5721 {
5722     ChessSquare (* pieces)[BOARD_FILES];
5723     int i, j, pawnRow, overrule,
5724     oldx = gameInfo.boardWidth,
5725     oldy = gameInfo.boardHeight,
5726     oldh = gameInfo.holdingsWidth;
5727     static int oldv;
5728
5729     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5730
5731     /* [AS] Initialize pv info list [HGM] and game status */
5732     {
5733         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5734             pvInfoList[i].depth = 0;
5735             boards[i][EP_STATUS] = EP_NONE;
5736             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5737         }
5738
5739         initialRulePlies = 0; /* 50-move counter start */
5740
5741         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5742         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5743     }
5744
5745
5746     /* [HGM] logic here is completely changed. In stead of full positions */
5747     /* the initialized data only consist of the two backranks. The switch */
5748     /* selects which one we will use, which is than copied to the Board   */
5749     /* initialPosition, which for the rest is initialized by Pawns and    */
5750     /* empty squares. This initial position is then copied to boards[0],  */
5751     /* possibly after shuffling, so that it remains available.            */
5752
5753     gameInfo.holdingsWidth = 0; /* default board sizes */
5754     gameInfo.boardWidth    = 8;
5755     gameInfo.boardHeight   = 8;
5756     gameInfo.holdingsSize  = 0;
5757     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5758     for(i=0; i<BOARD_FILES-2; i++)
5759       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5760     initialPosition[EP_STATUS] = EP_NONE;
5761     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5762     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5763          SetCharTable(pieceNickName, appData.pieceNickNames);
5764     else SetCharTable(pieceNickName, "............");
5765     pieces = FIDEArray;
5766
5767     switch (gameInfo.variant) {
5768     case VariantFischeRandom:
5769       shuffleOpenings = TRUE;
5770     default:
5771       break;
5772     case VariantShatranj:
5773       pieces = ShatranjArray;
5774       nrCastlingRights = 0;
5775       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5776       break;
5777     case VariantMakruk:
5778       pieces = makrukArray;
5779       nrCastlingRights = 0;
5780       startedFromSetupPosition = TRUE;
5781       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5782       break;
5783     case VariantTwoKings:
5784       pieces = twoKingsArray;
5785       break;
5786     case VariantGrand:
5787       pieces = GrandArray;
5788       nrCastlingRights = 0;
5789       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5790       gameInfo.boardWidth = 10;
5791       gameInfo.boardHeight = 10;
5792       gameInfo.holdingsSize = 7;
5793       break;
5794     case VariantCapaRandom:
5795       shuffleOpenings = TRUE;
5796     case VariantCapablanca:
5797       pieces = CapablancaArray;
5798       gameInfo.boardWidth = 10;
5799       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5800       break;
5801     case VariantGothic:
5802       pieces = GothicArray;
5803       gameInfo.boardWidth = 10;
5804       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5805       break;
5806     case VariantSChess:
5807       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5808       gameInfo.holdingsSize = 7;
5809       break;
5810     case VariantJanus:
5811       pieces = JanusArray;
5812       gameInfo.boardWidth = 10;
5813       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5814       nrCastlingRights = 6;
5815         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5816         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5817         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5818         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5819         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5820         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5821       break;
5822     case VariantFalcon:
5823       pieces = FalconArray;
5824       gameInfo.boardWidth = 10;
5825       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5826       break;
5827     case VariantXiangqi:
5828       pieces = XiangqiArray;
5829       gameInfo.boardWidth  = 9;
5830       gameInfo.boardHeight = 10;
5831       nrCastlingRights = 0;
5832       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5833       break;
5834     case VariantShogi:
5835       pieces = ShogiArray;
5836       gameInfo.boardWidth  = 9;
5837       gameInfo.boardHeight = 9;
5838       gameInfo.holdingsSize = 7;
5839       nrCastlingRights = 0;
5840       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5841       break;
5842     case VariantCourier:
5843       pieces = CourierArray;
5844       gameInfo.boardWidth  = 12;
5845       nrCastlingRights = 0;
5846       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5847       break;
5848     case VariantKnightmate:
5849       pieces = KnightmateArray;
5850       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5851       break;
5852     case VariantSpartan:
5853       pieces = SpartanArray;
5854       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5855       break;
5856     case VariantFairy:
5857       pieces = fairyArray;
5858       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5859       break;
5860     case VariantGreat:
5861       pieces = GreatArray;
5862       gameInfo.boardWidth = 10;
5863       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5864       gameInfo.holdingsSize = 8;
5865       break;
5866     case VariantSuper:
5867       pieces = FIDEArray;
5868       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5869       gameInfo.holdingsSize = 8;
5870       startedFromSetupPosition = TRUE;
5871       break;
5872     case VariantCrazyhouse:
5873     case VariantBughouse:
5874       pieces = FIDEArray;
5875       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5876       gameInfo.holdingsSize = 5;
5877       break;
5878     case VariantWildCastle:
5879       pieces = FIDEArray;
5880       /* !!?shuffle with kings guaranteed to be on d or e file */
5881       shuffleOpenings = 1;
5882       break;
5883     case VariantNoCastle:
5884       pieces = FIDEArray;
5885       nrCastlingRights = 0;
5886       /* !!?unconstrained back-rank shuffle */
5887       shuffleOpenings = 1;
5888       break;
5889     }
5890
5891     overrule = 0;
5892     if(appData.NrFiles >= 0) {
5893         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5894         gameInfo.boardWidth = appData.NrFiles;
5895     }
5896     if(appData.NrRanks >= 0) {
5897         gameInfo.boardHeight = appData.NrRanks;
5898     }
5899     if(appData.holdingsSize >= 0) {
5900         i = appData.holdingsSize;
5901         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5902         gameInfo.holdingsSize = i;
5903     }
5904     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5905     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5906         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5907
5908     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5909     if(pawnRow < 1) pawnRow = 1;
5910     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5911
5912     /* User pieceToChar list overrules defaults */
5913     if(appData.pieceToCharTable != NULL)
5914         SetCharTable(pieceToChar, appData.pieceToCharTable);
5915
5916     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5917
5918         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5919             s = (ChessSquare) 0; /* account holding counts in guard band */
5920         for( i=0; i<BOARD_HEIGHT; i++ )
5921             initialPosition[i][j] = s;
5922
5923         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5924         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5925         initialPosition[pawnRow][j] = WhitePawn;
5926         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5927         if(gameInfo.variant == VariantXiangqi) {
5928             if(j&1) {
5929                 initialPosition[pawnRow][j] =
5930                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5931                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5932                    initialPosition[2][j] = WhiteCannon;
5933                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5934                 }
5935             }
5936         }
5937         if(gameInfo.variant == VariantGrand) {
5938             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5939                initialPosition[0][j] = WhiteRook;
5940                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5941             }
5942         }
5943         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5944     }
5945     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5946
5947             j=BOARD_LEFT+1;
5948             initialPosition[1][j] = WhiteBishop;
5949             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5950             j=BOARD_RGHT-2;
5951             initialPosition[1][j] = WhiteRook;
5952             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5953     }
5954
5955     if( nrCastlingRights == -1) {
5956         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5957         /*       This sets default castling rights from none to normal corners   */
5958         /* Variants with other castling rights must set them themselves above    */
5959         nrCastlingRights = 6;
5960
5961         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5962         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5963         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5964         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5965         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5966         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5967      }
5968
5969      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5970      if(gameInfo.variant == VariantGreat) { // promotion commoners
5971         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5972         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5973         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5974         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5975      }
5976      if( gameInfo.variant == VariantSChess ) {
5977       initialPosition[1][0] = BlackMarshall;
5978       initialPosition[2][0] = BlackAngel;
5979       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5980       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5981       initialPosition[1][1] = initialPosition[2][1] = 
5982       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5983      }
5984   if (appData.debugMode) {
5985     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5986   }
5987     if(shuffleOpenings) {
5988         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5989         startedFromSetupPosition = TRUE;
5990     }
5991     if(startedFromPositionFile) {
5992       /* [HGM] loadPos: use PositionFile for every new game */
5993       CopyBoard(initialPosition, filePosition);
5994       for(i=0; i<nrCastlingRights; i++)
5995           initialRights[i] = filePosition[CASTLING][i];
5996       startedFromSetupPosition = TRUE;
5997     }
5998
5999     CopyBoard(boards[0], initialPosition);
6000
6001     if(oldx != gameInfo.boardWidth ||
6002        oldy != gameInfo.boardHeight ||
6003        oldv != gameInfo.variant ||
6004        oldh != gameInfo.holdingsWidth
6005                                          )
6006             InitDrawingSizes(-2 ,0);
6007
6008     oldv = gameInfo.variant;
6009     if (redraw)
6010       DrawPosition(TRUE, boards[currentMove]);
6011 }
6012
6013 void
6014 SendBoard(cps, moveNum)
6015      ChessProgramState *cps;
6016      int moveNum;
6017 {
6018     char message[MSG_SIZ];
6019
6020     if (cps->useSetboard) {
6021       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6022       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6023       SendToProgram(message, cps);
6024       free(fen);
6025
6026     } else {
6027       ChessSquare *bp;
6028       int i, j;
6029       /* Kludge to set black to move, avoiding the troublesome and now
6030        * deprecated "black" command.
6031        */
6032       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6033         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6034
6035       SendToProgram("edit\n", cps);
6036       SendToProgram("#\n", cps);
6037       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6038         bp = &boards[moveNum][i][BOARD_LEFT];
6039         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6040           if ((int) *bp < (int) BlackPawn) {
6041             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6042                     AAA + j, ONE + i);
6043             if(message[0] == '+' || message[0] == '~') {
6044               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6045                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6046                         AAA + j, ONE + i);
6047             }
6048             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6049                 message[1] = BOARD_RGHT   - 1 - j + '1';
6050                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6051             }
6052             SendToProgram(message, cps);
6053           }
6054         }
6055       }
6056
6057       SendToProgram("c\n", cps);
6058       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6059         bp = &boards[moveNum][i][BOARD_LEFT];
6060         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6061           if (((int) *bp != (int) EmptySquare)
6062               && ((int) *bp >= (int) BlackPawn)) {
6063             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6064                     AAA + j, ONE + i);
6065             if(message[0] == '+' || message[0] == '~') {
6066               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6067                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6068                         AAA + j, ONE + i);
6069             }
6070             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6071                 message[1] = BOARD_RGHT   - 1 - j + '1';
6072                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6073             }
6074             SendToProgram(message, cps);
6075           }
6076         }
6077       }
6078
6079       SendToProgram(".\n", cps);
6080     }
6081     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6082 }
6083
6084 ChessSquare
6085 DefaultPromoChoice(int white)
6086 {
6087     ChessSquare result;
6088     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6089         result = WhiteFerz; // no choice
6090     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6091         result= WhiteKing; // in Suicide Q is the last thing we want
6092     else if(gameInfo.variant == VariantSpartan)
6093         result = white ? WhiteQueen : WhiteAngel;
6094     else result = WhiteQueen;
6095     if(!white) result = WHITE_TO_BLACK result;
6096     return result;
6097 }
6098
6099 static int autoQueen; // [HGM] oneclick
6100
6101 int
6102 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6103 {
6104     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6105     /* [HGM] add Shogi promotions */
6106     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6107     ChessSquare piece;
6108     ChessMove moveType;
6109     Boolean premove;
6110
6111     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6112     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6113
6114     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6115       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6116         return FALSE;
6117
6118     piece = boards[currentMove][fromY][fromX];
6119     if(gameInfo.variant == VariantShogi) {
6120         promotionZoneSize = BOARD_HEIGHT/3;
6121         highestPromotingPiece = (int)WhiteFerz;
6122     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6123         promotionZoneSize = 3;
6124     }
6125
6126     // Treat Lance as Pawn when it is not representing Amazon
6127     if(gameInfo.variant != VariantSuper) {
6128         if(piece == WhiteLance) piece = WhitePawn; else
6129         if(piece == BlackLance) piece = BlackPawn;
6130     }
6131
6132     // next weed out all moves that do not touch the promotion zone at all
6133     if((int)piece >= BlackPawn) {
6134         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6135              return FALSE;
6136         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6137     } else {
6138         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6139            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6140     }
6141
6142     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6143
6144     // weed out mandatory Shogi promotions
6145     if(gameInfo.variant == VariantShogi) {
6146         if(piece >= BlackPawn) {
6147             if(toY == 0 && piece == BlackPawn ||
6148                toY == 0 && piece == BlackQueen ||
6149                toY <= 1 && piece == BlackKnight) {
6150                 *promoChoice = '+';
6151                 return FALSE;
6152             }
6153         } else {
6154             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6155                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6156                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6157                 *promoChoice = '+';
6158                 return FALSE;
6159             }
6160         }
6161     }
6162
6163     // weed out obviously illegal Pawn moves
6164     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6165         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6166         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6167         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6168         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6169         // note we are not allowed to test for valid (non-)capture, due to premove
6170     }
6171
6172     // we either have a choice what to promote to, or (in Shogi) whether to promote
6173     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6174         *promoChoice = PieceToChar(BlackFerz);  // no choice
6175         return FALSE;
6176     }
6177     // no sense asking what we must promote to if it is going to explode...
6178     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6179         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6180         return FALSE;
6181     }
6182     // give caller the default choice even if we will not make it
6183     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6184     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6185     if(        sweepSelect && gameInfo.variant != VariantGreat
6186                            && gameInfo.variant != VariantGrand
6187                            && gameInfo.variant != VariantSuper) return FALSE;
6188     if(autoQueen) return FALSE; // predetermined
6189
6190     // suppress promotion popup on illegal moves that are not premoves
6191     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6192               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6193     if(appData.testLegality && !premove) {
6194         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6195                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6196         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6197             return FALSE;
6198     }
6199
6200     return TRUE;
6201 }
6202
6203 int
6204 InPalace(row, column)
6205      int row, column;
6206 {   /* [HGM] for Xiangqi */
6207     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6208          column < (BOARD_WIDTH + 4)/2 &&
6209          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6210     return FALSE;
6211 }
6212
6213 int
6214 PieceForSquare (x, y)
6215      int x;
6216      int y;
6217 {
6218   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6219      return -1;
6220   else
6221      return boards[currentMove][y][x];
6222 }
6223
6224 int
6225 OKToStartUserMove(x, y)
6226      int x, y;
6227 {
6228     ChessSquare from_piece;
6229     int white_piece;
6230
6231     if (matchMode) return FALSE;
6232     if (gameMode == EditPosition) return TRUE;
6233
6234     if (x >= 0 && y >= 0)
6235       from_piece = boards[currentMove][y][x];
6236     else
6237       from_piece = EmptySquare;
6238
6239     if (from_piece == EmptySquare) return FALSE;
6240
6241     white_piece = (int)from_piece >= (int)WhitePawn &&
6242       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6243
6244     switch (gameMode) {
6245       case AnalyzeFile:
6246       case TwoMachinesPlay:
6247       case EndOfGame:
6248         return FALSE;
6249
6250       case IcsObserving:
6251       case IcsIdle:
6252         return FALSE;
6253
6254       case MachinePlaysWhite:
6255       case IcsPlayingBlack:
6256         if (appData.zippyPlay) return FALSE;
6257         if (white_piece) {
6258             DisplayMoveError(_("You are playing Black"));
6259             return FALSE;
6260         }
6261         break;
6262
6263       case MachinePlaysBlack:
6264       case IcsPlayingWhite:
6265         if (appData.zippyPlay) return FALSE;
6266         if (!white_piece) {
6267             DisplayMoveError(_("You are playing White"));
6268             return FALSE;
6269         }
6270         break;
6271
6272       case PlayFromGameFile:
6273             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6274       case EditGame:
6275         if (!white_piece && WhiteOnMove(currentMove)) {
6276             DisplayMoveError(_("It is White's turn"));
6277             return FALSE;
6278         }
6279         if (white_piece && !WhiteOnMove(currentMove)) {
6280             DisplayMoveError(_("It is Black's turn"));
6281             return FALSE;
6282         }
6283         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6284             /* Editing correspondence game history */
6285             /* Could disallow this or prompt for confirmation */
6286             cmailOldMove = -1;
6287         }
6288         break;
6289
6290       case BeginningOfGame:
6291         if (appData.icsActive) return FALSE;
6292         if (!appData.noChessProgram) {
6293             if (!white_piece) {
6294                 DisplayMoveError(_("You are playing White"));
6295                 return FALSE;
6296             }
6297         }
6298         break;
6299
6300       case Training:
6301         if (!white_piece && WhiteOnMove(currentMove)) {
6302             DisplayMoveError(_("It is White's turn"));
6303             return FALSE;
6304         }
6305         if (white_piece && !WhiteOnMove(currentMove)) {
6306             DisplayMoveError(_("It is Black's turn"));
6307             return FALSE;
6308         }
6309         break;
6310
6311       default:
6312       case IcsExamining:
6313         break;
6314     }
6315     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6316         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6317         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6318         && gameMode != AnalyzeFile && gameMode != Training) {
6319         DisplayMoveError(_("Displayed position is not current"));
6320         return FALSE;
6321     }
6322     return TRUE;
6323 }
6324
6325 Boolean
6326 OnlyMove(int *x, int *y, Boolean captures) {
6327     DisambiguateClosure cl;
6328     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6329     switch(gameMode) {
6330       case MachinePlaysBlack:
6331       case IcsPlayingWhite:
6332       case BeginningOfGame:
6333         if(!WhiteOnMove(currentMove)) return FALSE;
6334         break;
6335       case MachinePlaysWhite:
6336       case IcsPlayingBlack:
6337         if(WhiteOnMove(currentMove)) return FALSE;
6338         break;
6339       case EditGame:
6340         break;
6341       default:
6342         return FALSE;
6343     }
6344     cl.pieceIn = EmptySquare;
6345     cl.rfIn = *y;
6346     cl.ffIn = *x;
6347     cl.rtIn = -1;
6348     cl.ftIn = -1;
6349     cl.promoCharIn = NULLCHAR;
6350     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6351     if( cl.kind == NormalMove ||
6352         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6353         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6354         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6355       fromX = cl.ff;
6356       fromY = cl.rf;
6357       *x = cl.ft;
6358       *y = cl.rt;
6359       return TRUE;
6360     }
6361     if(cl.kind != ImpossibleMove) return FALSE;
6362     cl.pieceIn = EmptySquare;
6363     cl.rfIn = -1;
6364     cl.ffIn = -1;
6365     cl.rtIn = *y;
6366     cl.ftIn = *x;
6367     cl.promoCharIn = NULLCHAR;
6368     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6369     if( cl.kind == NormalMove ||
6370         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6371         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6372         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6373       fromX = cl.ff;
6374       fromY = cl.rf;
6375       *x = cl.ft;
6376       *y = cl.rt;
6377       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6378       return TRUE;
6379     }
6380     return FALSE;
6381 }
6382
6383 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6384 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6385 int lastLoadGameUseList = FALSE;
6386 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6387 ChessMove lastLoadGameStart = EndOfFile;
6388
6389 void
6390 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6391      int fromX, fromY, toX, toY;
6392      int promoChar;
6393 {
6394     ChessMove moveType;
6395     ChessSquare pdown, pup;
6396
6397     /* Check if the user is playing in turn.  This is complicated because we
6398        let the user "pick up" a piece before it is his turn.  So the piece he
6399        tried to pick up may have been captured by the time he puts it down!
6400        Therefore we use the color the user is supposed to be playing in this
6401        test, not the color of the piece that is currently on the starting
6402        square---except in EditGame mode, where the user is playing both
6403        sides; fortunately there the capture race can't happen.  (It can
6404        now happen in IcsExamining mode, but that's just too bad.  The user
6405        will get a somewhat confusing message in that case.)
6406        */
6407
6408     switch (gameMode) {
6409       case AnalyzeFile:
6410       case TwoMachinesPlay:
6411       case EndOfGame:
6412       case IcsObserving:
6413       case IcsIdle:
6414         /* We switched into a game mode where moves are not accepted,
6415            perhaps while the mouse button was down. */
6416         return;
6417
6418       case MachinePlaysWhite:
6419         /* User is moving for Black */
6420         if (WhiteOnMove(currentMove)) {
6421             DisplayMoveError(_("It is White's turn"));
6422             return;
6423         }
6424         break;
6425
6426       case MachinePlaysBlack:
6427         /* User is moving for White */
6428         if (!WhiteOnMove(currentMove)) {
6429             DisplayMoveError(_("It is Black's turn"));
6430             return;
6431         }
6432         break;
6433
6434       case PlayFromGameFile:
6435             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6436       case EditGame:
6437       case IcsExamining:
6438       case BeginningOfGame:
6439       case AnalyzeMode:
6440       case Training:
6441         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6442         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6443             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6444             /* User is moving for Black */
6445             if (WhiteOnMove(currentMove)) {
6446                 DisplayMoveError(_("It is White's turn"));
6447                 return;
6448             }
6449         } else {
6450             /* User is moving for White */
6451             if (!WhiteOnMove(currentMove)) {
6452                 DisplayMoveError(_("It is Black's turn"));
6453                 return;
6454             }
6455         }
6456         break;
6457
6458       case IcsPlayingBlack:
6459         /* User is moving for Black */
6460         if (WhiteOnMove(currentMove)) {
6461             if (!appData.premove) {
6462                 DisplayMoveError(_("It is White's turn"));
6463             } else if (toX >= 0 && toY >= 0) {
6464                 premoveToX = toX;
6465                 premoveToY = toY;
6466                 premoveFromX = fromX;
6467                 premoveFromY = fromY;
6468                 premovePromoChar = promoChar;
6469                 gotPremove = 1;
6470                 if (appData.debugMode)
6471                     fprintf(debugFP, "Got premove: fromX %d,"
6472                             "fromY %d, toX %d, toY %d\n",
6473                             fromX, fromY, toX, toY);
6474             }
6475             return;
6476         }
6477         break;
6478
6479       case IcsPlayingWhite:
6480         /* User is moving for White */
6481         if (!WhiteOnMove(currentMove)) {
6482             if (!appData.premove) {
6483                 DisplayMoveError(_("It is Black's turn"));
6484             } else if (toX >= 0 && toY >= 0) {
6485                 premoveToX = toX;
6486                 premoveToY = toY;
6487                 premoveFromX = fromX;
6488                 premoveFromY = fromY;
6489                 premovePromoChar = promoChar;
6490                 gotPremove = 1;
6491                 if (appData.debugMode)
6492                     fprintf(debugFP, "Got premove: fromX %d,"
6493                             "fromY %d, toX %d, toY %d\n",
6494                             fromX, fromY, toX, toY);
6495             }
6496             return;
6497         }
6498         break;
6499
6500       default:
6501         break;
6502
6503       case EditPosition:
6504         /* EditPosition, empty square, or different color piece;
6505            click-click move is possible */
6506         if (toX == -2 || toY == -2) {
6507             boards[0][fromY][fromX] = EmptySquare;
6508             DrawPosition(FALSE, boards[currentMove]);
6509             return;
6510         } else if (toX >= 0 && toY >= 0) {
6511             boards[0][toY][toX] = boards[0][fromY][fromX];
6512             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6513                 if(boards[0][fromY][0] != EmptySquare) {
6514                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6515                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6516                 }
6517             } else
6518             if(fromX == BOARD_RGHT+1) {
6519                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6520                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6521                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6522                 }
6523             } else
6524             boards[0][fromY][fromX] = EmptySquare;
6525             DrawPosition(FALSE, boards[currentMove]);
6526             return;
6527         }
6528         return;
6529     }
6530
6531     if(toX < 0 || toY < 0) return;
6532     pdown = boards[currentMove][fromY][fromX];
6533     pup = boards[currentMove][toY][toX];
6534
6535     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6536     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6537          if( pup != EmptySquare ) return;
6538          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6539            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6540                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6541            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6542            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6543            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6544            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6545          fromY = DROP_RANK;
6546     }
6547
6548     /* [HGM] always test for legality, to get promotion info */
6549     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6550                                          fromY, fromX, toY, toX, promoChar);
6551
6552     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6553
6554     /* [HGM] but possibly ignore an IllegalMove result */
6555     if (appData.testLegality) {
6556         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6557             DisplayMoveError(_("Illegal move"));
6558             return;
6559         }
6560     }
6561
6562     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6563 }
6564
6565 /* Common tail of UserMoveEvent and DropMenuEvent */
6566 int
6567 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6568      ChessMove moveType;
6569      int fromX, fromY, toX, toY;
6570      /*char*/int promoChar;
6571 {
6572     char *bookHit = 0;
6573
6574     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6575         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6576         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6577         if(WhiteOnMove(currentMove)) {
6578             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6579         } else {
6580             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6581         }
6582     }
6583
6584     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6585        move type in caller when we know the move is a legal promotion */
6586     if(moveType == NormalMove && promoChar)
6587         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6588
6589     /* [HGM] <popupFix> The following if has been moved here from
6590        UserMoveEvent(). Because it seemed to belong here (why not allow
6591        piece drops in training games?), and because it can only be
6592        performed after it is known to what we promote. */
6593     if (gameMode == Training) {
6594       /* compare the move played on the board to the next move in the
6595        * game. If they match, display the move and the opponent's response.
6596        * If they don't match, display an error message.
6597        */
6598       int saveAnimate;
6599       Board testBoard;
6600       CopyBoard(testBoard, boards[currentMove]);
6601       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6602
6603       if (CompareBoards(testBoard, boards[currentMove+1])) {
6604         ForwardInner(currentMove+1);
6605
6606         /* Autoplay the opponent's response.
6607          * if appData.animate was TRUE when Training mode was entered,
6608          * the response will be animated.
6609          */
6610         saveAnimate = appData.animate;
6611         appData.animate = animateTraining;
6612         ForwardInner(currentMove+1);
6613         appData.animate = saveAnimate;
6614
6615         /* check for the end of the game */
6616         if (currentMove >= forwardMostMove) {
6617           gameMode = PlayFromGameFile;
6618           ModeHighlight();
6619           SetTrainingModeOff();
6620           DisplayInformation(_("End of game"));
6621         }
6622       } else {
6623         DisplayError(_("Incorrect move"), 0);
6624       }
6625       return 1;
6626     }
6627
6628   /* Ok, now we know that the move is good, so we can kill
6629      the previous line in Analysis Mode */
6630   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6631                                 && currentMove < forwardMostMove) {
6632     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6633     else forwardMostMove = currentMove;
6634   }
6635
6636   /* If we need the chess program but it's dead, restart it */
6637   ResurrectChessProgram();
6638
6639   /* A user move restarts a paused game*/
6640   if (pausing)
6641     PauseEvent();
6642
6643   thinkOutput[0] = NULLCHAR;
6644
6645   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6646
6647   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6648     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6649     return 1;
6650   }
6651
6652   if (gameMode == BeginningOfGame) {
6653     if (appData.noChessProgram) {
6654       gameMode = EditGame;
6655       SetGameInfo();
6656     } else {
6657       char buf[MSG_SIZ];
6658       gameMode = MachinePlaysBlack;
6659       StartClocks();
6660       SetGameInfo();
6661       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6662       DisplayTitle(buf);
6663       if (first.sendName) {
6664         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6665         SendToProgram(buf, &first);
6666       }
6667       StartClocks();
6668     }
6669     ModeHighlight();
6670   }
6671
6672   /* Relay move to ICS or chess engine */
6673   if (appData.icsActive) {
6674     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6675         gameMode == IcsExamining) {
6676       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6677         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6678         SendToICS("draw ");
6679         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6680       }
6681       // also send plain move, in case ICS does not understand atomic claims
6682       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6683       ics_user_moved = 1;
6684     }
6685   } else {
6686     if (first.sendTime && (gameMode == BeginningOfGame ||
6687                            gameMode == MachinePlaysWhite ||
6688                            gameMode == MachinePlaysBlack)) {
6689       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6690     }
6691     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6692          // [HGM] book: if program might be playing, let it use book
6693         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6694         first.maybeThinking = TRUE;
6695     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6696         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6697         SendBoard(&first, currentMove+1);
6698     } else SendMoveToProgram(forwardMostMove-1, &first);
6699     if (currentMove == cmailOldMove + 1) {
6700       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6701     }
6702   }
6703
6704   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6705
6706   switch (gameMode) {
6707   case EditGame:
6708     if(appData.testLegality)
6709     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6710     case MT_NONE:
6711     case MT_CHECK:
6712       break;
6713     case MT_CHECKMATE:
6714     case MT_STAINMATE:
6715       if (WhiteOnMove(currentMove)) {
6716         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6717       } else {
6718         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6719       }
6720       break;
6721     case MT_STALEMATE:
6722       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6723       break;
6724     }
6725     break;
6726
6727   case MachinePlaysBlack:
6728   case MachinePlaysWhite:
6729     /* disable certain menu options while machine is thinking */
6730     SetMachineThinkingEnables();
6731     break;
6732
6733   default:
6734     break;
6735   }
6736
6737   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6738   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6739
6740   if(bookHit) { // [HGM] book: simulate book reply
6741         static char bookMove[MSG_SIZ]; // a bit generous?
6742
6743         programStats.nodes = programStats.depth = programStats.time =
6744         programStats.score = programStats.got_only_move = 0;
6745         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6746
6747         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6748         strcat(bookMove, bookHit);
6749         HandleMachineMove(bookMove, &first);
6750   }
6751   return 1;
6752 }
6753
6754 void
6755 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6756      Board board;
6757      int flags;
6758      ChessMove kind;
6759      int rf, ff, rt, ft;
6760      VOIDSTAR closure;
6761 {
6762     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6763     Markers *m = (Markers *) closure;
6764     if(rf == fromY && ff == fromX)
6765         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6766                          || kind == WhiteCapturesEnPassant
6767                          || kind == BlackCapturesEnPassant);
6768     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6769 }
6770
6771 void
6772 MarkTargetSquares(int clear)
6773 {
6774   int x, y;
6775   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6776      !appData.testLegality || gameMode == EditPosition) return;
6777   if(clear) {
6778     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6779   } else {
6780     int capt = 0;
6781     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6782     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6783       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6784       if(capt)
6785       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6786     }
6787   }
6788   DrawPosition(TRUE, NULL);
6789 }
6790
6791 int
6792 Explode(Board board, int fromX, int fromY, int toX, int toY)
6793 {
6794     if(gameInfo.variant == VariantAtomic &&
6795        (board[toY][toX] != EmptySquare ||                     // capture?
6796         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6797                          board[fromY][fromX] == BlackPawn   )
6798       )) {
6799         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6800         return TRUE;
6801     }
6802     return FALSE;
6803 }
6804
6805 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6806
6807 int CanPromote(ChessSquare piece, int y)
6808 {
6809         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6810         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6811         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6812            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6813            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6814                                                   gameInfo.variant == VariantMakruk) return FALSE;
6815         return (piece == BlackPawn && y == 1 ||
6816                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6817                 piece == BlackLance && y == 1 ||
6818                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6819 }
6820
6821 void LeftClick(ClickType clickType, int xPix, int yPix)
6822 {
6823     int x, y;
6824     Boolean saveAnimate;
6825     static int second = 0, promotionChoice = 0, clearFlag = 0;
6826     char promoChoice = NULLCHAR;
6827     ChessSquare piece;
6828
6829     if(appData.seekGraph && appData.icsActive && loggedOn &&
6830         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6831         SeekGraphClick(clickType, xPix, yPix, 0);
6832         return;
6833     }
6834
6835     if (clickType == Press) ErrorPopDown();
6836
6837     x = EventToSquare(xPix, BOARD_WIDTH);
6838     y = EventToSquare(yPix, BOARD_HEIGHT);
6839     if (!flipView && y >= 0) {
6840         y = BOARD_HEIGHT - 1 - y;
6841     }
6842     if (flipView && x >= 0) {
6843         x = BOARD_WIDTH - 1 - x;
6844     }
6845
6846     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6847         defaultPromoChoice = promoSweep;
6848         promoSweep = EmptySquare;   // terminate sweep
6849         promoDefaultAltered = TRUE;
6850         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6851     }
6852
6853     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6854         if(clickType == Release) return; // ignore upclick of click-click destination
6855         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6856         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6857         if(gameInfo.holdingsWidth &&
6858                 (WhiteOnMove(currentMove)
6859                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6860                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6861             // click in right holdings, for determining promotion piece
6862             ChessSquare p = boards[currentMove][y][x];
6863             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6864             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6865             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6866                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6867                 fromX = fromY = -1;
6868                 return;
6869             }
6870         }
6871         DrawPosition(FALSE, boards[currentMove]);
6872         return;
6873     }
6874
6875     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6876     if(clickType == Press
6877             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6878               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6879               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6880         return;
6881
6882     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6883         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6884
6885     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6886         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6887                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6888         defaultPromoChoice = DefaultPromoChoice(side);
6889     }
6890
6891     autoQueen = appData.alwaysPromoteToQueen;
6892
6893     if (fromX == -1) {
6894       int originalY = y;
6895       gatingPiece = EmptySquare;
6896       if (clickType != Press) {
6897         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6898             DragPieceEnd(xPix, yPix); dragging = 0;
6899             DrawPosition(FALSE, NULL);
6900         }
6901         return;
6902       }
6903       fromX = x; fromY = y; toX = toY = -1;
6904       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6905          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6906          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6907             /* First square */
6908             if (OKToStartUserMove(fromX, fromY)) {
6909                 second = 0;
6910                 MarkTargetSquares(0);
6911                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6912                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6913                     promoSweep = defaultPromoChoice;
6914                     selectFlag = 0; lastX = xPix; lastY = yPix;
6915                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6916                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6917                 }
6918                 if (appData.highlightDragging) {
6919                     SetHighlights(fromX, fromY, -1, -1);
6920                 }
6921             } else fromX = fromY = -1;
6922             return;
6923         }
6924     }
6925
6926     /* fromX != -1 */
6927     if (clickType == Press && gameMode != EditPosition) {
6928         ChessSquare fromP;
6929         ChessSquare toP;
6930         int frc;
6931
6932         // ignore off-board to clicks
6933         if(y < 0 || x < 0) return;
6934
6935         /* Check if clicking again on the same color piece */
6936         fromP = boards[currentMove][fromY][fromX];
6937         toP = boards[currentMove][y][x];
6938         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6939         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6940              WhitePawn <= toP && toP <= WhiteKing &&
6941              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6942              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6943             (BlackPawn <= fromP && fromP <= BlackKing &&
6944              BlackPawn <= toP && toP <= BlackKing &&
6945              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6946              !(fromP == BlackKing && toP == BlackRook && frc))) {
6947             /* Clicked again on same color piece -- changed his mind */
6948             second = (x == fromX && y == fromY);
6949             promoDefaultAltered = FALSE;
6950             MarkTargetSquares(1);
6951            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6952             if (appData.highlightDragging) {
6953                 SetHighlights(x, y, -1, -1);
6954             } else {
6955                 ClearHighlights();
6956             }
6957             if (OKToStartUserMove(x, y)) {
6958                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6959                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6960                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6961                  gatingPiece = boards[currentMove][fromY][fromX];
6962                 else gatingPiece = EmptySquare;
6963                 fromX = x;
6964                 fromY = y; dragging = 1;
6965                 MarkTargetSquares(0);
6966                 DragPieceBegin(xPix, yPix, FALSE);
6967                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6968                     promoSweep = defaultPromoChoice;
6969                     selectFlag = 0; lastX = xPix; lastY = yPix;
6970                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6971                 }
6972             }
6973            }
6974            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6975            second = FALSE; 
6976         }
6977         // ignore clicks on holdings
6978         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6979     }
6980
6981     if (clickType == Release && x == fromX && y == fromY) {
6982         DragPieceEnd(xPix, yPix); dragging = 0;
6983         if(clearFlag) {
6984             // a deferred attempt to click-click move an empty square on top of a piece
6985             boards[currentMove][y][x] = EmptySquare;
6986             ClearHighlights();
6987             DrawPosition(FALSE, boards[currentMove]);
6988             fromX = fromY = -1; clearFlag = 0;
6989             return;
6990         }
6991         if (appData.animateDragging) {
6992             /* Undo animation damage if any */
6993             DrawPosition(FALSE, NULL);
6994         }
6995         if (second) {
6996             /* Second up/down in same square; just abort move */
6997             second = 0;
6998             fromX = fromY = -1;
6999             gatingPiece = EmptySquare;
7000             ClearHighlights();
7001             gotPremove = 0;
7002             ClearPremoveHighlights();
7003         } else {
7004             /* First upclick in same square; start click-click mode */
7005             SetHighlights(x, y, -1, -1);
7006         }
7007         return;
7008     }
7009
7010     clearFlag = 0;
7011
7012     /* we now have a different from- and (possibly off-board) to-square */
7013     /* Completed move */
7014     toX = x;
7015     toY = y;
7016     saveAnimate = appData.animate;
7017     MarkTargetSquares(1);
7018     if (clickType == Press) {
7019         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7020             // must be Edit Position mode with empty-square selected
7021             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7022             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7023             return;
7024         }
7025         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7026             ChessSquare piece = boards[currentMove][fromY][fromX];
7027             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7028             promoSweep = defaultPromoChoice;
7029             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7030             selectFlag = 0; lastX = xPix; lastY = yPix;
7031             Sweep(0); // Pawn that is going to promote: preview promotion piece
7032             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7033             DrawPosition(FALSE, boards[currentMove]);
7034             return;
7035         }
7036         /* Finish clickclick move */
7037         if (appData.animate || appData.highlightLastMove) {
7038             SetHighlights(fromX, fromY, toX, toY);
7039         } else {
7040             ClearHighlights();
7041         }
7042     } else {
7043         /* Finish drag move */
7044         if (appData.highlightLastMove) {
7045             SetHighlights(fromX, fromY, toX, toY);
7046         } else {
7047             ClearHighlights();
7048         }
7049         DragPieceEnd(xPix, yPix); dragging = 0;
7050         /* Don't animate move and drag both */
7051         appData.animate = FALSE;
7052     }
7053
7054     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7055     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7056         ChessSquare piece = boards[currentMove][fromY][fromX];
7057         if(gameMode == EditPosition && piece != EmptySquare &&
7058            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7059             int n;
7060
7061             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7062                 n = PieceToNumber(piece - (int)BlackPawn);
7063                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7064                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7065                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7066             } else
7067             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7068                 n = PieceToNumber(piece);
7069                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7070                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7071                 boards[currentMove][n][BOARD_WIDTH-2]++;
7072             }
7073             boards[currentMove][fromY][fromX] = EmptySquare;
7074         }
7075         ClearHighlights();
7076         fromX = fromY = -1;
7077         DrawPosition(TRUE, boards[currentMove]);
7078         return;
7079     }
7080
7081     // off-board moves should not be highlighted
7082     if(x < 0 || y < 0) ClearHighlights();
7083
7084     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7085
7086     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7087         SetHighlights(fromX, fromY, toX, toY);
7088         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7089             // [HGM] super: promotion to captured piece selected from holdings
7090             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7091             promotionChoice = TRUE;
7092             // kludge follows to temporarily execute move on display, without promoting yet
7093             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7094             boards[currentMove][toY][toX] = p;
7095             DrawPosition(FALSE, boards[currentMove]);
7096             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7097             boards[currentMove][toY][toX] = q;
7098             DisplayMessage("Click in holdings to choose piece", "");
7099             return;
7100         }
7101         PromotionPopUp();
7102     } else {
7103         int oldMove = currentMove;
7104         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7105         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7106         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7107         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7108            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7109             DrawPosition(TRUE, boards[currentMove]);
7110         fromX = fromY = -1;
7111     }
7112     appData.animate = saveAnimate;
7113     if (appData.animate || appData.animateDragging) {
7114         /* Undo animation damage if needed */
7115         DrawPosition(FALSE, NULL);
7116     }
7117 }
7118
7119 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7120 {   // front-end-free part taken out of PieceMenuPopup
7121     int whichMenu; int xSqr, ySqr;
7122
7123     if(seekGraphUp) { // [HGM] seekgraph
7124         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7125         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7126         return -2;
7127     }
7128
7129     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7130          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7131         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7132         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7133         if(action == Press)   {
7134             originalFlip = flipView;
7135             flipView = !flipView; // temporarily flip board to see game from partners perspective
7136             DrawPosition(TRUE, partnerBoard);
7137             DisplayMessage(partnerStatus, "");
7138             partnerUp = TRUE;
7139         } else if(action == Release) {
7140             flipView = originalFlip;
7141             DrawPosition(TRUE, boards[currentMove]);
7142             partnerUp = FALSE;
7143         }
7144         return -2;
7145     }
7146
7147     xSqr = EventToSquare(x, BOARD_WIDTH);
7148     ySqr = EventToSquare(y, BOARD_HEIGHT);
7149     if (action == Release) {
7150         if(pieceSweep != EmptySquare) {
7151             EditPositionMenuEvent(pieceSweep, toX, toY);
7152             pieceSweep = EmptySquare;
7153         } else UnLoadPV(); // [HGM] pv
7154     }
7155     if (action != Press) return -2; // return code to be ignored
7156     switch (gameMode) {
7157       case IcsExamining:
7158         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7159       case EditPosition:
7160         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7161         if (xSqr < 0 || ySqr < 0) return -1;
7162         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7163         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7164         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7165         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7166         NextPiece(0);
7167         return 2; // grab
7168       case IcsObserving:
7169         if(!appData.icsEngineAnalyze) return -1;
7170       case IcsPlayingWhite:
7171       case IcsPlayingBlack:
7172         if(!appData.zippyPlay) goto noZip;
7173       case AnalyzeMode:
7174       case AnalyzeFile:
7175       case MachinePlaysWhite:
7176       case MachinePlaysBlack:
7177       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7178         if (!appData.dropMenu) {
7179           LoadPV(x, y);
7180           return 2; // flag front-end to grab mouse events
7181         }
7182         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7183            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7184       case EditGame:
7185       noZip:
7186         if (xSqr < 0 || ySqr < 0) return -1;
7187         if (!appData.dropMenu || appData.testLegality &&
7188             gameInfo.variant != VariantBughouse &&
7189             gameInfo.variant != VariantCrazyhouse) return -1;
7190         whichMenu = 1; // drop menu
7191         break;
7192       default:
7193         return -1;
7194     }
7195
7196     if (((*fromX = xSqr) < 0) ||
7197         ((*fromY = ySqr) < 0)) {
7198         *fromX = *fromY = -1;
7199         return -1;
7200     }
7201     if (flipView)
7202       *fromX = BOARD_WIDTH - 1 - *fromX;
7203     else
7204       *fromY = BOARD_HEIGHT - 1 - *fromY;
7205
7206     return whichMenu;
7207 }
7208
7209 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7210 {
7211 //    char * hint = lastHint;
7212     FrontEndProgramStats stats;
7213
7214     stats.which = cps == &first ? 0 : 1;
7215     stats.depth = cpstats->depth;
7216     stats.nodes = cpstats->nodes;
7217     stats.score = cpstats->score;
7218     stats.time = cpstats->time;
7219     stats.pv = cpstats->movelist;
7220     stats.hint = lastHint;
7221     stats.an_move_index = 0;
7222     stats.an_move_count = 0;
7223
7224     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7225         stats.hint = cpstats->move_name;
7226         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7227         stats.an_move_count = cpstats->nr_moves;
7228     }
7229
7230     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
7231
7232     SetProgramStats( &stats );
7233 }
7234
7235 void
7236 ClearEngineOutputPane(int which)
7237 {
7238     static FrontEndProgramStats dummyStats;
7239     dummyStats.which = which;
7240     dummyStats.pv = "#";
7241     SetProgramStats( &dummyStats );
7242 }
7243
7244 #define MAXPLAYERS 500
7245
7246 char *
7247 TourneyStandings(int display)
7248 {
7249     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7250     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7251     char result, *p, *names[MAXPLAYERS];
7252
7253     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7254         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7255     names[0] = p = strdup(appData.participants);
7256     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7257
7258     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7259
7260     while(result = appData.results[nr]) {
7261         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7262         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7263         wScore = bScore = 0;
7264         switch(result) {
7265           case '+': wScore = 2; break;
7266           case '-': bScore = 2; break;
7267           case '=': wScore = bScore = 1; break;
7268           case ' ':
7269           case '*': return strdup("busy"); // tourney not finished
7270         }
7271         score[w] += wScore;
7272         score[b] += bScore;
7273         games[w]++;
7274         games[b]++;
7275         nr++;
7276     }
7277     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7278     for(w=0; w<nPlayers; w++) {
7279         bScore = -1;
7280         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7281         ranking[w] = b; points[w] = bScore; score[b] = -2;
7282     }
7283     p = malloc(nPlayers*34+1);
7284     for(w=0; w<nPlayers && w<display; w++)
7285         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7286     free(names[0]);
7287     return p;
7288 }
7289
7290 void
7291 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7292 {       // count all piece types
7293         int p, f, r;
7294         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7295         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7296         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7297                 p = board[r][f];
7298                 pCnt[p]++;
7299                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7300                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7301                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7302                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7303                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7304                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7305         }
7306 }
7307
7308 int
7309 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7310 {
7311         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7312         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7313
7314         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7315         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7316         if(myPawns == 2 && nMine == 3) // KPP
7317             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7318         if(myPawns == 1 && nMine == 2) // KP
7319             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7320         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7321             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7322         if(myPawns) return FALSE;
7323         if(pCnt[WhiteRook+side])
7324             return pCnt[BlackRook-side] ||
7325                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7326                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7327                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7328         if(pCnt[WhiteCannon+side]) {
7329             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7330             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7331         }
7332         if(pCnt[WhiteKnight+side])
7333             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7334         return FALSE;
7335 }
7336
7337 int
7338 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7339 {
7340         VariantClass v = gameInfo.variant;
7341
7342         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7343         if(v == VariantShatranj) return TRUE; // always winnable through baring
7344         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7345         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7346
7347         if(v == VariantXiangqi) {
7348                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7349
7350                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7351                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7352                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7353                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7354                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7355                 if(stale) // we have at least one last-rank P plus perhaps C
7356                     return majors // KPKX
7357                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7358                 else // KCA*E*
7359                     return pCnt[WhiteFerz+side] // KCAK
7360                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7361                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7362                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7363
7364         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7365                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7366
7367                 if(nMine == 1) return FALSE; // bare King
7368                 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
7369                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7370                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7371                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7372                 if(pCnt[WhiteKnight+side])
7373                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7374                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7375                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7376                 if(nBishops)
7377                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7378                 if(pCnt[WhiteAlfil+side])
7379                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7380                 if(pCnt[WhiteWazir+side])
7381                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7382         }
7383
7384         return TRUE;
7385 }
7386
7387 int
7388 CompareWithRights(Board b1, Board b2)
7389 {
7390     int rights = 0;
7391     if(!CompareBoards(b1, b2)) return FALSE;
7392     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7393     /* compare castling rights */
7394     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7395            rights++; /* King lost rights, while rook still had them */
7396     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7397         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7398            rights++; /* but at least one rook lost them */
7399     }
7400     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7401            rights++;
7402     if( b1[CASTLING][5] != NoRights ) {
7403         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7404            rights++;
7405     }
7406     return rights == 0;
7407 }
7408
7409 int
7410 Adjudicate(ChessProgramState *cps)
7411 {       // [HGM] some adjudications useful with buggy engines
7412         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7413         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7414         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7415         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7416         int k, count = 0; static int bare = 1;
7417         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7418         Boolean canAdjudicate = !appData.icsActive;
7419
7420         // most tests only when we understand the game, i.e. legality-checking on
7421             if( appData.testLegality )
7422             {   /* [HGM] Some more adjudications for obstinate engines */
7423                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7424                 static int moveCount = 6;
7425                 ChessMove result;
7426                 char *reason = NULL;
7427
7428                 /* Count what is on board. */
7429                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7430
7431                 /* Some material-based adjudications that have to be made before stalemate test */
7432                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7433                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7434                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7435                      if(canAdjudicate && appData.checkMates) {
7436                          if(engineOpponent)
7437                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7438                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7439                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7440                          return 1;
7441                      }
7442                 }
7443
7444                 /* Bare King in Shatranj (loses) or Losers (wins) */
7445                 if( nrW == 1 || nrB == 1) {
7446                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7447                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7448                      if(canAdjudicate && appData.checkMates) {
7449                          if(engineOpponent)
7450                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7451                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7452                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7453                          return 1;
7454                      }
7455                   } else
7456                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7457                   {    /* bare King */
7458                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7459                         if(canAdjudicate && appData.checkMates) {
7460                             /* but only adjudicate if adjudication enabled */
7461                             if(engineOpponent)
7462                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7463                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7464                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7465                             return 1;
7466                         }
7467                   }
7468                 } else bare = 1;
7469
7470
7471             // don't wait for engine to announce game end if we can judge ourselves
7472             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7473               case MT_CHECK:
7474                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7475                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7476                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7477                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7478                             checkCnt++;
7479                         if(checkCnt >= 2) {
7480                             reason = "Xboard adjudication: 3rd check";
7481                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7482                             break;
7483                         }
7484                     }
7485                 }
7486               case MT_NONE:
7487               default:
7488                 break;
7489               case MT_STALEMATE:
7490               case MT_STAINMATE:
7491                 reason = "Xboard adjudication: Stalemate";
7492                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7493                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7494                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7495                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7496                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7497                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7498                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7499                                                                         EP_CHECKMATE : EP_WINS);
7500                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7501                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7502                 }
7503                 break;
7504               case MT_CHECKMATE:
7505                 reason = "Xboard adjudication: Checkmate";
7506                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7507                 break;
7508             }
7509
7510                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7511                     case EP_STALEMATE:
7512                         result = GameIsDrawn; break;
7513                     case EP_CHECKMATE:
7514                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7515                     case EP_WINS:
7516                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7517                     default:
7518                         result = EndOfFile;
7519                 }
7520                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7521                     if(engineOpponent)
7522                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7523                     GameEnds( result, reason, GE_XBOARD );
7524                     return 1;
7525                 }
7526
7527                 /* Next absolutely insufficient mating material. */
7528                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7529                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7530                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7531
7532                      /* always flag draws, for judging claims */
7533                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7534
7535                      if(canAdjudicate && appData.materialDraws) {
7536                          /* but only adjudicate them if adjudication enabled */
7537                          if(engineOpponent) {
7538                            SendToProgram("force\n", engineOpponent); // suppress reply
7539                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7540                          }
7541                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7542                          return 1;
7543                      }
7544                 }
7545
7546                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7547                 if(gameInfo.variant == VariantXiangqi ?
7548                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7549                  : nrW + nrB == 4 &&
7550                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7551                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7552                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7553                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7554                    ) ) {
7555                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7556                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7557                           if(engineOpponent) {
7558                             SendToProgram("force\n", engineOpponent); // suppress reply
7559                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7560                           }
7561                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7562                           return 1;
7563                      }
7564                 } else moveCount = 6;
7565             }
7566         if (appData.debugMode) { int i;
7567             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7568                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7569                     appData.drawRepeats);
7570             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7571               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7572
7573         }
7574
7575         // Repetition draws and 50-move rule can be applied independently of legality testing
7576
7577                 /* Check for rep-draws */
7578                 count = 0;
7579                 for(k = forwardMostMove-2;
7580                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7581                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7582                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7583                     k-=2)
7584                 {   int rights=0;
7585                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7586                         /* compare castling rights */
7587                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7588                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7589                                 rights++; /* King lost rights, while rook still had them */
7590                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7591                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7592                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7593                                    rights++; /* but at least one rook lost them */
7594                         }
7595                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7596                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7597                                 rights++;
7598                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7599                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7600                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7601                                    rights++;
7602                         }
7603                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7604                             && appData.drawRepeats > 1) {
7605                              /* adjudicate after user-specified nr of repeats */
7606                              int result = GameIsDrawn;
7607                              char *details = "XBoard adjudication: repetition draw";
7608                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7609                                 // [HGM] xiangqi: check for forbidden perpetuals
7610                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7611                                 for(m=forwardMostMove; m>k; m-=2) {
7612                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7613                                         ourPerpetual = 0; // the current mover did not always check
7614                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7615                                         hisPerpetual = 0; // the opponent did not always check
7616                                 }
7617                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7618                                                                         ourPerpetual, hisPerpetual);
7619                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7620                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7621                                     details = "Xboard adjudication: perpetual checking";
7622                                 } else
7623                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7624                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7625                                 } else
7626                                 // Now check for perpetual chases
7627                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7628                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7629                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7630                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7631                                         static char resdet[MSG_SIZ];
7632                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7633                                         details = resdet;
7634                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7635                                     } else
7636                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7637                                         break; // Abort repetition-checking loop.
7638                                 }
7639                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7640                              }
7641                              if(engineOpponent) {
7642                                SendToProgram("force\n", engineOpponent); // suppress reply
7643                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7644                              }
7645                              GameEnds( result, details, GE_XBOARD );
7646                              return 1;
7647                         }
7648                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7649                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7650                     }
7651                 }
7652
7653                 /* Now we test for 50-move draws. Determine ply count */
7654                 count = forwardMostMove;
7655                 /* look for last irreversble move */
7656                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7657                     count--;
7658                 /* if we hit starting position, add initial plies */
7659                 if( count == backwardMostMove )
7660                     count -= initialRulePlies;
7661                 count = forwardMostMove - count;
7662                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7663                         // adjust reversible move counter for checks in Xiangqi
7664                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7665                         if(i < backwardMostMove) i = backwardMostMove;
7666                         while(i <= forwardMostMove) {
7667                                 lastCheck = inCheck; // check evasion does not count
7668                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7669                                 if(inCheck || lastCheck) count--; // check does not count
7670                                 i++;
7671                         }
7672                 }
7673                 if( count >= 100)
7674                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7675                          /* this is used to judge if draw claims are legal */
7676                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7677                          if(engineOpponent) {
7678                            SendToProgram("force\n", engineOpponent); // suppress reply
7679                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7680                          }
7681                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7682                          return 1;
7683                 }
7684
7685                 /* if draw offer is pending, treat it as a draw claim
7686                  * when draw condition present, to allow engines a way to
7687                  * claim draws before making their move to avoid a race
7688                  * condition occurring after their move
7689                  */
7690                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7691                          char *p = NULL;
7692                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7693                              p = "Draw claim: 50-move rule";
7694                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7695                              p = "Draw claim: 3-fold repetition";
7696                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7697                              p = "Draw claim: insufficient mating material";
7698                          if( p != NULL && canAdjudicate) {
7699                              if(engineOpponent) {
7700                                SendToProgram("force\n", engineOpponent); // suppress reply
7701                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7702                              }
7703                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7704                              return 1;
7705                          }
7706                 }
7707
7708                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7709                     if(engineOpponent) {
7710                       SendToProgram("force\n", engineOpponent); // suppress reply
7711                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7712                     }
7713                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7714                     return 1;
7715                 }
7716         return 0;
7717 }
7718
7719 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7720 {   // [HGM] book: this routine intercepts moves to simulate book replies
7721     char *bookHit = NULL;
7722
7723     //first determine if the incoming move brings opponent into his book
7724     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7725         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7726     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7727     if(bookHit != NULL && !cps->bookSuspend) {
7728         // make sure opponent is not going to reply after receiving move to book position
7729         SendToProgram("force\n", cps);
7730         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7731     }
7732     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7733     // now arrange restart after book miss
7734     if(bookHit) {
7735         // after a book hit we never send 'go', and the code after the call to this routine
7736         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7737         char buf[MSG_SIZ], *move = bookHit;
7738         if(cps->useSAN) {
7739             int fromX, fromY, toX, toY;
7740             char promoChar;
7741             ChessMove moveType;
7742             move = buf + 30;
7743             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7744                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7745                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7746                                     PosFlags(forwardMostMove),
7747                                     fromY, fromX, toY, toX, promoChar, move);
7748             } else {
7749                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7750                 bookHit = NULL;
7751             }
7752         }
7753         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7754         SendToProgram(buf, cps);
7755         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7756     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7757         SendToProgram("go\n", cps);
7758         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7759     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7760         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7761             SendToProgram("go\n", cps);
7762         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7763     }
7764     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7765 }
7766
7767 char *savedMessage;
7768 ChessProgramState *savedState;
7769 void DeferredBookMove(void)
7770 {
7771         if(savedState->lastPing != savedState->lastPong)
7772                     ScheduleDelayedEvent(DeferredBookMove, 10);
7773         else
7774         HandleMachineMove(savedMessage, savedState);
7775 }
7776
7777 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7778
7779 void
7780 HandleMachineMove(message, cps)
7781      char *message;
7782      ChessProgramState *cps;
7783 {
7784     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7785     char realname[MSG_SIZ];
7786     int fromX, fromY, toX, toY;
7787     ChessMove moveType;
7788     char promoChar;
7789     char *p, *pv=buf1;
7790     int machineWhite;
7791     char *bookHit;
7792
7793     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7794         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7795         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7796             DisplayError(_("Invalid pairing from pairing engine"), 0);
7797             return;
7798         }
7799         pairingReceived = 1;
7800         NextMatchGame();
7801         return; // Skim the pairing messages here.
7802     }
7803
7804     cps->userError = 0;
7805
7806 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7807     /*
7808      * Kludge to ignore BEL characters
7809      */
7810     while (*message == '\007') message++;
7811
7812     /*
7813      * [HGM] engine debug message: ignore lines starting with '#' character
7814      */
7815     if(cps->debug && *message == '#') return;
7816
7817     /*
7818      * Look for book output
7819      */
7820     if (cps == &first && bookRequested) {
7821         if (message[0] == '\t' || message[0] == ' ') {
7822             /* Part of the book output is here; append it */
7823             strcat(bookOutput, message);
7824             strcat(bookOutput, "  \n");
7825             return;
7826         } else if (bookOutput[0] != NULLCHAR) {
7827             /* All of book output has arrived; display it */
7828             char *p = bookOutput;
7829             while (*p != NULLCHAR) {
7830                 if (*p == '\t') *p = ' ';
7831                 p++;
7832             }
7833             DisplayInformation(bookOutput);
7834             bookRequested = FALSE;
7835             /* Fall through to parse the current output */
7836         }
7837     }
7838
7839     /*
7840      * Look for machine move.
7841      */
7842     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7843         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7844     {
7845         /* This method is only useful on engines that support ping */
7846         if (cps->lastPing != cps->lastPong) {
7847           if (gameMode == BeginningOfGame) {
7848             /* Extra move from before last new; ignore */
7849             if (appData.debugMode) {
7850                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7851             }
7852           } else {
7853             if (appData.debugMode) {
7854                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7855                         cps->which, gameMode);
7856             }
7857
7858             SendToProgram("undo\n", cps);
7859           }
7860           return;
7861         }
7862
7863         switch (gameMode) {
7864           case BeginningOfGame:
7865             /* Extra move from before last reset; ignore */
7866             if (appData.debugMode) {
7867                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7868             }
7869             return;
7870
7871           case EndOfGame:
7872           case IcsIdle:
7873           default:
7874             /* Extra move after we tried to stop.  The mode test is
7875                not a reliable way of detecting this problem, but it's
7876                the best we can do on engines that don't support ping.
7877             */
7878             if (appData.debugMode) {
7879                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7880                         cps->which, gameMode);
7881             }
7882             SendToProgram("undo\n", cps);
7883             return;
7884
7885           case MachinePlaysWhite:
7886           case IcsPlayingWhite:
7887             machineWhite = TRUE;
7888             break;
7889
7890           case MachinePlaysBlack:
7891           case IcsPlayingBlack:
7892             machineWhite = FALSE;
7893             break;
7894
7895           case TwoMachinesPlay:
7896             machineWhite = (cps->twoMachinesColor[0] == 'w');
7897             break;
7898         }
7899         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7900             if (appData.debugMode) {
7901                 fprintf(debugFP,
7902                         "Ignoring move out of turn by %s, gameMode %d"
7903                         ", forwardMost %d\n",
7904                         cps->which, gameMode, forwardMostMove);
7905             }
7906             return;
7907         }
7908
7909     if (appData.debugMode) { int f = forwardMostMove;
7910         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7911                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7912                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7913     }
7914         if(cps->alphaRank) AlphaRank(machineMove, 4);
7915         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7916                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7917             /* Machine move could not be parsed; ignore it. */
7918           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7919                     machineMove, _(cps->which));
7920             DisplayError(buf1, 0);
7921             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7922                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7923             if (gameMode == TwoMachinesPlay) {
7924               GameEnds(machineWhite ? BlackWins : WhiteWins,
7925                        buf1, GE_XBOARD);
7926             }
7927             return;
7928         }
7929
7930         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7931         /* So we have to redo legality test with true e.p. status here,  */
7932         /* to make sure an illegal e.p. capture does not slip through,   */
7933         /* to cause a forfeit on a justified illegal-move complaint      */
7934         /* of the opponent.                                              */
7935         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7936            ChessMove moveType;
7937            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7938                              fromY, fromX, toY, toX, promoChar);
7939             if (appData.debugMode) {
7940                 int i;
7941                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7942                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7943                 fprintf(debugFP, "castling rights\n");
7944             }
7945             if(moveType == IllegalMove) {
7946               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7947                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7948                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7949                            buf1, GE_XBOARD);
7950                 return;
7951            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7952            /* [HGM] Kludge to handle engines that send FRC-style castling
7953               when they shouldn't (like TSCP-Gothic) */
7954            switch(moveType) {
7955              case WhiteASideCastleFR:
7956              case BlackASideCastleFR:
7957                toX+=2;
7958                currentMoveString[2]++;
7959                break;
7960              case WhiteHSideCastleFR:
7961              case BlackHSideCastleFR:
7962                toX--;
7963                currentMoveString[2]--;
7964                break;
7965              default: ; // nothing to do, but suppresses warning of pedantic compilers
7966            }
7967         }
7968         hintRequested = FALSE;
7969         lastHint[0] = NULLCHAR;
7970         bookRequested = FALSE;
7971         /* Program may be pondering now */
7972         cps->maybeThinking = TRUE;
7973         if (cps->sendTime == 2) cps->sendTime = 1;
7974         if (cps->offeredDraw) cps->offeredDraw--;
7975
7976         /* [AS] Save move info*/
7977         pvInfoList[ forwardMostMove ].score = programStats.score;
7978         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7979         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7980
7981         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7982
7983         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7984         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7985             int count = 0;
7986
7987             while( count < adjudicateLossPlies ) {
7988                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7989
7990                 if( count & 1 ) {
7991                     score = -score; /* Flip score for winning side */
7992                 }
7993
7994                 if( score > adjudicateLossThreshold ) {
7995                     break;
7996                 }
7997
7998                 count++;
7999             }
8000
8001             if( count >= adjudicateLossPlies ) {
8002                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8003
8004                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8005                     "Xboard adjudication",
8006                     GE_XBOARD );
8007
8008                 return;
8009             }
8010         }
8011
8012         if(Adjudicate(cps)) {
8013             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8014             return; // [HGM] adjudicate: for all automatic game ends
8015         }
8016
8017 #if ZIPPY
8018         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8019             first.initDone) {
8020           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8021                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8022                 SendToICS("draw ");
8023                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8024           }
8025           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8026           ics_user_moved = 1;
8027           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8028                 char buf[3*MSG_SIZ];
8029
8030                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8031                         programStats.score / 100.,
8032                         programStats.depth,
8033                         programStats.time / 100.,
8034                         (unsigned int)programStats.nodes,
8035                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8036                         programStats.movelist);
8037                 SendToICS(buf);
8038 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8039           }
8040         }
8041 #endif
8042
8043         /* [AS] Clear stats for next move */
8044         ClearProgramStats();
8045         thinkOutput[0] = NULLCHAR;
8046         hiddenThinkOutputState = 0;
8047
8048         bookHit = NULL;
8049         if (gameMode == TwoMachinesPlay) {
8050             /* [HGM] relaying draw offers moved to after reception of move */
8051             /* and interpreting offer as claim if it brings draw condition */
8052             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8053                 SendToProgram("draw\n", cps->other);
8054             }
8055             if (cps->other->sendTime) {
8056                 SendTimeRemaining(cps->other,
8057                                   cps->other->twoMachinesColor[0] == 'w');
8058             }
8059             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8060             if (firstMove && !bookHit) {
8061                 firstMove = FALSE;
8062                 if (cps->other->useColors) {
8063                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8064                 }
8065                 SendToProgram("go\n", cps->other);
8066             }
8067             cps->other->maybeThinking = TRUE;
8068         }
8069
8070         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8071
8072         if (!pausing && appData.ringBellAfterMoves) {
8073             RingBell();
8074         }
8075
8076         /*
8077          * Reenable menu items that were disabled while
8078          * machine was thinking
8079          */
8080         if (gameMode != TwoMachinesPlay)
8081             SetUserThinkingEnables();
8082
8083         // [HGM] book: after book hit opponent has received move and is now in force mode
8084         // force the book reply into it, and then fake that it outputted this move by jumping
8085         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8086         if(bookHit) {
8087                 static char bookMove[MSG_SIZ]; // a bit generous?
8088
8089                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8090                 strcat(bookMove, bookHit);
8091                 message = bookMove;
8092                 cps = cps->other;
8093                 programStats.nodes = programStats.depth = programStats.time =
8094                 programStats.score = programStats.got_only_move = 0;
8095                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8096
8097                 if(cps->lastPing != cps->lastPong) {
8098                     savedMessage = message; // args for deferred call
8099                     savedState = cps;
8100                     ScheduleDelayedEvent(DeferredBookMove, 10);
8101                     return;
8102                 }
8103                 goto FakeBookMove;
8104         }
8105
8106         return;
8107     }
8108
8109     /* Set special modes for chess engines.  Later something general
8110      *  could be added here; for now there is just one kludge feature,
8111      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8112      *  when "xboard" is given as an interactive command.
8113      */
8114     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8115         cps->useSigint = FALSE;
8116         cps->useSigterm = FALSE;
8117     }
8118     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8119       ParseFeatures(message+8, cps);
8120       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8121     }
8122
8123     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8124                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8125       int dummy, s=6; char buf[MSG_SIZ];
8126       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8127       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8128       if(startedFromSetupPosition) return;
8129       ParseFEN(boards[0], &dummy, message+s);
8130       DrawPosition(TRUE, boards[0]);
8131       startedFromSetupPosition = TRUE;
8132       return;
8133     }
8134     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8135      * want this, I was asked to put it in, and obliged.
8136      */
8137     if (!strncmp(message, "setboard ", 9)) {
8138         Board initial_position;
8139
8140         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8141
8142         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8143             DisplayError(_("Bad FEN received from engine"), 0);
8144             return ;
8145         } else {
8146            Reset(TRUE, FALSE);
8147            CopyBoard(boards[0], initial_position);
8148            initialRulePlies = FENrulePlies;
8149            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8150            else gameMode = MachinePlaysBlack;
8151            DrawPosition(FALSE, boards[currentMove]);
8152         }
8153         return;
8154     }
8155
8156     /*
8157      * Look for communication commands
8158      */
8159     if (!strncmp(message, "telluser ", 9)) {
8160         if(message[9] == '\\' && message[10] == '\\')
8161             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8162         PlayTellSound();
8163         DisplayNote(message + 9);
8164         return;
8165     }
8166     if (!strncmp(message, "tellusererror ", 14)) {
8167         cps->userError = 1;
8168         if(message[14] == '\\' && message[15] == '\\')
8169             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8170         PlayTellSound();
8171         DisplayError(message + 14, 0);
8172         return;
8173     }
8174     if (!strncmp(message, "tellopponent ", 13)) {
8175       if (appData.icsActive) {
8176         if (loggedOn) {
8177           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8178           SendToICS(buf1);
8179         }
8180       } else {
8181         DisplayNote(message + 13);
8182       }
8183       return;
8184     }
8185     if (!strncmp(message, "tellothers ", 11)) {
8186       if (appData.icsActive) {
8187         if (loggedOn) {
8188           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8189           SendToICS(buf1);
8190         }
8191       }
8192       return;
8193     }
8194     if (!strncmp(message, "tellall ", 8)) {
8195       if (appData.icsActive) {
8196         if (loggedOn) {
8197           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8198           SendToICS(buf1);
8199         }
8200       } else {
8201         DisplayNote(message + 8);
8202       }
8203       return;
8204     }
8205     if (strncmp(message, "warning", 7) == 0) {
8206         /* Undocumented feature, use tellusererror in new code */
8207         DisplayError(message, 0);
8208         return;
8209     }
8210     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8211         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8212         strcat(realname, " query");
8213         AskQuestion(realname, buf2, buf1, cps->pr);
8214         return;
8215     }
8216     /* Commands from the engine directly to ICS.  We don't allow these to be
8217      *  sent until we are logged on. Crafty kibitzes have been known to
8218      *  interfere with the login process.
8219      */
8220     if (loggedOn) {
8221         if (!strncmp(message, "tellics ", 8)) {
8222             SendToICS(message + 8);
8223             SendToICS("\n");
8224             return;
8225         }
8226         if (!strncmp(message, "tellicsnoalias ", 15)) {
8227             SendToICS(ics_prefix);
8228             SendToICS(message + 15);
8229             SendToICS("\n");
8230             return;
8231         }
8232         /* The following are for backward compatibility only */
8233         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8234             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8235             SendToICS(ics_prefix);
8236             SendToICS(message);
8237             SendToICS("\n");
8238             return;
8239         }
8240     }
8241     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8242         return;
8243     }
8244     /*
8245      * If the move is illegal, cancel it and redraw the board.
8246      * Also deal with other error cases.  Matching is rather loose
8247      * here to accommodate engines written before the spec.
8248      */
8249     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8250         strncmp(message, "Error", 5) == 0) {
8251         if (StrStr(message, "name") ||
8252             StrStr(message, "rating") || StrStr(message, "?") ||
8253             StrStr(message, "result") || StrStr(message, "board") ||
8254             StrStr(message, "bk") || StrStr(message, "computer") ||
8255             StrStr(message, "variant") || StrStr(message, "hint") ||
8256             StrStr(message, "random") || StrStr(message, "depth") ||
8257             StrStr(message, "accepted")) {
8258             return;
8259         }
8260         if (StrStr(message, "protover")) {
8261           /* Program is responding to input, so it's apparently done
8262              initializing, and this error message indicates it is
8263              protocol version 1.  So we don't need to wait any longer
8264              for it to initialize and send feature commands. */
8265           FeatureDone(cps, 1);
8266           cps->protocolVersion = 1;
8267           return;
8268         }
8269         cps->maybeThinking = FALSE;
8270
8271         if (StrStr(message, "draw")) {
8272             /* Program doesn't have "draw" command */
8273             cps->sendDrawOffers = 0;
8274             return;
8275         }
8276         if (cps->sendTime != 1 &&
8277             (StrStr(message, "time") || StrStr(message, "otim"))) {
8278           /* Program apparently doesn't have "time" or "otim" command */
8279           cps->sendTime = 0;
8280           return;
8281         }
8282         if (StrStr(message, "analyze")) {
8283             cps->analysisSupport = FALSE;
8284             cps->analyzing = FALSE;
8285 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8286             EditGameEvent(); // [HGM] try to preserve loaded game
8287             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8288             DisplayError(buf2, 0);
8289             return;
8290         }
8291         if (StrStr(message, "(no matching move)st")) {
8292           /* Special kludge for GNU Chess 4 only */
8293           cps->stKludge = TRUE;
8294           SendTimeControl(cps, movesPerSession, timeControl,
8295                           timeIncrement, appData.searchDepth,
8296                           searchTime);
8297           return;
8298         }
8299         if (StrStr(message, "(no matching move)sd")) {
8300           /* Special kludge for GNU Chess 4 only */
8301           cps->sdKludge = TRUE;
8302           SendTimeControl(cps, movesPerSession, timeControl,
8303                           timeIncrement, appData.searchDepth,
8304                           searchTime);
8305           return;
8306         }
8307         if (!StrStr(message, "llegal")) {
8308             return;
8309         }
8310         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8311             gameMode == IcsIdle) return;
8312         if (forwardMostMove <= backwardMostMove) return;
8313         if (pausing) PauseEvent();
8314       if(appData.forceIllegal) {
8315             // [HGM] illegal: machine refused move; force position after move into it
8316           SendToProgram("force\n", cps);
8317           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8318                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8319                 // when black is to move, while there might be nothing on a2 or black
8320                 // might already have the move. So send the board as if white has the move.
8321                 // But first we must change the stm of the engine, as it refused the last move
8322                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8323                 if(WhiteOnMove(forwardMostMove)) {
8324                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8325                     SendBoard(cps, forwardMostMove); // kludgeless board
8326                 } else {
8327                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8328                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8329                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8330                 }
8331           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8332             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8333                  gameMode == TwoMachinesPlay)
8334               SendToProgram("go\n", cps);
8335             return;
8336       } else
8337         if (gameMode == PlayFromGameFile) {
8338             /* Stop reading this game file */
8339             gameMode = EditGame;
8340             ModeHighlight();
8341         }
8342         /* [HGM] illegal-move claim should forfeit game when Xboard */
8343         /* only passes fully legal moves                            */
8344         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8345             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8346                                 "False illegal-move claim", GE_XBOARD );
8347             return; // do not take back move we tested as valid
8348         }
8349         currentMove = forwardMostMove-1;
8350         DisplayMove(currentMove-1); /* before DisplayMoveError */
8351         SwitchClocks(forwardMostMove-1); // [HGM] race
8352         DisplayBothClocks();
8353         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8354                 parseList[currentMove], _(cps->which));
8355         DisplayMoveError(buf1);
8356         DrawPosition(FALSE, boards[currentMove]);
8357         return;
8358     }
8359     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8360         /* Program has a broken "time" command that
8361            outputs a string not ending in newline.
8362            Don't use it. */
8363         cps->sendTime = 0;
8364     }
8365
8366     /*
8367      * If chess program startup fails, exit with an error message.
8368      * Attempts to recover here are futile.
8369      */
8370     if ((StrStr(message, "unknown host") != NULL)
8371         || (StrStr(message, "No remote directory") != NULL)
8372         || (StrStr(message, "not found") != NULL)
8373         || (StrStr(message, "No such file") != NULL)
8374         || (StrStr(message, "can't alloc") != NULL)
8375         || (StrStr(message, "Permission denied") != NULL)) {
8376
8377         cps->maybeThinking = FALSE;
8378         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8379                 _(cps->which), cps->program, cps->host, message);
8380         RemoveInputSource(cps->isr);
8381         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8382             if(cps == &first) appData.noChessProgram = TRUE;
8383             DisplayError(buf1, 0);
8384         }
8385         return;
8386     }
8387
8388     /*
8389      * Look for hint output
8390      */
8391     if (sscanf(message, "Hint: %s", buf1) == 1) {
8392         if (cps == &first && hintRequested) {
8393             hintRequested = FALSE;
8394             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8395                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8396                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8397                                     PosFlags(forwardMostMove),
8398                                     fromY, fromX, toY, toX, promoChar, buf1);
8399                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8400                 DisplayInformation(buf2);
8401             } else {
8402                 /* Hint move could not be parsed!? */
8403               snprintf(buf2, sizeof(buf2),
8404                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8405                         buf1, _(cps->which));
8406                 DisplayError(buf2, 0);
8407             }
8408         } else {
8409           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8410         }
8411         return;
8412     }
8413
8414     /*
8415      * Ignore other messages if game is not in progress
8416      */
8417     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8418         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8419
8420     /*
8421      * look for win, lose, draw, or draw offer
8422      */
8423     if (strncmp(message, "1-0", 3) == 0) {
8424         char *p, *q, *r = "";
8425         p = strchr(message, '{');
8426         if (p) {
8427             q = strchr(p, '}');
8428             if (q) {
8429                 *q = NULLCHAR;
8430                 r = p + 1;
8431             }
8432         }
8433         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8434         return;
8435     } else if (strncmp(message, "0-1", 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         /* Kludge for Arasan 4.1 bug */
8446         if (strcmp(r, "Black resigns") == 0) {
8447             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8448             return;
8449         }
8450         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8451         return;
8452     } else if (strncmp(message, "1/2", 3) == 0) {
8453         char *p, *q, *r = "";
8454         p = strchr(message, '{');
8455         if (p) {
8456             q = strchr(p, '}');
8457             if (q) {
8458                 *q = NULLCHAR;
8459                 r = p + 1;
8460             }
8461         }
8462
8463         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8464         return;
8465
8466     } else if (strncmp(message, "White resign", 12) == 0) {
8467         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8468         return;
8469     } else if (strncmp(message, "Black resign", 12) == 0) {
8470         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8471         return;
8472     } else if (strncmp(message, "White matches", 13) == 0 ||
8473                strncmp(message, "Black matches", 13) == 0   ) {
8474         /* [HGM] ignore GNUShogi noises */
8475         return;
8476     } else if (strncmp(message, "White", 5) == 0 &&
8477                message[5] != '(' &&
8478                StrStr(message, "Black") == NULL) {
8479         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8480         return;
8481     } else if (strncmp(message, "Black", 5) == 0 &&
8482                message[5] != '(') {
8483         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8484         return;
8485     } else if (strcmp(message, "resign") == 0 ||
8486                strcmp(message, "computer resigns") == 0) {
8487         switch (gameMode) {
8488           case MachinePlaysBlack:
8489           case IcsPlayingBlack:
8490             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8491             break;
8492           case MachinePlaysWhite:
8493           case IcsPlayingWhite:
8494             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8495             break;
8496           case TwoMachinesPlay:
8497             if (cps->twoMachinesColor[0] == 'w')
8498               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8499             else
8500               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8501             break;
8502           default:
8503             /* can't happen */
8504             break;
8505         }
8506         return;
8507     } else if (strncmp(message, "opponent mates", 14) == 0) {
8508         switch (gameMode) {
8509           case MachinePlaysBlack:
8510           case IcsPlayingBlack:
8511             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8512             break;
8513           case MachinePlaysWhite:
8514           case IcsPlayingWhite:
8515             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8516             break;
8517           case TwoMachinesPlay:
8518             if (cps->twoMachinesColor[0] == 'w')
8519               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8520             else
8521               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8522             break;
8523           default:
8524             /* can't happen */
8525             break;
8526         }
8527         return;
8528     } else if (strncmp(message, "computer mates", 14) == 0) {
8529         switch (gameMode) {
8530           case MachinePlaysBlack:
8531           case IcsPlayingBlack:
8532             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8533             break;
8534           case MachinePlaysWhite:
8535           case IcsPlayingWhite:
8536             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8537             break;
8538           case TwoMachinesPlay:
8539             if (cps->twoMachinesColor[0] == 'w')
8540               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8541             else
8542               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8543             break;
8544           default:
8545             /* can't happen */
8546             break;
8547         }
8548         return;
8549     } else if (strncmp(message, "checkmate", 9) == 0) {
8550         if (WhiteOnMove(forwardMostMove)) {
8551             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8552         } else {
8553             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8554         }
8555         return;
8556     } else if (strstr(message, "Draw") != NULL ||
8557                strstr(message, "game is a draw") != NULL) {
8558         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8559         return;
8560     } else if (strstr(message, "offer") != NULL &&
8561                strstr(message, "draw") != NULL) {
8562 #if ZIPPY
8563         if (appData.zippyPlay && first.initDone) {
8564             /* Relay offer to ICS */
8565             SendToICS(ics_prefix);
8566             SendToICS("draw\n");
8567         }
8568 #endif
8569         cps->offeredDraw = 2; /* valid until this engine moves twice */
8570         if (gameMode == TwoMachinesPlay) {
8571             if (cps->other->offeredDraw) {
8572                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8573             /* [HGM] in two-machine mode we delay relaying draw offer      */
8574             /* until after we also have move, to see if it is really claim */
8575             }
8576         } else if (gameMode == MachinePlaysWhite ||
8577                    gameMode == MachinePlaysBlack) {
8578           if (userOfferedDraw) {
8579             DisplayInformation(_("Machine accepts your draw offer"));
8580             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8581           } else {
8582             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8583           }
8584         }
8585     }
8586
8587
8588     /*
8589      * Look for thinking output
8590      */
8591     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8592           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8593                                 ) {
8594         int plylev, mvleft, mvtot, curscore, time;
8595         char mvname[MOVE_LEN];
8596         u64 nodes; // [DM]
8597         char plyext;
8598         int ignore = FALSE;
8599         int prefixHint = FALSE;
8600         mvname[0] = NULLCHAR;
8601
8602         switch (gameMode) {
8603           case MachinePlaysBlack:
8604           case IcsPlayingBlack:
8605             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8606             break;
8607           case MachinePlaysWhite:
8608           case IcsPlayingWhite:
8609             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8610             break;
8611           case AnalyzeMode:
8612           case AnalyzeFile:
8613             break;
8614           case IcsObserving: /* [DM] icsEngineAnalyze */
8615             if (!appData.icsEngineAnalyze) ignore = TRUE;
8616             break;
8617           case TwoMachinesPlay:
8618             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8619                 ignore = TRUE;
8620             }
8621             break;
8622           default:
8623             ignore = TRUE;
8624             break;
8625         }
8626
8627         if (!ignore) {
8628             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8629             buf1[0] = NULLCHAR;
8630             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8631                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8632
8633                 if (plyext != ' ' && plyext != '\t') {
8634                     time *= 100;
8635                 }
8636
8637                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8638                 if( cps->scoreIsAbsolute &&
8639                     ( gameMode == MachinePlaysBlack ||
8640                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8641                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8642                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8643                      !WhiteOnMove(currentMove)
8644                     ) )
8645                 {
8646                     curscore = -curscore;
8647                 }
8648
8649                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8650
8651                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8652                         char buf[MSG_SIZ];
8653                         FILE *f;
8654                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8655                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8656                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8657                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8658                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8659                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8660                                 fclose(f);
8661                         } else DisplayError("failed writing PV", 0);
8662                 }
8663
8664                 tempStats.depth = plylev;
8665                 tempStats.nodes = nodes;
8666                 tempStats.time = time;
8667                 tempStats.score = curscore;
8668                 tempStats.got_only_move = 0;
8669
8670                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8671                         int ticklen;
8672
8673                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8674                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8675                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8676                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8677                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8678                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8679                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8680                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8681                 }
8682
8683                 /* Buffer overflow protection */
8684                 if (pv[0] != NULLCHAR) {
8685                     if (strlen(pv) >= sizeof(tempStats.movelist)
8686                         && appData.debugMode) {
8687                         fprintf(debugFP,
8688                                 "PV is too long; using the first %u bytes.\n",
8689                                 (unsigned) sizeof(tempStats.movelist) - 1);
8690                     }
8691
8692                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8693                 } else {
8694                     sprintf(tempStats.movelist, " no PV\n");
8695                 }
8696
8697                 if (tempStats.seen_stat) {
8698                     tempStats.ok_to_send = 1;
8699                 }
8700
8701                 if (strchr(tempStats.movelist, '(') != NULL) {
8702                     tempStats.line_is_book = 1;
8703                     tempStats.nr_moves = 0;
8704                     tempStats.moves_left = 0;
8705                 } else {
8706                     tempStats.line_is_book = 0;
8707                 }
8708
8709                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8710                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8711
8712                 SendProgramStatsToFrontend( cps, &tempStats );
8713
8714                 /*
8715                     [AS] Protect the thinkOutput buffer from overflow... this
8716                     is only useful if buf1 hasn't overflowed first!
8717                 */
8718                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8719                          plylev,
8720                          (gameMode == TwoMachinesPlay ?
8721                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8722                          ((double) curscore) / 100.0,
8723                          prefixHint ? lastHint : "",
8724                          prefixHint ? " " : "" );
8725
8726                 if( buf1[0] != NULLCHAR ) {
8727                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8728
8729                     if( strlen(pv) > max_len ) {
8730                         if( appData.debugMode) {
8731                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8732                         }
8733                         pv[max_len+1] = '\0';
8734                     }
8735
8736                     strcat( thinkOutput, pv);
8737                 }
8738
8739                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8740                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8741                     DisplayMove(currentMove - 1);
8742                 }
8743                 return;
8744
8745             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8746                 /* crafty (9.25+) says "(only move) <move>"
8747                  * if there is only 1 legal move
8748                  */
8749                 sscanf(p, "(only move) %s", buf1);
8750                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8751                 sprintf(programStats.movelist, "%s (only move)", buf1);
8752                 programStats.depth = 1;
8753                 programStats.nr_moves = 1;
8754                 programStats.moves_left = 1;
8755                 programStats.nodes = 1;
8756                 programStats.time = 1;
8757                 programStats.got_only_move = 1;
8758
8759                 /* Not really, but we also use this member to
8760                    mean "line isn't going to change" (Crafty
8761                    isn't searching, so stats won't change) */
8762                 programStats.line_is_book = 1;
8763
8764                 SendProgramStatsToFrontend( cps, &programStats );
8765
8766                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8767                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8768                     DisplayMove(currentMove - 1);
8769                 }
8770                 return;
8771             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8772                               &time, &nodes, &plylev, &mvleft,
8773                               &mvtot, mvname) >= 5) {
8774                 /* The stat01: line is from Crafty (9.29+) in response
8775                    to the "." command */
8776                 programStats.seen_stat = 1;
8777                 cps->maybeThinking = TRUE;
8778
8779                 if (programStats.got_only_move || !appData.periodicUpdates)
8780                   return;
8781
8782                 programStats.depth = plylev;
8783                 programStats.time = time;
8784                 programStats.nodes = nodes;
8785                 programStats.moves_left = mvleft;
8786                 programStats.nr_moves = mvtot;
8787                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8788                 programStats.ok_to_send = 1;
8789                 programStats.movelist[0] = '\0';
8790
8791                 SendProgramStatsToFrontend( cps, &programStats );
8792
8793                 return;
8794
8795             } else if (strncmp(message,"++",2) == 0) {
8796                 /* Crafty 9.29+ outputs this */
8797                 programStats.got_fail = 2;
8798                 return;
8799
8800             } else if (strncmp(message,"--",2) == 0) {
8801                 /* Crafty 9.29+ outputs this */
8802                 programStats.got_fail = 1;
8803                 return;
8804
8805             } else if (thinkOutput[0] != NULLCHAR &&
8806                        strncmp(message, "    ", 4) == 0) {
8807                 unsigned message_len;
8808
8809                 p = message;
8810                 while (*p && *p == ' ') p++;
8811
8812                 message_len = strlen( p );
8813
8814                 /* [AS] Avoid buffer overflow */
8815                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8816                     strcat(thinkOutput, " ");
8817                     strcat(thinkOutput, p);
8818                 }
8819
8820                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8821                     strcat(programStats.movelist, " ");
8822                     strcat(programStats.movelist, p);
8823                 }
8824
8825                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8826                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8827                     DisplayMove(currentMove - 1);
8828                 }
8829                 return;
8830             }
8831         }
8832         else {
8833             buf1[0] = NULLCHAR;
8834
8835             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8836                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8837             {
8838                 ChessProgramStats cpstats;
8839
8840                 if (plyext != ' ' && plyext != '\t') {
8841                     time *= 100;
8842                 }
8843
8844                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8845                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8846                     curscore = -curscore;
8847                 }
8848
8849                 cpstats.depth = plylev;
8850                 cpstats.nodes = nodes;
8851                 cpstats.time = time;
8852                 cpstats.score = curscore;
8853                 cpstats.got_only_move = 0;
8854                 cpstats.movelist[0] = '\0';
8855
8856                 if (buf1[0] != NULLCHAR) {
8857                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8858                 }
8859
8860                 cpstats.ok_to_send = 0;
8861                 cpstats.line_is_book = 0;
8862                 cpstats.nr_moves = 0;
8863                 cpstats.moves_left = 0;
8864
8865                 SendProgramStatsToFrontend( cps, &cpstats );
8866             }
8867         }
8868     }
8869 }
8870
8871
8872 /* Parse a game score from the character string "game", and
8873    record it as the history of the current game.  The game
8874    score is NOT assumed to start from the standard position.
8875    The display is not updated in any way.
8876    */
8877 void
8878 ParseGameHistory(game)
8879      char *game;
8880 {
8881     ChessMove moveType;
8882     int fromX, fromY, toX, toY, boardIndex;
8883     char promoChar;
8884     char *p, *q;
8885     char buf[MSG_SIZ];
8886
8887     if (appData.debugMode)
8888       fprintf(debugFP, "Parsing game history: %s\n", game);
8889
8890     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8891     gameInfo.site = StrSave(appData.icsHost);
8892     gameInfo.date = PGNDate();
8893     gameInfo.round = StrSave("-");
8894
8895     /* Parse out names of players */
8896     while (*game == ' ') game++;
8897     p = buf;
8898     while (*game != ' ') *p++ = *game++;
8899     *p = NULLCHAR;
8900     gameInfo.white = StrSave(buf);
8901     while (*game == ' ') game++;
8902     p = buf;
8903     while (*game != ' ' && *game != '\n') *p++ = *game++;
8904     *p = NULLCHAR;
8905     gameInfo.black = StrSave(buf);
8906
8907     /* Parse moves */
8908     boardIndex = blackPlaysFirst ? 1 : 0;
8909     yynewstr(game);
8910     for (;;) {
8911         yyboardindex = boardIndex;
8912         moveType = (ChessMove) Myylex();
8913         switch (moveType) {
8914           case IllegalMove:             /* maybe suicide chess, etc. */
8915   if (appData.debugMode) {
8916     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8917     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8918     setbuf(debugFP, NULL);
8919   }
8920           case WhitePromotion:
8921           case BlackPromotion:
8922           case WhiteNonPromotion:
8923           case BlackNonPromotion:
8924           case NormalMove:
8925           case WhiteCapturesEnPassant:
8926           case BlackCapturesEnPassant:
8927           case WhiteKingSideCastle:
8928           case WhiteQueenSideCastle:
8929           case BlackKingSideCastle:
8930           case BlackQueenSideCastle:
8931           case WhiteKingSideCastleWild:
8932           case WhiteQueenSideCastleWild:
8933           case BlackKingSideCastleWild:
8934           case BlackQueenSideCastleWild:
8935           /* PUSH Fabien */
8936           case WhiteHSideCastleFR:
8937           case WhiteASideCastleFR:
8938           case BlackHSideCastleFR:
8939           case BlackASideCastleFR:
8940           /* POP Fabien */
8941             fromX = currentMoveString[0] - AAA;
8942             fromY = currentMoveString[1] - ONE;
8943             toX = currentMoveString[2] - AAA;
8944             toY = currentMoveString[3] - ONE;
8945             promoChar = currentMoveString[4];
8946             break;
8947           case WhiteDrop:
8948           case BlackDrop:
8949             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8950             fromX = moveType == WhiteDrop ?
8951               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8952             (int) CharToPiece(ToLower(currentMoveString[0]));
8953             fromY = DROP_RANK;
8954             toX = currentMoveString[2] - AAA;
8955             toY = currentMoveString[3] - ONE;
8956             promoChar = NULLCHAR;
8957             break;
8958           case AmbiguousMove:
8959             /* bug? */
8960             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8961   if (appData.debugMode) {
8962     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8963     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8964     setbuf(debugFP, NULL);
8965   }
8966             DisplayError(buf, 0);
8967             return;
8968           case ImpossibleMove:
8969             /* bug? */
8970             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8971   if (appData.debugMode) {
8972     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8973     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8974     setbuf(debugFP, NULL);
8975   }
8976             DisplayError(buf, 0);
8977             return;
8978           case EndOfFile:
8979             if (boardIndex < backwardMostMove) {
8980                 /* Oops, gap.  How did that happen? */
8981                 DisplayError(_("Gap in move list"), 0);
8982                 return;
8983             }
8984             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8985             if (boardIndex > forwardMostMove) {
8986                 forwardMostMove = boardIndex;
8987             }
8988             return;
8989           case ElapsedTime:
8990             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8991                 strcat(parseList[boardIndex-1], " ");
8992                 strcat(parseList[boardIndex-1], yy_text);
8993             }
8994             continue;
8995           case Comment:
8996           case PGNTag:
8997           case NAG:
8998           default:
8999             /* ignore */
9000             continue;
9001           case WhiteWins:
9002           case BlackWins:
9003           case GameIsDrawn:
9004           case GameUnfinished:
9005             if (gameMode == IcsExamining) {
9006                 if (boardIndex < backwardMostMove) {
9007                     /* Oops, gap.  How did that happen? */
9008                     return;
9009                 }
9010                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9011                 return;
9012             }
9013             gameInfo.result = moveType;
9014             p = strchr(yy_text, '{');
9015             if (p == NULL) p = strchr(yy_text, '(');
9016             if (p == NULL) {
9017                 p = yy_text;
9018                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9019             } else {
9020                 q = strchr(p, *p == '{' ? '}' : ')');
9021                 if (q != NULL) *q = NULLCHAR;
9022                 p++;
9023             }
9024             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9025             gameInfo.resultDetails = StrSave(p);
9026             continue;
9027         }
9028         if (boardIndex >= forwardMostMove &&
9029             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9030             backwardMostMove = blackPlaysFirst ? 1 : 0;
9031             return;
9032         }
9033         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9034                                  fromY, fromX, toY, toX, promoChar,
9035                                  parseList[boardIndex]);
9036         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9037         /* currentMoveString is set as a side-effect of yylex */
9038         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9039         strcat(moveList[boardIndex], "\n");
9040         boardIndex++;
9041         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9042         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9043           case MT_NONE:
9044           case MT_STALEMATE:
9045           default:
9046             break;
9047           case MT_CHECK:
9048             if(gameInfo.variant != VariantShogi)
9049                 strcat(parseList[boardIndex - 1], "+");
9050             break;
9051           case MT_CHECKMATE:
9052           case MT_STAINMATE:
9053             strcat(parseList[boardIndex - 1], "#");
9054             break;
9055         }
9056     }
9057 }
9058
9059
9060 /* Apply a move to the given board  */
9061 void
9062 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9063      int fromX, fromY, toX, toY;
9064      int promoChar;
9065      Board board;
9066 {
9067   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9068   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9069
9070     /* [HGM] compute & store e.p. status and castling rights for new position */
9071     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9072
9073       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9074       oldEP = (signed char)board[EP_STATUS];
9075       board[EP_STATUS] = EP_NONE;
9076
9077   if (fromY == DROP_RANK) {
9078         /* must be first */
9079         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9080             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9081             return;
9082         }
9083         piece = board[toY][toX] = (ChessSquare) fromX;
9084   } else {
9085       int i;
9086
9087       if( board[toY][toX] != EmptySquare )
9088            board[EP_STATUS] = EP_CAPTURE;
9089
9090       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9091            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9092                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9093       } else
9094       if( board[fromY][fromX] == WhitePawn ) {
9095            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9096                board[EP_STATUS] = EP_PAWN_MOVE;
9097            if( toY-fromY==2) {
9098                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9099                         gameInfo.variant != VariantBerolina || toX < fromX)
9100                       board[EP_STATUS] = toX | berolina;
9101                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9102                         gameInfo.variant != VariantBerolina || toX > fromX)
9103                       board[EP_STATUS] = toX;
9104            }
9105       } else
9106       if( board[fromY][fromX] == BlackPawn ) {
9107            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9108                board[EP_STATUS] = EP_PAWN_MOVE;
9109            if( toY-fromY== -2) {
9110                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9111                         gameInfo.variant != VariantBerolina || toX < fromX)
9112                       board[EP_STATUS] = toX | berolina;
9113                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9114                         gameInfo.variant != VariantBerolina || toX > fromX)
9115                       board[EP_STATUS] = toX;
9116            }
9117        }
9118
9119        for(i=0; i<nrCastlingRights; i++) {
9120            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9121               board[CASTLING][i] == toX   && castlingRank[i] == toY
9122              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9123        }
9124
9125      if (fromX == toX && fromY == toY) return;
9126
9127      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9128      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9129      if(gameInfo.variant == VariantKnightmate)
9130          king += (int) WhiteUnicorn - (int) WhiteKing;
9131
9132     /* Code added by Tord: */
9133     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9134     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9135         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9136       board[fromY][fromX] = EmptySquare;
9137       board[toY][toX] = EmptySquare;
9138       if((toX > fromX) != (piece == WhiteRook)) {
9139         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9140       } else {
9141         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9142       }
9143     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9144                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9145       board[fromY][fromX] = EmptySquare;
9146       board[toY][toX] = EmptySquare;
9147       if((toX > fromX) != (piece == BlackRook)) {
9148         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9149       } else {
9150         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9151       }
9152     /* End of code added by Tord */
9153
9154     } else if (board[fromY][fromX] == king
9155         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9156         && toY == fromY && toX > fromX+1) {
9157         board[fromY][fromX] = EmptySquare;
9158         board[toY][toX] = king;
9159         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9160         board[fromY][BOARD_RGHT-1] = EmptySquare;
9161     } else if (board[fromY][fromX] == king
9162         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9163                && toY == fromY && toX < fromX-1) {
9164         board[fromY][fromX] = EmptySquare;
9165         board[toY][toX] = king;
9166         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9167         board[fromY][BOARD_LEFT] = EmptySquare;
9168     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9169                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9170                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9171                ) {
9172         /* white pawn promotion */
9173         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9174         if(gameInfo.variant==VariantBughouse ||
9175            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9176             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9177         board[fromY][fromX] = EmptySquare;
9178     } else if ((fromY >= BOARD_HEIGHT>>1)
9179                && (toX != fromX)
9180                && gameInfo.variant != VariantXiangqi
9181                && gameInfo.variant != VariantBerolina
9182                && (board[fromY][fromX] == WhitePawn)
9183                && (board[toY][toX] == EmptySquare)) {
9184         board[fromY][fromX] = EmptySquare;
9185         board[toY][toX] = WhitePawn;
9186         captured = board[toY - 1][toX];
9187         board[toY - 1][toX] = EmptySquare;
9188     } else if ((fromY == BOARD_HEIGHT-4)
9189                && (toX == fromX)
9190                && gameInfo.variant == VariantBerolina
9191                && (board[fromY][fromX] == WhitePawn)
9192                && (board[toY][toX] == EmptySquare)) {
9193         board[fromY][fromX] = EmptySquare;
9194         board[toY][toX] = WhitePawn;
9195         if(oldEP & EP_BEROLIN_A) {
9196                 captured = board[fromY][fromX-1];
9197                 board[fromY][fromX-1] = EmptySquare;
9198         }else{  captured = board[fromY][fromX+1];
9199                 board[fromY][fromX+1] = EmptySquare;
9200         }
9201     } else if (board[fromY][fromX] == king
9202         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9203                && toY == fromY && toX > fromX+1) {
9204         board[fromY][fromX] = EmptySquare;
9205         board[toY][toX] = king;
9206         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9207         board[fromY][BOARD_RGHT-1] = EmptySquare;
9208     } else if (board[fromY][fromX] == king
9209         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9210                && toY == fromY && toX < fromX-1) {
9211         board[fromY][fromX] = EmptySquare;
9212         board[toY][toX] = king;
9213         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9214         board[fromY][BOARD_LEFT] = EmptySquare;
9215     } else if (fromY == 7 && fromX == 3
9216                && board[fromY][fromX] == BlackKing
9217                && toY == 7 && toX == 5) {
9218         board[fromY][fromX] = EmptySquare;
9219         board[toY][toX] = BlackKing;
9220         board[fromY][7] = EmptySquare;
9221         board[toY][4] = BlackRook;
9222     } else if (fromY == 7 && fromX == 3
9223                && board[fromY][fromX] == BlackKing
9224                && toY == 7 && toX == 1) {
9225         board[fromY][fromX] = EmptySquare;
9226         board[toY][toX] = BlackKing;
9227         board[fromY][0] = EmptySquare;
9228         board[toY][2] = BlackRook;
9229     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9230                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9231                && toY < promoRank && promoChar
9232                ) {
9233         /* black pawn promotion */
9234         board[toY][toX] = CharToPiece(ToLower(promoChar));
9235         if(gameInfo.variant==VariantBughouse ||
9236            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9237             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9238         board[fromY][fromX] = EmptySquare;
9239     } else if ((fromY < BOARD_HEIGHT>>1)
9240                && (toX != fromX)
9241                && gameInfo.variant != VariantXiangqi
9242                && gameInfo.variant != VariantBerolina
9243                && (board[fromY][fromX] == BlackPawn)
9244                && (board[toY][toX] == EmptySquare)) {
9245         board[fromY][fromX] = EmptySquare;
9246         board[toY][toX] = BlackPawn;
9247         captured = board[toY + 1][toX];
9248         board[toY + 1][toX] = EmptySquare;
9249     } else if ((fromY == 3)
9250                && (toX == fromX)
9251                && gameInfo.variant == VariantBerolina
9252                && (board[fromY][fromX] == BlackPawn)
9253                && (board[toY][toX] == EmptySquare)) {
9254         board[fromY][fromX] = EmptySquare;
9255         board[toY][toX] = BlackPawn;
9256         if(oldEP & EP_BEROLIN_A) {
9257                 captured = board[fromY][fromX-1];
9258                 board[fromY][fromX-1] = EmptySquare;
9259         }else{  captured = board[fromY][fromX+1];
9260                 board[fromY][fromX+1] = EmptySquare;
9261         }
9262     } else {
9263         board[toY][toX] = board[fromY][fromX];
9264         board[fromY][fromX] = EmptySquare;
9265     }
9266   }
9267
9268     if (gameInfo.holdingsWidth != 0) {
9269
9270       /* !!A lot more code needs to be written to support holdings  */
9271       /* [HGM] OK, so I have written it. Holdings are stored in the */
9272       /* penultimate board files, so they are automaticlly stored   */
9273       /* in the game history.                                       */
9274       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9275                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9276         /* Delete from holdings, by decreasing count */
9277         /* and erasing image if necessary            */
9278         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9279         if(p < (int) BlackPawn) { /* white drop */
9280              p -= (int)WhitePawn;
9281                  p = PieceToNumber((ChessSquare)p);
9282              if(p >= gameInfo.holdingsSize) p = 0;
9283              if(--board[p][BOARD_WIDTH-2] <= 0)
9284                   board[p][BOARD_WIDTH-1] = EmptySquare;
9285              if((int)board[p][BOARD_WIDTH-2] < 0)
9286                         board[p][BOARD_WIDTH-2] = 0;
9287         } else {                  /* black drop */
9288              p -= (int)BlackPawn;
9289                  p = PieceToNumber((ChessSquare)p);
9290              if(p >= gameInfo.holdingsSize) p = 0;
9291              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9292                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9293              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9294                         board[BOARD_HEIGHT-1-p][1] = 0;
9295         }
9296       }
9297       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9298           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9299         /* [HGM] holdings: Add to holdings, if holdings exist */
9300         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9301                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9302                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9303         }
9304         p = (int) captured;
9305         if (p >= (int) BlackPawn) {
9306           p -= (int)BlackPawn;
9307           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9308                   /* in Shogi restore piece to its original  first */
9309                   captured = (ChessSquare) (DEMOTED captured);
9310                   p = DEMOTED p;
9311           }
9312           p = PieceToNumber((ChessSquare)p);
9313           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9314           board[p][BOARD_WIDTH-2]++;
9315           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9316         } else {
9317           p -= (int)WhitePawn;
9318           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9319                   captured = (ChessSquare) (DEMOTED captured);
9320                   p = DEMOTED p;
9321           }
9322           p = PieceToNumber((ChessSquare)p);
9323           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9324           board[BOARD_HEIGHT-1-p][1]++;
9325           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9326         }
9327       }
9328     } else if (gameInfo.variant == VariantAtomic) {
9329       if (captured != EmptySquare) {
9330         int y, x;
9331         for (y = toY-1; y <= toY+1; y++) {
9332           for (x = toX-1; x <= toX+1; x++) {
9333             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9334                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9335               board[y][x] = EmptySquare;
9336             }
9337           }
9338         }
9339         board[toY][toX] = EmptySquare;
9340       }
9341     }
9342     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9343         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9344     } else
9345     if(promoChar == '+') {
9346         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9347         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9348     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9349         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9350     }
9351     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9352                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9353         // [HGM] superchess: take promotion piece out of holdings
9354         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9355         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9356             if(!--board[k][BOARD_WIDTH-2])
9357                 board[k][BOARD_WIDTH-1] = EmptySquare;
9358         } else {
9359             if(!--board[BOARD_HEIGHT-1-k][1])
9360                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9361         }
9362     }
9363
9364 }
9365
9366 /* Updates forwardMostMove */
9367 void
9368 MakeMove(fromX, fromY, toX, toY, promoChar)
9369      int fromX, fromY, toX, toY;
9370      int promoChar;
9371 {
9372 //    forwardMostMove++; // [HGM] bare: moved downstream
9373
9374     (void) CoordsToAlgebraic(boards[forwardMostMove],
9375                              PosFlags(forwardMostMove),
9376                              fromY, fromX, toY, toX, promoChar,
9377                              parseList[forwardMostMove]);
9378
9379     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9380         int timeLeft; static int lastLoadFlag=0; int king, piece;
9381         piece = boards[forwardMostMove][fromY][fromX];
9382         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9383         if(gameInfo.variant == VariantKnightmate)
9384             king += (int) WhiteUnicorn - (int) WhiteKing;
9385         if(forwardMostMove == 0) {
9386             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9387                 fprintf(serverMoves, "%s;", UserName());
9388             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9389                 fprintf(serverMoves, "%s;", second.tidy);
9390             fprintf(serverMoves, "%s;", first.tidy);
9391             if(gameMode == MachinePlaysWhite)
9392                 fprintf(serverMoves, "%s;", UserName());
9393             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9394                 fprintf(serverMoves, "%s;", second.tidy);
9395         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9396         lastLoadFlag = loadFlag;
9397         // print base move
9398         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9399         // print castling suffix
9400         if( toY == fromY && piece == king ) {
9401             if(toX-fromX > 1)
9402                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9403             if(fromX-toX >1)
9404                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9405         }
9406         // e.p. suffix
9407         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9408              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9409              boards[forwardMostMove][toY][toX] == EmptySquare
9410              && fromX != toX && fromY != toY)
9411                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9412         // promotion suffix
9413         if(promoChar != NULLCHAR)
9414                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9415         if(!loadFlag) {
9416                 char buf[MOVE_LEN*2], *p; int len;
9417             fprintf(serverMoves, "/%d/%d",
9418                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9419             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9420             else                      timeLeft = blackTimeRemaining/1000;
9421             fprintf(serverMoves, "/%d", timeLeft);
9422                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9423                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9424                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9425             fprintf(serverMoves, "/%s", buf);
9426         }
9427         fflush(serverMoves);
9428     }
9429
9430     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9431         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9432       return;
9433     }
9434     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9435     if (commentList[forwardMostMove+1] != NULL) {
9436         free(commentList[forwardMostMove+1]);
9437         commentList[forwardMostMove+1] = NULL;
9438     }
9439     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9440     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9441     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9442     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9443     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9444     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9445     adjustedClock = FALSE;
9446     gameInfo.result = GameUnfinished;
9447     if (gameInfo.resultDetails != NULL) {
9448         free(gameInfo.resultDetails);
9449         gameInfo.resultDetails = NULL;
9450     }
9451     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9452                               moveList[forwardMostMove - 1]);
9453     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9454       case MT_NONE:
9455       case MT_STALEMATE:
9456       default:
9457         break;
9458       case MT_CHECK:
9459         if(gameInfo.variant != VariantShogi)
9460             strcat(parseList[forwardMostMove - 1], "+");
9461         break;
9462       case MT_CHECKMATE:
9463       case MT_STAINMATE:
9464         strcat(parseList[forwardMostMove - 1], "#");
9465         break;
9466     }
9467     if (appData.debugMode) {
9468         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9469     }
9470
9471 }
9472
9473 /* Updates currentMove if not pausing */
9474 void
9475 ShowMove(fromX, fromY, toX, toY)
9476 {
9477     int instant = (gameMode == PlayFromGameFile) ?
9478         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9479     if(appData.noGUI) return;
9480     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9481         if (!instant) {
9482             if (forwardMostMove == currentMove + 1) {
9483                 AnimateMove(boards[forwardMostMove - 1],
9484                             fromX, fromY, toX, toY);
9485             }
9486             if (appData.highlightLastMove) {
9487                 SetHighlights(fromX, fromY, toX, toY);
9488             }
9489         }
9490         currentMove = forwardMostMove;
9491     }
9492
9493     if (instant) return;
9494
9495     DisplayMove(currentMove - 1);
9496     DrawPosition(FALSE, boards[currentMove]);
9497     DisplayBothClocks();
9498     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9499 }
9500
9501 void SendEgtPath(ChessProgramState *cps)
9502 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9503         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9504
9505         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9506
9507         while(*p) {
9508             char c, *q = name+1, *r, *s;
9509
9510             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9511             while(*p && *p != ',') *q++ = *p++;
9512             *q++ = ':'; *q = 0;
9513             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9514                 strcmp(name, ",nalimov:") == 0 ) {
9515                 // take nalimov path from the menu-changeable option first, if it is defined
9516               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9517                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9518             } else
9519             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9520                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9521                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9522                 s = r = StrStr(s, ":") + 1; // beginning of path info
9523                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9524                 c = *r; *r = 0;             // temporarily null-terminate path info
9525                     *--q = 0;               // strip of trailig ':' from name
9526                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9527                 *r = c;
9528                 SendToProgram(buf,cps);     // send egtbpath command for this format
9529             }
9530             if(*p == ',') p++; // read away comma to position for next format name
9531         }
9532 }
9533
9534 void
9535 InitChessProgram(cps, setup)
9536      ChessProgramState *cps;
9537      int setup; /* [HGM] needed to setup FRC opening position */
9538 {
9539     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9540     if (appData.noChessProgram) return;
9541     hintRequested = FALSE;
9542     bookRequested = FALSE;
9543
9544     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9545     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9546     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9547     if(cps->memSize) { /* [HGM] memory */
9548       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9549         SendToProgram(buf, cps);
9550     }
9551     SendEgtPath(cps); /* [HGM] EGT */
9552     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9553       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9554         SendToProgram(buf, cps);
9555     }
9556
9557     SendToProgram(cps->initString, cps);
9558     if (gameInfo.variant != VariantNormal &&
9559         gameInfo.variant != VariantLoadable
9560         /* [HGM] also send variant if board size non-standard */
9561         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9562                                             ) {
9563       char *v = VariantName(gameInfo.variant);
9564       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9565         /* [HGM] in protocol 1 we have to assume all variants valid */
9566         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9567         DisplayFatalError(buf, 0, 1);
9568         return;
9569       }
9570
9571       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9572       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9573       if( gameInfo.variant == VariantXiangqi )
9574            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9575       if( gameInfo.variant == VariantShogi )
9576            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9577       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9578            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9579       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9580           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9581            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9582       if( gameInfo.variant == VariantCourier )
9583            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9584       if( gameInfo.variant == VariantSuper )
9585            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9586       if( gameInfo.variant == VariantGreat )
9587            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9588       if( gameInfo.variant == VariantSChess )
9589            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9590       if( gameInfo.variant == VariantGrand )
9591            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9592
9593       if(overruled) {
9594         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9595                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9596            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9597            if(StrStr(cps->variants, b) == NULL) {
9598                // specific sized variant not known, check if general sizing allowed
9599                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9600                    if(StrStr(cps->variants, "boardsize") == NULL) {
9601                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9602                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9603                        DisplayFatalError(buf, 0, 1);
9604                        return;
9605                    }
9606                    /* [HGM] here we really should compare with the maximum supported board size */
9607                }
9608            }
9609       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9610       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9611       SendToProgram(buf, cps);
9612     }
9613     currentlyInitializedVariant = gameInfo.variant;
9614
9615     /* [HGM] send opening position in FRC to first engine */
9616     if(setup) {
9617           SendToProgram("force\n", cps);
9618           SendBoard(cps, 0);
9619           /* engine is now in force mode! Set flag to wake it up after first move. */
9620           setboardSpoiledMachineBlack = 1;
9621     }
9622
9623     if (cps->sendICS) {
9624       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9625       SendToProgram(buf, cps);
9626     }
9627     cps->maybeThinking = FALSE;
9628     cps->offeredDraw = 0;
9629     if (!appData.icsActive) {
9630         SendTimeControl(cps, movesPerSession, timeControl,
9631                         timeIncrement, appData.searchDepth,
9632                         searchTime);
9633     }
9634     if (appData.showThinking
9635         // [HGM] thinking: four options require thinking output to be sent
9636         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9637                                 ) {
9638         SendToProgram("post\n", cps);
9639     }
9640     SendToProgram("hard\n", cps);
9641     if (!appData.ponderNextMove) {
9642         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9643            it without being sure what state we are in first.  "hard"
9644            is not a toggle, so that one is OK.
9645          */
9646         SendToProgram("easy\n", cps);
9647     }
9648     if (cps->usePing) {
9649       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9650       SendToProgram(buf, cps);
9651     }
9652     cps->initDone = TRUE;
9653     ClearEngineOutputPane(cps == &second);
9654 }
9655
9656
9657 void
9658 StartChessProgram(cps)
9659      ChessProgramState *cps;
9660 {
9661     char buf[MSG_SIZ];
9662     int err;
9663
9664     if (appData.noChessProgram) return;
9665     cps->initDone = FALSE;
9666
9667     if (strcmp(cps->host, "localhost") == 0) {
9668         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9669     } else if (*appData.remoteShell == NULLCHAR) {
9670         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9671     } else {
9672         if (*appData.remoteUser == NULLCHAR) {
9673           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9674                     cps->program);
9675         } else {
9676           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9677                     cps->host, appData.remoteUser, cps->program);
9678         }
9679         err = StartChildProcess(buf, "", &cps->pr);
9680     }
9681
9682     if (err != 0) {
9683       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9684         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9685         if(cps != &first) return;
9686         appData.noChessProgram = TRUE;
9687         ThawUI();
9688         SetNCPMode();
9689 //      DisplayFatalError(buf, err, 1);
9690 //      cps->pr = NoProc;
9691 //      cps->isr = NULL;
9692         return;
9693     }
9694
9695     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9696     if (cps->protocolVersion > 1) {
9697       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9698       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9699       cps->comboCnt = 0;  //                and values of combo boxes
9700       SendToProgram(buf, cps);
9701     } else {
9702       SendToProgram("xboard\n", cps);
9703     }
9704 }
9705
9706 void
9707 TwoMachinesEventIfReady P((void))
9708 {
9709   static int curMess = 0;
9710   if (first.lastPing != first.lastPong) {
9711     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9712     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9713     return;
9714   }
9715   if (second.lastPing != second.lastPong) {
9716     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9717     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9718     return;
9719   }
9720   DisplayMessage("", ""); curMess = 0;
9721   ThawUI();
9722   TwoMachinesEvent();
9723 }
9724
9725 char *
9726 MakeName(char *template)
9727 {
9728     time_t clock;
9729     struct tm *tm;
9730     static char buf[MSG_SIZ];
9731     char *p = buf;
9732     int i;
9733
9734     clock = time((time_t *)NULL);
9735     tm = localtime(&clock);
9736
9737     while(*p++ = *template++) if(p[-1] == '%') {
9738         switch(*template++) {
9739           case 0:   *p = 0; return buf;
9740           case 'Y': i = tm->tm_year+1900; break;
9741           case 'y': i = tm->tm_year-100; break;
9742           case 'M': i = tm->tm_mon+1; break;
9743           case 'd': i = tm->tm_mday; break;
9744           case 'h': i = tm->tm_hour; break;
9745           case 'm': i = tm->tm_min; break;
9746           case 's': i = tm->tm_sec; break;
9747           default:  i = 0;
9748         }
9749         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9750     }
9751     return buf;
9752 }
9753
9754 int
9755 CountPlayers(char *p)
9756 {
9757     int n = 0;
9758     while(p = strchr(p, '\n')) p++, n++; // count participants
9759     return n;
9760 }
9761
9762 FILE *
9763 WriteTourneyFile(char *results, FILE *f)
9764 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9765     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9766     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9767         // create a file with tournament description
9768         fprintf(f, "-participants {%s}\n", appData.participants);
9769         fprintf(f, "-seedBase %d\n", appData.seedBase);
9770         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9771         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9772         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9773         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9774         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9775         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9776         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9777         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9778         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9779         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9780         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9781         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9782         if(searchTime > 0)
9783                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9784         else {
9785                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9786                 fprintf(f, "-tc %s\n", appData.timeControl);
9787                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9788         }
9789         fprintf(f, "-results \"%s\"\n", results);
9790     }
9791     return f;
9792 }
9793
9794 #define MAXENGINES 1000
9795 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9796
9797 void Substitute(char *participants, int expunge)
9798 {
9799     int i, changed, changes=0, nPlayers=0;
9800     char *p, *q, *r, buf[MSG_SIZ];
9801     if(participants == NULL) return;
9802     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9803     r = p = participants; q = appData.participants;
9804     while(*p && *p == *q) {
9805         if(*p == '\n') r = p+1, nPlayers++;
9806         p++; q++;
9807     }
9808     if(*p) { // difference
9809         while(*p && *p++ != '\n');
9810         while(*q && *q++ != '\n');
9811       changed = nPlayers;
9812         changes = 1 + (strcmp(p, q) != 0);
9813     }
9814     if(changes == 1) { // a single engine mnemonic was changed
9815         q = r; while(*q) nPlayers += (*q++ == '\n');
9816         p = buf; while(*r && (*p = *r++) != '\n') p++;
9817         *p = NULLCHAR;
9818         NamesToList(firstChessProgramNames, command, mnemonic);
9819         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9820         if(mnemonic[i]) { // The substitute is valid
9821             FILE *f;
9822             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9823                 flock(fileno(f), LOCK_EX);
9824                 ParseArgsFromFile(f);
9825                 fseek(f, 0, SEEK_SET);
9826                 FREE(appData.participants); appData.participants = participants;
9827                 if(expunge) { // erase results of replaced engine
9828                     int len = strlen(appData.results), w, b, dummy;
9829                     for(i=0; i<len; i++) {
9830                         Pairing(i, nPlayers, &w, &b, &dummy);
9831                         if((w == changed || b == changed) && appData.results[i] == '*') {
9832                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9833                             fclose(f);
9834                             return;
9835                         }
9836                     }
9837                     for(i=0; i<len; i++) {
9838                         Pairing(i, nPlayers, &w, &b, &dummy);
9839                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9840                     }
9841                 }
9842                 WriteTourneyFile(appData.results, f);
9843                 fclose(f); // release lock
9844                 return;
9845             }
9846         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9847     }
9848     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9849     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9850     free(participants);
9851     return;
9852 }
9853
9854 int
9855 CreateTourney(char *name)
9856 {
9857         FILE *f;
9858         if(matchMode && strcmp(name, appData.tourneyFile)) {
9859              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9860         }
9861         if(name[0] == NULLCHAR) {
9862             if(appData.participants[0])
9863                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9864             return 0;
9865         }
9866         f = fopen(name, "r");
9867         if(f) { // file exists
9868             ASSIGN(appData.tourneyFile, name);
9869             ParseArgsFromFile(f); // parse it
9870         } else {
9871             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9872             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9873                 DisplayError(_("Not enough participants"), 0);
9874                 return 0;
9875             }
9876             ASSIGN(appData.tourneyFile, name);
9877             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9878             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9879         }
9880         fclose(f);
9881         appData.noChessProgram = FALSE;
9882         appData.clockMode = TRUE;
9883         SetGNUMode();
9884         return 1;
9885 }
9886
9887 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9888 {
9889     char buf[MSG_SIZ], *p, *q;
9890     int i=1;
9891     while(*names) {
9892         p = names; q = buf;
9893         while(*p && *p != '\n') *q++ = *p++;
9894         *q = 0;
9895         if(engineList[i]) free(engineList[i]);
9896         engineList[i] = strdup(buf);
9897         if(*p == '\n') p++;
9898         TidyProgramName(engineList[i], "localhost", buf);
9899         if(engineMnemonic[i]) free(engineMnemonic[i]);
9900         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9901             strcat(buf, " (");
9902             sscanf(q + 8, "%s", buf + strlen(buf));
9903             strcat(buf, ")");
9904         }
9905         engineMnemonic[i] = strdup(buf);
9906         names = p; i++;
9907       if(i > MAXENGINES - 2) break;
9908     }
9909     engineList[i] = engineMnemonic[i] = NULL;
9910 }
9911
9912 // following implemented as macro to avoid type limitations
9913 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9914
9915 void SwapEngines(int n)
9916 {   // swap settings for first engine and other engine (so far only some selected options)
9917     int h;
9918     char *p;
9919     if(n == 0) return;
9920     SWAP(directory, p)
9921     SWAP(chessProgram, p)
9922     SWAP(isUCI, h)
9923     SWAP(hasOwnBookUCI, h)
9924     SWAP(protocolVersion, h)
9925     SWAP(reuse, h)
9926     SWAP(scoreIsAbsolute, h)
9927     SWAP(timeOdds, h)
9928     SWAP(logo, p)
9929     SWAP(pgnName, p)
9930     SWAP(pvSAN, h)
9931     SWAP(engOptions, p)
9932 }
9933
9934 void
9935 SetPlayer(int player)
9936 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9937     int i;
9938     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9939     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9940     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9941     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9942     if(mnemonic[i]) {
9943         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9944         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9945         appData.firstHasOwnBookUCI = !appData.defNoBook;
9946         ParseArgsFromString(buf);
9947     }
9948     free(engineName);
9949 }
9950
9951 int
9952 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9953 {   // determine players from game number
9954     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9955
9956     if(appData.tourneyType == 0) {
9957         roundsPerCycle = (nPlayers - 1) | 1;
9958         pairingsPerRound = nPlayers / 2;
9959     } else if(appData.tourneyType > 0) {
9960         roundsPerCycle = nPlayers - appData.tourneyType;
9961         pairingsPerRound = appData.tourneyType;
9962     }
9963     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9964     gamesPerCycle = gamesPerRound * roundsPerCycle;
9965     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9966     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9967     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9968     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9969     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9970     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9971
9972     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9973     if(appData.roundSync) *syncInterval = gamesPerRound;
9974
9975     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9976
9977     if(appData.tourneyType == 0) {
9978         if(curPairing == (nPlayers-1)/2 ) {
9979             *whitePlayer = curRound;
9980             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9981         } else {
9982             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9983             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9984             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9985             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9986         }
9987     } else if(appData.tourneyType > 0) {
9988         *whitePlayer = curPairing;
9989         *blackPlayer = curRound + appData.tourneyType;
9990     }
9991
9992     // take care of white/black alternation per round. 
9993     // For cycles and games this is already taken care of by default, derived from matchGame!
9994     return curRound & 1;
9995 }
9996
9997 int
9998 NextTourneyGame(int nr, int *swapColors)
9999 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10000     char *p, *q;
10001     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10002     FILE *tf;
10003     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10004     tf = fopen(appData.tourneyFile, "r");
10005     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10006     ParseArgsFromFile(tf); fclose(tf);
10007     InitTimeControls(); // TC might be altered from tourney file
10008
10009     nPlayers = CountPlayers(appData.participants); // count participants
10010     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10011     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10012
10013     if(syncInterval) {
10014         p = q = appData.results;
10015         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10016         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10017             DisplayMessage(_("Waiting for other game(s)"),"");
10018             waitingForGame = TRUE;
10019             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10020             return 0;
10021         }
10022         waitingForGame = FALSE;
10023     }
10024
10025     if(appData.tourneyType < 0) {
10026         if(nr>=0 && !pairingReceived) {
10027             char buf[1<<16];
10028             if(pairing.pr == NoProc) {
10029                 if(!appData.pairingEngine[0]) {
10030                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10031                     return 0;
10032                 }
10033                 StartChessProgram(&pairing); // starts the pairing engine
10034             }
10035             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10036             SendToProgram(buf, &pairing);
10037             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10038             SendToProgram(buf, &pairing);
10039             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10040         }
10041         pairingReceived = 0;                              // ... so we continue here 
10042         *swapColors = 0;
10043         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10044         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10045         matchGame = 1; roundNr = nr / syncInterval + 1;
10046     }
10047
10048     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10049
10050     // redefine engines, engine dir, etc.
10051     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10052     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10053     SwapEngines(1);
10054     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10055     SwapEngines(1);         // and make that valid for second engine by swapping
10056     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10057     InitEngine(&second, 1);
10058     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10059     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10060     return 1;
10061 }
10062
10063 void
10064 NextMatchGame()
10065 {   // performs game initialization that does not invoke engines, and then tries to start the game
10066     int res, firstWhite, swapColors = 0;
10067     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10068     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10069     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10070     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10071     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10072     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10073     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10074     Reset(FALSE, first.pr != NoProc);
10075     res = LoadGameOrPosition(matchGame); // setup game
10076     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10077     if(!res) return; // abort when bad game/pos file
10078     TwoMachinesEvent();
10079 }
10080
10081 void UserAdjudicationEvent( int result )
10082 {
10083     ChessMove gameResult = GameIsDrawn;
10084
10085     if( result > 0 ) {
10086         gameResult = WhiteWins;
10087     }
10088     else if( result < 0 ) {
10089         gameResult = BlackWins;
10090     }
10091
10092     if( gameMode == TwoMachinesPlay ) {
10093         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10094     }
10095 }
10096
10097
10098 // [HGM] save: calculate checksum of game to make games easily identifiable
10099 int StringCheckSum(char *s)
10100 {
10101         int i = 0;
10102         if(s==NULL) return 0;
10103         while(*s) i = i*259 + *s++;
10104         return i;
10105 }
10106
10107 int GameCheckSum()
10108 {
10109         int i, sum=0;
10110         for(i=backwardMostMove; i<forwardMostMove; i++) {
10111                 sum += pvInfoList[i].depth;
10112                 sum += StringCheckSum(parseList[i]);
10113                 sum += StringCheckSum(commentList[i]);
10114                 sum *= 261;
10115         }
10116         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10117         return sum + StringCheckSum(commentList[i]);
10118 } // end of save patch
10119
10120 void
10121 GameEnds(result, resultDetails, whosays)
10122      ChessMove result;
10123      char *resultDetails;
10124      int whosays;
10125 {
10126     GameMode nextGameMode;
10127     int isIcsGame;
10128     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10129
10130     if(endingGame) return; /* [HGM] crash: forbid recursion */
10131     endingGame = 1;
10132     if(twoBoards) { // [HGM] dual: switch back to one board
10133         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10134         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10135     }
10136     if (appData.debugMode) {
10137       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10138               result, resultDetails ? resultDetails : "(null)", whosays);
10139     }
10140
10141     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10142
10143     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10144         /* If we are playing on ICS, the server decides when the
10145            game is over, but the engine can offer to draw, claim
10146            a draw, or resign.
10147          */
10148 #if ZIPPY
10149         if (appData.zippyPlay && first.initDone) {
10150             if (result == GameIsDrawn) {
10151                 /* In case draw still needs to be claimed */
10152                 SendToICS(ics_prefix);
10153                 SendToICS("draw\n");
10154             } else if (StrCaseStr(resultDetails, "resign")) {
10155                 SendToICS(ics_prefix);
10156                 SendToICS("resign\n");
10157             }
10158         }
10159 #endif
10160         endingGame = 0; /* [HGM] crash */
10161         return;
10162     }
10163
10164     /* If we're loading the game from a file, stop */
10165     if (whosays == GE_FILE) {
10166       (void) StopLoadGameTimer();
10167       gameFileFP = NULL;
10168     }
10169
10170     /* Cancel draw offers */
10171     first.offeredDraw = second.offeredDraw = 0;
10172
10173     /* If this is an ICS game, only ICS can really say it's done;
10174        if not, anyone can. */
10175     isIcsGame = (gameMode == IcsPlayingWhite ||
10176                  gameMode == IcsPlayingBlack ||
10177                  gameMode == IcsObserving    ||
10178                  gameMode == IcsExamining);
10179
10180     if (!isIcsGame || whosays == GE_ICS) {
10181         /* OK -- not an ICS game, or ICS said it was done */
10182         StopClocks();
10183         if (!isIcsGame && !appData.noChessProgram)
10184           SetUserThinkingEnables();
10185
10186         /* [HGM] if a machine claims the game end we verify this claim */
10187         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10188             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10189                 char claimer;
10190                 ChessMove trueResult = (ChessMove) -1;
10191
10192                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10193                                             first.twoMachinesColor[0] :
10194                                             second.twoMachinesColor[0] ;
10195
10196                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10197                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10198                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10199                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10200                 } else
10201                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10202                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10203                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10204                 } else
10205                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10206                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10207                 }
10208
10209                 // now verify win claims, but not in drop games, as we don't understand those yet
10210                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10211                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10212                     (result == WhiteWins && claimer == 'w' ||
10213                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10214                       if (appData.debugMode) {
10215                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10216                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10217                       }
10218                       if(result != trueResult) {
10219                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10220                               result = claimer == 'w' ? BlackWins : WhiteWins;
10221                               resultDetails = buf;
10222                       }
10223                 } else
10224                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10225                     && (forwardMostMove <= backwardMostMove ||
10226                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10227                         (claimer=='b')==(forwardMostMove&1))
10228                                                                                   ) {
10229                       /* [HGM] verify: draws that were not flagged are false claims */
10230                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10231                       result = claimer == 'w' ? BlackWins : WhiteWins;
10232                       resultDetails = buf;
10233                 }
10234                 /* (Claiming a loss is accepted no questions asked!) */
10235             }
10236             /* [HGM] bare: don't allow bare King to win */
10237             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10238                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10239                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10240                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10241                && result != GameIsDrawn)
10242             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10243                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10244                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10245                         if(p >= 0 && p <= (int)WhiteKing) k++;
10246                 }
10247                 if (appData.debugMode) {
10248                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10249                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10250                 }
10251                 if(k <= 1) {
10252                         result = GameIsDrawn;
10253                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10254                         resultDetails = buf;
10255                 }
10256             }
10257         }
10258
10259
10260         if(serverMoves != NULL && !loadFlag) { char c = '=';
10261             if(result==WhiteWins) c = '+';
10262             if(result==BlackWins) c = '-';
10263             if(resultDetails != NULL)
10264                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10265         }
10266         if (resultDetails != NULL) {
10267             gameInfo.result = result;
10268             gameInfo.resultDetails = StrSave(resultDetails);
10269
10270             /* display last move only if game was not loaded from file */
10271             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10272                 DisplayMove(currentMove - 1);
10273
10274             if (forwardMostMove != 0) {
10275                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10276                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10277                                                                 ) {
10278                     if (*appData.saveGameFile != NULLCHAR) {
10279                         SaveGameToFile(appData.saveGameFile, TRUE);
10280                     } else if (appData.autoSaveGames) {
10281                         AutoSaveGame();
10282                     }
10283                     if (*appData.savePositionFile != NULLCHAR) {
10284                         SavePositionToFile(appData.savePositionFile);
10285                     }
10286                 }
10287             }
10288
10289             /* Tell program how game ended in case it is learning */
10290             /* [HGM] Moved this to after saving the PGN, just in case */
10291             /* engine died and we got here through time loss. In that */
10292             /* case we will get a fatal error writing the pipe, which */
10293             /* would otherwise lose us the PGN.                       */
10294             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10295             /* output during GameEnds should never be fatal anymore   */
10296             if (gameMode == MachinePlaysWhite ||
10297                 gameMode == MachinePlaysBlack ||
10298                 gameMode == TwoMachinesPlay ||
10299                 gameMode == IcsPlayingWhite ||
10300                 gameMode == IcsPlayingBlack ||
10301                 gameMode == BeginningOfGame) {
10302                 char buf[MSG_SIZ];
10303                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10304                         resultDetails);
10305                 if (first.pr != NoProc) {
10306                     SendToProgram(buf, &first);
10307                 }
10308                 if (second.pr != NoProc &&
10309                     gameMode == TwoMachinesPlay) {
10310                     SendToProgram(buf, &second);
10311                 }
10312             }
10313         }
10314
10315         if (appData.icsActive) {
10316             if (appData.quietPlay &&
10317                 (gameMode == IcsPlayingWhite ||
10318                  gameMode == IcsPlayingBlack)) {
10319                 SendToICS(ics_prefix);
10320                 SendToICS("set shout 1\n");
10321             }
10322             nextGameMode = IcsIdle;
10323             ics_user_moved = FALSE;
10324             /* clean up premove.  It's ugly when the game has ended and the
10325              * premove highlights are still on the board.
10326              */
10327             if (gotPremove) {
10328               gotPremove = FALSE;
10329               ClearPremoveHighlights();
10330               DrawPosition(FALSE, boards[currentMove]);
10331             }
10332             if (whosays == GE_ICS) {
10333                 switch (result) {
10334                 case WhiteWins:
10335                     if (gameMode == IcsPlayingWhite)
10336                         PlayIcsWinSound();
10337                     else if(gameMode == IcsPlayingBlack)
10338                         PlayIcsLossSound();
10339                     break;
10340                 case BlackWins:
10341                     if (gameMode == IcsPlayingBlack)
10342                         PlayIcsWinSound();
10343                     else if(gameMode == IcsPlayingWhite)
10344                         PlayIcsLossSound();
10345                     break;
10346                 case GameIsDrawn:
10347                     PlayIcsDrawSound();
10348                     break;
10349                 default:
10350                     PlayIcsUnfinishedSound();
10351                 }
10352             }
10353         } else if (gameMode == EditGame ||
10354                    gameMode == PlayFromGameFile ||
10355                    gameMode == AnalyzeMode ||
10356                    gameMode == AnalyzeFile) {
10357             nextGameMode = gameMode;
10358         } else {
10359             nextGameMode = EndOfGame;
10360         }
10361         pausing = FALSE;
10362         ModeHighlight();
10363     } else {
10364         nextGameMode = gameMode;
10365     }
10366
10367     if (appData.noChessProgram) {
10368         gameMode = nextGameMode;
10369         ModeHighlight();
10370         endingGame = 0; /* [HGM] crash */
10371         return;
10372     }
10373
10374     if (first.reuse) {
10375         /* Put first chess program into idle state */
10376         if (first.pr != NoProc &&
10377             (gameMode == MachinePlaysWhite ||
10378              gameMode == MachinePlaysBlack ||
10379              gameMode == TwoMachinesPlay ||
10380              gameMode == IcsPlayingWhite ||
10381              gameMode == IcsPlayingBlack ||
10382              gameMode == BeginningOfGame)) {
10383             SendToProgram("force\n", &first);
10384             if (first.usePing) {
10385               char buf[MSG_SIZ];
10386               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10387               SendToProgram(buf, &first);
10388             }
10389         }
10390     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10391         /* Kill off first chess program */
10392         if (first.isr != NULL)
10393           RemoveInputSource(first.isr);
10394         first.isr = NULL;
10395
10396         if (first.pr != NoProc) {
10397             ExitAnalyzeMode();
10398             DoSleep( appData.delayBeforeQuit );
10399             SendToProgram("quit\n", &first);
10400             DoSleep( appData.delayAfterQuit );
10401             DestroyChildProcess(first.pr, first.useSigterm);
10402         }
10403         first.pr = NoProc;
10404     }
10405     if (second.reuse) {
10406         /* Put second chess program into idle state */
10407         if (second.pr != NoProc &&
10408             gameMode == TwoMachinesPlay) {
10409             SendToProgram("force\n", &second);
10410             if (second.usePing) {
10411               char buf[MSG_SIZ];
10412               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10413               SendToProgram(buf, &second);
10414             }
10415         }
10416     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10417         /* Kill off second chess program */
10418         if (second.isr != NULL)
10419           RemoveInputSource(second.isr);
10420         second.isr = NULL;
10421
10422         if (second.pr != NoProc) {
10423             DoSleep( appData.delayBeforeQuit );
10424             SendToProgram("quit\n", &second);
10425             DoSleep( appData.delayAfterQuit );
10426             DestroyChildProcess(second.pr, second.useSigterm);
10427         }
10428         second.pr = NoProc;
10429     }
10430
10431     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10432         char resChar = '=';
10433         switch (result) {
10434         case WhiteWins:
10435           resChar = '+';
10436           if (first.twoMachinesColor[0] == 'w') {
10437             first.matchWins++;
10438           } else {
10439             second.matchWins++;
10440           }
10441           break;
10442         case BlackWins:
10443           resChar = '-';
10444           if (first.twoMachinesColor[0] == 'b') {
10445             first.matchWins++;
10446           } else {
10447             second.matchWins++;
10448           }
10449           break;
10450         case GameUnfinished:
10451           resChar = ' ';
10452         default:
10453           break;
10454         }
10455
10456         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10457         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10458             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10459             ReserveGame(nextGame, resChar); // sets nextGame
10460             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10461             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10462         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10463
10464         if (nextGame <= appData.matchGames && !abortMatch) {
10465             gameMode = nextGameMode;
10466             matchGame = nextGame; // this will be overruled in tourney mode!
10467             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10468             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10469             endingGame = 0; /* [HGM] crash */
10470             return;
10471         } else {
10472             gameMode = nextGameMode;
10473             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10474                      first.tidy, second.tidy,
10475                      first.matchWins, second.matchWins,
10476                      appData.matchGames - (first.matchWins + second.matchWins));
10477             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10478             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10479             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10480                 first.twoMachinesColor = "black\n";
10481                 second.twoMachinesColor = "white\n";
10482             } else {
10483                 first.twoMachinesColor = "white\n";
10484                 second.twoMachinesColor = "black\n";
10485             }
10486         }
10487     }
10488     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10489         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10490       ExitAnalyzeMode();
10491     gameMode = nextGameMode;
10492     ModeHighlight();
10493     endingGame = 0;  /* [HGM] crash */
10494     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10495         if(matchMode == TRUE) { // match through command line: exit with or without popup
10496             if(ranking) {
10497                 ToNrEvent(forwardMostMove);
10498                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10499                 else ExitEvent(0);
10500             } else DisplayFatalError(buf, 0, 0);
10501         } else { // match through menu; just stop, with or without popup
10502             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10503             ModeHighlight();
10504             if(ranking){
10505                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10506             } else DisplayNote(buf);
10507       }
10508       if(ranking) free(ranking);
10509     }
10510 }
10511
10512 /* Assumes program was just initialized (initString sent).
10513    Leaves program in force mode. */
10514 void
10515 FeedMovesToProgram(cps, upto)
10516      ChessProgramState *cps;
10517      int upto;
10518 {
10519     int i;
10520
10521     if (appData.debugMode)
10522       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10523               startedFromSetupPosition ? "position and " : "",
10524               backwardMostMove, upto, cps->which);
10525     if(currentlyInitializedVariant != gameInfo.variant) {
10526       char buf[MSG_SIZ];
10527         // [HGM] variantswitch: make engine aware of new variant
10528         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10529                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10530         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10531         SendToProgram(buf, cps);
10532         currentlyInitializedVariant = gameInfo.variant;
10533     }
10534     SendToProgram("force\n", cps);
10535     if (startedFromSetupPosition) {
10536         SendBoard(cps, backwardMostMove);
10537     if (appData.debugMode) {
10538         fprintf(debugFP, "feedMoves\n");
10539     }
10540     }
10541     for (i = backwardMostMove; i < upto; i++) {
10542         SendMoveToProgram(i, cps);
10543     }
10544 }
10545
10546
10547 int
10548 ResurrectChessProgram()
10549 {
10550      /* The chess program may have exited.
10551         If so, restart it and feed it all the moves made so far. */
10552     static int doInit = 0;
10553
10554     if (appData.noChessProgram) return 1;
10555
10556     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10557         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10558         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10559         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10560     } else {
10561         if (first.pr != NoProc) return 1;
10562         StartChessProgram(&first);
10563     }
10564     InitChessProgram(&first, FALSE);
10565     FeedMovesToProgram(&first, currentMove);
10566
10567     if (!first.sendTime) {
10568         /* can't tell gnuchess what its clock should read,
10569            so we bow to its notion. */
10570         ResetClocks();
10571         timeRemaining[0][currentMove] = whiteTimeRemaining;
10572         timeRemaining[1][currentMove] = blackTimeRemaining;
10573     }
10574
10575     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10576                 appData.icsEngineAnalyze) && first.analysisSupport) {
10577       SendToProgram("analyze\n", &first);
10578       first.analyzing = TRUE;
10579     }
10580     return 1;
10581 }
10582
10583 /*
10584  * Button procedures
10585  */
10586 void
10587 Reset(redraw, init)
10588      int redraw, init;
10589 {
10590     int i;
10591
10592     if (appData.debugMode) {
10593         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10594                 redraw, init, gameMode);
10595     }
10596     CleanupTail(); // [HGM] vari: delete any stored variations
10597     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10598     pausing = pauseExamInvalid = FALSE;
10599     startedFromSetupPosition = blackPlaysFirst = FALSE;
10600     firstMove = TRUE;
10601     whiteFlag = blackFlag = FALSE;
10602     userOfferedDraw = FALSE;
10603     hintRequested = bookRequested = FALSE;
10604     first.maybeThinking = FALSE;
10605     second.maybeThinking = FALSE;
10606     first.bookSuspend = FALSE; // [HGM] book
10607     second.bookSuspend = FALSE;
10608     thinkOutput[0] = NULLCHAR;
10609     lastHint[0] = NULLCHAR;
10610     ClearGameInfo(&gameInfo);
10611     gameInfo.variant = StringToVariant(appData.variant);
10612     ics_user_moved = ics_clock_paused = FALSE;
10613     ics_getting_history = H_FALSE;
10614     ics_gamenum = -1;
10615     white_holding[0] = black_holding[0] = NULLCHAR;
10616     ClearProgramStats();
10617     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10618
10619     ResetFrontEnd();
10620     ClearHighlights();
10621     flipView = appData.flipView;
10622     ClearPremoveHighlights();
10623     gotPremove = FALSE;
10624     alarmSounded = FALSE;
10625
10626     GameEnds(EndOfFile, NULL, GE_PLAYER);
10627     if(appData.serverMovesName != NULL) {
10628         /* [HGM] prepare to make moves file for broadcasting */
10629         clock_t t = clock();
10630         if(serverMoves != NULL) fclose(serverMoves);
10631         serverMoves = fopen(appData.serverMovesName, "r");
10632         if(serverMoves != NULL) {
10633             fclose(serverMoves);
10634             /* delay 15 sec before overwriting, so all clients can see end */
10635             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10636         }
10637         serverMoves = fopen(appData.serverMovesName, "w");
10638     }
10639
10640     ExitAnalyzeMode();
10641     gameMode = BeginningOfGame;
10642     ModeHighlight();
10643     if(appData.icsActive) gameInfo.variant = VariantNormal;
10644     currentMove = forwardMostMove = backwardMostMove = 0;
10645     InitPosition(redraw);
10646     for (i = 0; i < MAX_MOVES; i++) {
10647         if (commentList[i] != NULL) {
10648             free(commentList[i]);
10649             commentList[i] = NULL;
10650         }
10651     }
10652     ResetClocks();
10653     timeRemaining[0][0] = whiteTimeRemaining;
10654     timeRemaining[1][0] = blackTimeRemaining;
10655
10656     if (first.pr == NoProc) {
10657         StartChessProgram(&first);
10658     }
10659     if (init) {
10660             InitChessProgram(&first, startedFromSetupPosition);
10661     }
10662     DisplayTitle("");
10663     DisplayMessage("", "");
10664     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10665     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10666 }
10667
10668 void
10669 AutoPlayGameLoop()
10670 {
10671     for (;;) {
10672         if (!AutoPlayOneMove())
10673           return;
10674         if (matchMode || appData.timeDelay == 0)
10675           continue;
10676         if (appData.timeDelay < 0)
10677           return;
10678         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10679         break;
10680     }
10681 }
10682
10683
10684 int
10685 AutoPlayOneMove()
10686 {
10687     int fromX, fromY, toX, toY;
10688
10689     if (appData.debugMode) {
10690       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10691     }
10692
10693     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10694       return FALSE;
10695
10696     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10697       pvInfoList[currentMove].depth = programStats.depth;
10698       pvInfoList[currentMove].score = programStats.score;
10699       pvInfoList[currentMove].time  = 0;
10700       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10701     }
10702
10703     if (currentMove >= forwardMostMove) {
10704       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10705 //      gameMode = EndOfGame;
10706 //      ModeHighlight();
10707
10708       /* [AS] Clear current move marker at the end of a game */
10709       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10710
10711       return FALSE;
10712     }
10713
10714     toX = moveList[currentMove][2] - AAA;
10715     toY = moveList[currentMove][3] - ONE;
10716
10717     if (moveList[currentMove][1] == '@') {
10718         if (appData.highlightLastMove) {
10719             SetHighlights(-1, -1, toX, toY);
10720         }
10721     } else {
10722         fromX = moveList[currentMove][0] - AAA;
10723         fromY = moveList[currentMove][1] - ONE;
10724
10725         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10726
10727         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10728
10729         if (appData.highlightLastMove) {
10730             SetHighlights(fromX, fromY, toX, toY);
10731         }
10732     }
10733     DisplayMove(currentMove);
10734     SendMoveToProgram(currentMove++, &first);
10735     DisplayBothClocks();
10736     DrawPosition(FALSE, boards[currentMove]);
10737     // [HGM] PV info: always display, routine tests if empty
10738     DisplayComment(currentMove - 1, commentList[currentMove]);
10739     return TRUE;
10740 }
10741
10742
10743 int
10744 LoadGameOneMove(readAhead)
10745      ChessMove readAhead;
10746 {
10747     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10748     char promoChar = NULLCHAR;
10749     ChessMove moveType;
10750     char move[MSG_SIZ];
10751     char *p, *q;
10752
10753     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10754         gameMode != AnalyzeMode && gameMode != Training) {
10755         gameFileFP = NULL;
10756         return FALSE;
10757     }
10758
10759     yyboardindex = forwardMostMove;
10760     if (readAhead != EndOfFile) {
10761       moveType = readAhead;
10762     } else {
10763       if (gameFileFP == NULL)
10764           return FALSE;
10765       moveType = (ChessMove) Myylex();
10766     }
10767
10768     done = FALSE;
10769     switch (moveType) {
10770       case Comment:
10771         if (appData.debugMode)
10772           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10773         p = yy_text;
10774
10775         /* append the comment but don't display it */
10776         AppendComment(currentMove, p, FALSE);
10777         return TRUE;
10778
10779       case WhiteCapturesEnPassant:
10780       case BlackCapturesEnPassant:
10781       case WhitePromotion:
10782       case BlackPromotion:
10783       case WhiteNonPromotion:
10784       case BlackNonPromotion:
10785       case NormalMove:
10786       case WhiteKingSideCastle:
10787       case WhiteQueenSideCastle:
10788       case BlackKingSideCastle:
10789       case BlackQueenSideCastle:
10790       case WhiteKingSideCastleWild:
10791       case WhiteQueenSideCastleWild:
10792       case BlackKingSideCastleWild:
10793       case BlackQueenSideCastleWild:
10794       /* PUSH Fabien */
10795       case WhiteHSideCastleFR:
10796       case WhiteASideCastleFR:
10797       case BlackHSideCastleFR:
10798       case BlackASideCastleFR:
10799       /* POP Fabien */
10800         if (appData.debugMode)
10801           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10802         fromX = currentMoveString[0] - AAA;
10803         fromY = currentMoveString[1] - ONE;
10804         toX = currentMoveString[2] - AAA;
10805         toY = currentMoveString[3] - ONE;
10806         promoChar = currentMoveString[4];
10807         break;
10808
10809       case WhiteDrop:
10810       case BlackDrop:
10811         if (appData.debugMode)
10812           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10813         fromX = moveType == WhiteDrop ?
10814           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10815         (int) CharToPiece(ToLower(currentMoveString[0]));
10816         fromY = DROP_RANK;
10817         toX = currentMoveString[2] - AAA;
10818         toY = currentMoveString[3] - ONE;
10819         break;
10820
10821       case WhiteWins:
10822       case BlackWins:
10823       case GameIsDrawn:
10824       case GameUnfinished:
10825         if (appData.debugMode)
10826           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10827         p = strchr(yy_text, '{');
10828         if (p == NULL) p = strchr(yy_text, '(');
10829         if (p == NULL) {
10830             p = yy_text;
10831             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10832         } else {
10833             q = strchr(p, *p == '{' ? '}' : ')');
10834             if (q != NULL) *q = NULLCHAR;
10835             p++;
10836         }
10837         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10838         GameEnds(moveType, p, GE_FILE);
10839         done = TRUE;
10840         if (cmailMsgLoaded) {
10841             ClearHighlights();
10842             flipView = WhiteOnMove(currentMove);
10843             if (moveType == GameUnfinished) flipView = !flipView;
10844             if (appData.debugMode)
10845               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10846         }
10847         break;
10848
10849       case EndOfFile:
10850         if (appData.debugMode)
10851           fprintf(debugFP, "Parser hit end of file\n");
10852         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10853           case MT_NONE:
10854           case MT_CHECK:
10855             break;
10856           case MT_CHECKMATE:
10857           case MT_STAINMATE:
10858             if (WhiteOnMove(currentMove)) {
10859                 GameEnds(BlackWins, "Black mates", GE_FILE);
10860             } else {
10861                 GameEnds(WhiteWins, "White mates", GE_FILE);
10862             }
10863             break;
10864           case MT_STALEMATE:
10865             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10866             break;
10867         }
10868         done = TRUE;
10869         break;
10870
10871       case MoveNumberOne:
10872         if (lastLoadGameStart == GNUChessGame) {
10873             /* GNUChessGames have numbers, but they aren't move numbers */
10874             if (appData.debugMode)
10875               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10876                       yy_text, (int) moveType);
10877             return LoadGameOneMove(EndOfFile); /* tail recursion */
10878         }
10879         /* else fall thru */
10880
10881       case XBoardGame:
10882       case GNUChessGame:
10883       case PGNTag:
10884         /* Reached start of next game in file */
10885         if (appData.debugMode)
10886           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10887         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10888           case MT_NONE:
10889           case MT_CHECK:
10890             break;
10891           case MT_CHECKMATE:
10892           case MT_STAINMATE:
10893             if (WhiteOnMove(currentMove)) {
10894                 GameEnds(BlackWins, "Black mates", GE_FILE);
10895             } else {
10896                 GameEnds(WhiteWins, "White mates", GE_FILE);
10897             }
10898             break;
10899           case MT_STALEMATE:
10900             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10901             break;
10902         }
10903         done = TRUE;
10904         break;
10905
10906       case PositionDiagram:     /* should not happen; ignore */
10907       case ElapsedTime:         /* ignore */
10908       case NAG:                 /* ignore */
10909         if (appData.debugMode)
10910           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10911                   yy_text, (int) moveType);
10912         return LoadGameOneMove(EndOfFile); /* tail recursion */
10913
10914       case IllegalMove:
10915         if (appData.testLegality) {
10916             if (appData.debugMode)
10917               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10918             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10919                     (forwardMostMove / 2) + 1,
10920                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10921             DisplayError(move, 0);
10922             done = TRUE;
10923         } else {
10924             if (appData.debugMode)
10925               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10926                       yy_text, currentMoveString);
10927             fromX = currentMoveString[0] - AAA;
10928             fromY = currentMoveString[1] - ONE;
10929             toX = currentMoveString[2] - AAA;
10930             toY = currentMoveString[3] - ONE;
10931             promoChar = currentMoveString[4];
10932         }
10933         break;
10934
10935       case AmbiguousMove:
10936         if (appData.debugMode)
10937           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10938         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10939                 (forwardMostMove / 2) + 1,
10940                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10941         DisplayError(move, 0);
10942         done = TRUE;
10943         break;
10944
10945       default:
10946       case ImpossibleMove:
10947         if (appData.debugMode)
10948           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10949         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10950                 (forwardMostMove / 2) + 1,
10951                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10952         DisplayError(move, 0);
10953         done = TRUE;
10954         break;
10955     }
10956
10957     if (done) {
10958         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10959             DrawPosition(FALSE, boards[currentMove]);
10960             DisplayBothClocks();
10961             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10962               DisplayComment(currentMove - 1, commentList[currentMove]);
10963         }
10964         (void) StopLoadGameTimer();
10965         gameFileFP = NULL;
10966         cmailOldMove = forwardMostMove;
10967         return FALSE;
10968     } else {
10969         /* currentMoveString is set as a side-effect of yylex */
10970
10971         thinkOutput[0] = NULLCHAR;
10972         MakeMove(fromX, fromY, toX, toY, promoChar);
10973         currentMove = forwardMostMove;
10974         return TRUE;
10975     }
10976 }
10977
10978 /* Load the nth game from the given file */
10979 int
10980 LoadGameFromFile(filename, n, title, useList)
10981      char *filename;
10982      int n;
10983      char *title;
10984      /*Boolean*/ int useList;
10985 {
10986     FILE *f;
10987     char buf[MSG_SIZ];
10988
10989     if (strcmp(filename, "-") == 0) {
10990         f = stdin;
10991         title = "stdin";
10992     } else {
10993         f = fopen(filename, "rb");
10994         if (f == NULL) {
10995           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10996             DisplayError(buf, errno);
10997             return FALSE;
10998         }
10999     }
11000     if (fseek(f, 0, 0) == -1) {
11001         /* f is not seekable; probably a pipe */
11002         useList = FALSE;
11003     }
11004     if (useList && n == 0) {
11005         int error = GameListBuild(f);
11006         if (error) {
11007             DisplayError(_("Cannot build game list"), error);
11008         } else if (!ListEmpty(&gameList) &&
11009                    ((ListGame *) gameList.tailPred)->number > 1) {
11010             GameListPopUp(f, title);
11011             return TRUE;
11012         }
11013         GameListDestroy();
11014         n = 1;
11015     }
11016     if (n == 0) n = 1;
11017     return LoadGame(f, n, title, FALSE);
11018 }
11019
11020
11021 void
11022 MakeRegisteredMove()
11023 {
11024     int fromX, fromY, toX, toY;
11025     char promoChar;
11026     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11027         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11028           case CMAIL_MOVE:
11029           case CMAIL_DRAW:
11030             if (appData.debugMode)
11031               fprintf(debugFP, "Restoring %s for game %d\n",
11032                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11033
11034             thinkOutput[0] = NULLCHAR;
11035             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11036             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11037             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11038             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11039             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11040             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11041             MakeMove(fromX, fromY, toX, toY, promoChar);
11042             ShowMove(fromX, fromY, toX, toY);
11043
11044             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11045               case MT_NONE:
11046               case MT_CHECK:
11047                 break;
11048
11049               case MT_CHECKMATE:
11050               case MT_STAINMATE:
11051                 if (WhiteOnMove(currentMove)) {
11052                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11053                 } else {
11054                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11055                 }
11056                 break;
11057
11058               case MT_STALEMATE:
11059                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11060                 break;
11061             }
11062
11063             break;
11064
11065           case CMAIL_RESIGN:
11066             if (WhiteOnMove(currentMove)) {
11067                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11068             } else {
11069                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11070             }
11071             break;
11072
11073           case CMAIL_ACCEPT:
11074             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11075             break;
11076
11077           default:
11078             break;
11079         }
11080     }
11081
11082     return;
11083 }
11084
11085 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11086 int
11087 CmailLoadGame(f, gameNumber, title, useList)
11088      FILE *f;
11089      int gameNumber;
11090      char *title;
11091      int useList;
11092 {
11093     int retVal;
11094
11095     if (gameNumber > nCmailGames) {
11096         DisplayError(_("No more games in this message"), 0);
11097         return FALSE;
11098     }
11099     if (f == lastLoadGameFP) {
11100         int offset = gameNumber - lastLoadGameNumber;
11101         if (offset == 0) {
11102             cmailMsg[0] = NULLCHAR;
11103             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11104                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11105                 nCmailMovesRegistered--;
11106             }
11107             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11108             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11109                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11110             }
11111         } else {
11112             if (! RegisterMove()) return FALSE;
11113         }
11114     }
11115
11116     retVal = LoadGame(f, gameNumber, title, useList);
11117
11118     /* Make move registered during previous look at this game, if any */
11119     MakeRegisteredMove();
11120
11121     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11122         commentList[currentMove]
11123           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11124         DisplayComment(currentMove - 1, commentList[currentMove]);
11125     }
11126
11127     return retVal;
11128 }
11129
11130 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11131 int
11132 ReloadGame(offset)
11133      int offset;
11134 {
11135     int gameNumber = lastLoadGameNumber + offset;
11136     if (lastLoadGameFP == NULL) {
11137         DisplayError(_("No game has been loaded yet"), 0);
11138         return FALSE;
11139     }
11140     if (gameNumber <= 0) {
11141         DisplayError(_("Can't back up any further"), 0);
11142         return FALSE;
11143     }
11144     if (cmailMsgLoaded) {
11145         return CmailLoadGame(lastLoadGameFP, gameNumber,
11146                              lastLoadGameTitle, lastLoadGameUseList);
11147     } else {
11148         return LoadGame(lastLoadGameFP, gameNumber,
11149                         lastLoadGameTitle, lastLoadGameUseList);
11150     }
11151 }
11152
11153 int keys[EmptySquare+1];
11154
11155 int
11156 PositionMatches(Board b1, Board b2)
11157 {
11158     int r, f, sum=0;
11159     switch(appData.searchMode) {
11160         case 1: return CompareWithRights(b1, b2);
11161         case 2:
11162             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11163                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11164             }
11165             return TRUE;
11166         case 3:
11167             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11168               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11169                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11170             }
11171             return sum==0;
11172         case 4:
11173             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11174                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11175             }
11176             return sum==0;
11177     }
11178     return TRUE;
11179 }
11180
11181 #define Q_PROMO  4
11182 #define Q_EP     3
11183 #define Q_BCASTL 2
11184 #define Q_WCASTL 1
11185
11186 int pieceList[256], quickBoard[256];
11187 ChessSquare pieceType[256] = { EmptySquare };
11188 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11189 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11190 int soughtTotal, turn;
11191 Boolean epOK, flipSearch;
11192
11193 typedef struct {
11194     unsigned char piece, to;
11195 } Move;
11196
11197 #define DSIZE (250000)
11198
11199 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11200 Move *moveDatabase = initialSpace;
11201 unsigned int movePtr, dataSize = DSIZE;
11202
11203 int MakePieceList(Board board, int *counts)
11204 {
11205     int r, f, n=Q_PROMO, total=0;
11206     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11207     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11208         int sq = f + (r<<4);
11209         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11210             quickBoard[sq] = ++n;
11211             pieceList[n] = sq;
11212             pieceType[n] = board[r][f];
11213             counts[board[r][f]]++;
11214             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11215             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11216             total++;
11217         }
11218     }
11219     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11220     return total;
11221 }
11222
11223 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11224 {
11225     int sq = fromX + (fromY<<4);
11226     int piece = quickBoard[sq];
11227     quickBoard[sq] = 0;
11228     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11229     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11230         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11231         moveDatabase[movePtr++].piece = Q_WCASTL;
11232         quickBoard[sq] = piece;
11233         piece = quickBoard[from]; quickBoard[from] = 0;
11234         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11235     } else
11236     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11237         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11238         moveDatabase[movePtr++].piece = Q_BCASTL;
11239         quickBoard[sq] = piece;
11240         piece = quickBoard[from]; quickBoard[from] = 0;
11241         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11242     } else
11243     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11244         quickBoard[(fromY<<4)+toX] = 0;
11245         moveDatabase[movePtr].piece = Q_EP;
11246         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11247         moveDatabase[movePtr].to = sq;
11248     } else
11249     if(promoPiece != pieceType[piece]) {
11250         moveDatabase[movePtr++].piece = Q_PROMO;
11251         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11252     }
11253     moveDatabase[movePtr].piece = piece;
11254     quickBoard[sq] = piece;
11255     movePtr++;
11256 }
11257
11258 int PackGame(Board board)
11259 {
11260     Move *newSpace = NULL;
11261     moveDatabase[movePtr].piece = 0; // terminate previous game
11262     if(movePtr > dataSize) {
11263         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11264         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11265         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11266         if(newSpace) {
11267             int i;
11268             Move *p = moveDatabase, *q = newSpace;
11269             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11270             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11271             moveDatabase = newSpace;
11272         } else { // calloc failed, we must be out of memory. Too bad...
11273             dataSize = 0; // prevent calloc events for all subsequent games
11274             return 0;     // and signal this one isn't cached
11275         }
11276     }
11277     movePtr++;
11278     MakePieceList(board, counts);
11279     return movePtr;
11280 }
11281
11282 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11283 {   // compare according to search mode
11284     int r, f;
11285     switch(appData.searchMode)
11286     {
11287       case 1: // exact position match
11288         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11289         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11290             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11291         }
11292         break;
11293       case 2: // can have extra material on empty squares
11294         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11295             if(board[r][f] == EmptySquare) continue;
11296             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11297         }
11298         break;
11299       case 3: // material with exact Pawn structure
11300         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11301             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11302             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11303         } // fall through to material comparison
11304       case 4: // exact material
11305         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11306         break;
11307       case 6: // material range with given imbalance
11308         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11309         // fall through to range comparison
11310       case 5: // material range
11311         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11312     }
11313     return TRUE;
11314 }
11315
11316 int QuickScan(Board board, Move *move)
11317 {   // reconstruct game,and compare all positions in it
11318     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11319     do {
11320         int piece = move->piece;
11321         int to = move->to, from = pieceList[piece];
11322         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11323           if(!piece) return -1;
11324           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11325             piece = (++move)->piece;
11326             from = pieceList[piece];
11327             counts[pieceType[piece]]--;
11328             pieceType[piece] = (ChessSquare) move->to;
11329             counts[move->to]++;
11330           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11331             counts[pieceType[quickBoard[to]]]--;
11332             quickBoard[to] = 0; total--;
11333             move++;
11334             continue;
11335           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11336             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11337             from  = pieceList[piece]; // so this must be King
11338             quickBoard[from] = 0;
11339             quickBoard[to] = piece;
11340             pieceList[piece] = to;
11341             move++;
11342             continue;
11343           }
11344         }
11345         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11346         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11347         quickBoard[from] = 0;
11348         quickBoard[to] = piece;
11349         pieceList[piece] = to;
11350         cnt++; turn ^= 3;
11351         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11352            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11353            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11354                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11355           ) {
11356             static int lastCounts[EmptySquare+1];
11357             int i;
11358             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11359             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11360         } else stretch = 0;
11361         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11362         move++;
11363     } while(1);
11364 }
11365
11366 void InitSearch()
11367 {
11368     int r, f;
11369     flipSearch = FALSE;
11370     CopyBoard(soughtBoard, boards[currentMove]);
11371     soughtTotal = MakePieceList(soughtBoard, maxSought);
11372     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11373     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11374     CopyBoard(reverseBoard, boards[currentMove]);
11375     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11376         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11377         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11378         reverseBoard[r][f] = piece;
11379     }
11380     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11381     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11382     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11383                  || (boards[currentMove][CASTLING][2] == NoRights || 
11384                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11385                  && (boards[currentMove][CASTLING][5] == NoRights || 
11386                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11387       ) {
11388         flipSearch = TRUE;
11389         CopyBoard(flipBoard, soughtBoard);
11390         CopyBoard(rotateBoard, reverseBoard);
11391         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11392             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11393             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11394         }
11395     }
11396     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11397     if(appData.searchMode >= 5) {
11398         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11399         MakePieceList(soughtBoard, minSought);
11400         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11401     }
11402     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11403         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11404 }
11405
11406 GameInfo dummyInfo;
11407
11408 int GameContainsPosition(FILE *f, ListGame *lg)
11409 {
11410     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11411     int fromX, fromY, toX, toY;
11412     char promoChar;
11413     static int initDone=FALSE;
11414
11415     // weed out games based on numerical tag comparison
11416     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11417     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11418     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11419     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11420     if(!initDone) {
11421         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11422         initDone = TRUE;
11423     }
11424     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11425     else CopyBoard(boards[scratch], initialPosition); // default start position
11426     if(lg->moves) {
11427         turn = btm + 1;
11428         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11429         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11430     }
11431     if(btm) plyNr++;
11432     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11433     fseek(f, lg->offset, 0);
11434     yynewfile(f);
11435     while(1) {
11436         yyboardindex = scratch;
11437         quickFlag = plyNr+1;
11438         next = Myylex();
11439         quickFlag = 0;
11440         switch(next) {
11441             case PGNTag:
11442                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11443             default:
11444                 continue;
11445
11446             case XBoardGame:
11447             case GNUChessGame:
11448                 if(plyNr) return -1; // after we have seen moves, this is for new game
11449               continue;
11450
11451             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11452             case ImpossibleMove:
11453             case WhiteWins: // game ends here with these four
11454             case BlackWins:
11455             case GameIsDrawn:
11456             case GameUnfinished:
11457                 return -1;
11458
11459             case IllegalMove:
11460                 if(appData.testLegality) return -1;
11461             case WhiteCapturesEnPassant:
11462             case BlackCapturesEnPassant:
11463             case WhitePromotion:
11464             case BlackPromotion:
11465             case WhiteNonPromotion:
11466             case BlackNonPromotion:
11467             case NormalMove:
11468             case WhiteKingSideCastle:
11469             case WhiteQueenSideCastle:
11470             case BlackKingSideCastle:
11471             case BlackQueenSideCastle:
11472             case WhiteKingSideCastleWild:
11473             case WhiteQueenSideCastleWild:
11474             case BlackKingSideCastleWild:
11475             case BlackQueenSideCastleWild:
11476             case WhiteHSideCastleFR:
11477             case WhiteASideCastleFR:
11478             case BlackHSideCastleFR:
11479             case BlackASideCastleFR:
11480                 fromX = currentMoveString[0] - AAA;
11481                 fromY = currentMoveString[1] - ONE;
11482                 toX = currentMoveString[2] - AAA;
11483                 toY = currentMoveString[3] - ONE;
11484                 promoChar = currentMoveString[4];
11485                 break;
11486             case WhiteDrop:
11487             case BlackDrop:
11488                 fromX = next == WhiteDrop ?
11489                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11490                   (int) CharToPiece(ToLower(currentMoveString[0]));
11491                 fromY = DROP_RANK;
11492                 toX = currentMoveString[2] - AAA;
11493                 toY = currentMoveString[3] - ONE;
11494                 promoChar = 0;
11495                 break;
11496         }
11497         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11498         plyNr++;
11499         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11500         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11501         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11502         if(appData.findMirror) {
11503             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11504             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11505         }
11506     }
11507 }
11508
11509 /* Load the nth game from open file f */
11510 int
11511 LoadGame(f, gameNumber, title, useList)
11512      FILE *f;
11513      int gameNumber;
11514      char *title;
11515      int useList;
11516 {
11517     ChessMove cm;
11518     char buf[MSG_SIZ];
11519     int gn = gameNumber;
11520     ListGame *lg = NULL;
11521     int numPGNTags = 0;
11522     int err, pos = -1;
11523     GameMode oldGameMode;
11524     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11525
11526     if (appData.debugMode)
11527         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11528
11529     if (gameMode == Training )
11530         SetTrainingModeOff();
11531
11532     oldGameMode = gameMode;
11533     if (gameMode != BeginningOfGame) {
11534       Reset(FALSE, TRUE);
11535     }
11536
11537     gameFileFP = f;
11538     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11539         fclose(lastLoadGameFP);
11540     }
11541
11542     if (useList) {
11543         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11544
11545         if (lg) {
11546             fseek(f, lg->offset, 0);
11547             GameListHighlight(gameNumber);
11548             pos = lg->position;
11549             gn = 1;
11550         }
11551         else {
11552             DisplayError(_("Game number out of range"), 0);
11553             return FALSE;
11554         }
11555     } else {
11556         GameListDestroy();
11557         if (fseek(f, 0, 0) == -1) {
11558             if (f == lastLoadGameFP ?
11559                 gameNumber == lastLoadGameNumber + 1 :
11560                 gameNumber == 1) {
11561                 gn = 1;
11562             } else {
11563                 DisplayError(_("Can't seek on game file"), 0);
11564                 return FALSE;
11565             }
11566         }
11567     }
11568     lastLoadGameFP = f;
11569     lastLoadGameNumber = gameNumber;
11570     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11571     lastLoadGameUseList = useList;
11572
11573     yynewfile(f);
11574
11575     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11576       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11577                 lg->gameInfo.black);
11578             DisplayTitle(buf);
11579     } else if (*title != NULLCHAR) {
11580         if (gameNumber > 1) {
11581           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11582             DisplayTitle(buf);
11583         } else {
11584             DisplayTitle(title);
11585         }
11586     }
11587
11588     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11589         gameMode = PlayFromGameFile;
11590         ModeHighlight();
11591     }
11592
11593     currentMove = forwardMostMove = backwardMostMove = 0;
11594     CopyBoard(boards[0], initialPosition);
11595     StopClocks();
11596
11597     /*
11598      * Skip the first gn-1 games in the file.
11599      * Also skip over anything that precedes an identifiable
11600      * start of game marker, to avoid being confused by
11601      * garbage at the start of the file.  Currently
11602      * recognized start of game markers are the move number "1",
11603      * the pattern "gnuchess .* game", the pattern
11604      * "^[#;%] [^ ]* game file", and a PGN tag block.
11605      * A game that starts with one of the latter two patterns
11606      * will also have a move number 1, possibly
11607      * following a position diagram.
11608      * 5-4-02: Let's try being more lenient and allowing a game to
11609      * start with an unnumbered move.  Does that break anything?
11610      */
11611     cm = lastLoadGameStart = EndOfFile;
11612     while (gn > 0) {
11613         yyboardindex = forwardMostMove;
11614         cm = (ChessMove) Myylex();
11615         switch (cm) {
11616           case EndOfFile:
11617             if (cmailMsgLoaded) {
11618                 nCmailGames = CMAIL_MAX_GAMES - gn;
11619             } else {
11620                 Reset(TRUE, TRUE);
11621                 DisplayError(_("Game not found in file"), 0);
11622             }
11623             return FALSE;
11624
11625           case GNUChessGame:
11626           case XBoardGame:
11627             gn--;
11628             lastLoadGameStart = cm;
11629             break;
11630
11631           case MoveNumberOne:
11632             switch (lastLoadGameStart) {
11633               case GNUChessGame:
11634               case XBoardGame:
11635               case PGNTag:
11636                 break;
11637               case MoveNumberOne:
11638               case EndOfFile:
11639                 gn--;           /* count this game */
11640                 lastLoadGameStart = cm;
11641                 break;
11642               default:
11643                 /* impossible */
11644                 break;
11645             }
11646             break;
11647
11648           case PGNTag:
11649             switch (lastLoadGameStart) {
11650               case GNUChessGame:
11651               case PGNTag:
11652               case MoveNumberOne:
11653               case EndOfFile:
11654                 gn--;           /* count this game */
11655                 lastLoadGameStart = cm;
11656                 break;
11657               case XBoardGame:
11658                 lastLoadGameStart = cm; /* game counted already */
11659                 break;
11660               default:
11661                 /* impossible */
11662                 break;
11663             }
11664             if (gn > 0) {
11665                 do {
11666                     yyboardindex = forwardMostMove;
11667                     cm = (ChessMove) Myylex();
11668                 } while (cm == PGNTag || cm == Comment);
11669             }
11670             break;
11671
11672           case WhiteWins:
11673           case BlackWins:
11674           case GameIsDrawn:
11675             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11676                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11677                     != CMAIL_OLD_RESULT) {
11678                     nCmailResults ++ ;
11679                     cmailResult[  CMAIL_MAX_GAMES
11680                                 - gn - 1] = CMAIL_OLD_RESULT;
11681                 }
11682             }
11683             break;
11684
11685           case NormalMove:
11686             /* Only a NormalMove can be at the start of a game
11687              * without a position diagram. */
11688             if (lastLoadGameStart == EndOfFile ) {
11689               gn--;
11690               lastLoadGameStart = MoveNumberOne;
11691             }
11692             break;
11693
11694           default:
11695             break;
11696         }
11697     }
11698
11699     if (appData.debugMode)
11700       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11701
11702     if (cm == XBoardGame) {
11703         /* Skip any header junk before position diagram and/or move 1 */
11704         for (;;) {
11705             yyboardindex = forwardMostMove;
11706             cm = (ChessMove) Myylex();
11707
11708             if (cm == EndOfFile ||
11709                 cm == GNUChessGame || cm == XBoardGame) {
11710                 /* Empty game; pretend end-of-file and handle later */
11711                 cm = EndOfFile;
11712                 break;
11713             }
11714
11715             if (cm == MoveNumberOne || cm == PositionDiagram ||
11716                 cm == PGNTag || cm == Comment)
11717               break;
11718         }
11719     } else if (cm == GNUChessGame) {
11720         if (gameInfo.event != NULL) {
11721             free(gameInfo.event);
11722         }
11723         gameInfo.event = StrSave(yy_text);
11724     }
11725
11726     startedFromSetupPosition = FALSE;
11727     while (cm == PGNTag) {
11728         if (appData.debugMode)
11729           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11730         err = ParsePGNTag(yy_text, &gameInfo);
11731         if (!err) numPGNTags++;
11732
11733         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11734         if(gameInfo.variant != oldVariant) {
11735             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11736             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11737             InitPosition(TRUE);
11738             oldVariant = gameInfo.variant;
11739             if (appData.debugMode)
11740               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11741         }
11742
11743
11744         if (gameInfo.fen != NULL) {
11745           Board initial_position;
11746           startedFromSetupPosition = TRUE;
11747           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11748             Reset(TRUE, TRUE);
11749             DisplayError(_("Bad FEN position in file"), 0);
11750             return FALSE;
11751           }
11752           CopyBoard(boards[0], initial_position);
11753           if (blackPlaysFirst) {
11754             currentMove = forwardMostMove = backwardMostMove = 1;
11755             CopyBoard(boards[1], initial_position);
11756             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11757             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11758             timeRemaining[0][1] = whiteTimeRemaining;
11759             timeRemaining[1][1] = blackTimeRemaining;
11760             if (commentList[0] != NULL) {
11761               commentList[1] = commentList[0];
11762               commentList[0] = NULL;
11763             }
11764           } else {
11765             currentMove = forwardMostMove = backwardMostMove = 0;
11766           }
11767           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11768           {   int i;
11769               initialRulePlies = FENrulePlies;
11770               for( i=0; i< nrCastlingRights; i++ )
11771                   initialRights[i] = initial_position[CASTLING][i];
11772           }
11773           yyboardindex = forwardMostMove;
11774           free(gameInfo.fen);
11775           gameInfo.fen = NULL;
11776         }
11777
11778         yyboardindex = forwardMostMove;
11779         cm = (ChessMove) Myylex();
11780
11781         /* Handle comments interspersed among the tags */
11782         while (cm == Comment) {
11783             char *p;
11784             if (appData.debugMode)
11785               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11786             p = yy_text;
11787             AppendComment(currentMove, p, FALSE);
11788             yyboardindex = forwardMostMove;
11789             cm = (ChessMove) Myylex();
11790         }
11791     }
11792
11793     /* don't rely on existence of Event tag since if game was
11794      * pasted from clipboard the Event tag may not exist
11795      */
11796     if (numPGNTags > 0){
11797         char *tags;
11798         if (gameInfo.variant == VariantNormal) {
11799           VariantClass v = StringToVariant(gameInfo.event);
11800           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11801           if(v < VariantShogi) gameInfo.variant = v;
11802         }
11803         if (!matchMode) {
11804           if( appData.autoDisplayTags ) {
11805             tags = PGNTags(&gameInfo);
11806             TagsPopUp(tags, CmailMsg());
11807             free(tags);
11808           }
11809         }
11810     } else {
11811         /* Make something up, but don't display it now */
11812         SetGameInfo();
11813         TagsPopDown();
11814     }
11815
11816     if (cm == PositionDiagram) {
11817         int i, j;
11818         char *p;
11819         Board initial_position;
11820
11821         if (appData.debugMode)
11822           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11823
11824         if (!startedFromSetupPosition) {
11825             p = yy_text;
11826             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11827               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11828                 switch (*p) {
11829                   case '{':
11830                   case '[':
11831                   case '-':
11832                   case ' ':
11833                   case '\t':
11834                   case '\n':
11835                   case '\r':
11836                     break;
11837                   default:
11838                     initial_position[i][j++] = CharToPiece(*p);
11839                     break;
11840                 }
11841             while (*p == ' ' || *p == '\t' ||
11842                    *p == '\n' || *p == '\r') p++;
11843
11844             if (strncmp(p, "black", strlen("black"))==0)
11845               blackPlaysFirst = TRUE;
11846             else
11847               blackPlaysFirst = FALSE;
11848             startedFromSetupPosition = TRUE;
11849
11850             CopyBoard(boards[0], initial_position);
11851             if (blackPlaysFirst) {
11852                 currentMove = forwardMostMove = backwardMostMove = 1;
11853                 CopyBoard(boards[1], initial_position);
11854                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11855                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11856                 timeRemaining[0][1] = whiteTimeRemaining;
11857                 timeRemaining[1][1] = blackTimeRemaining;
11858                 if (commentList[0] != NULL) {
11859                     commentList[1] = commentList[0];
11860                     commentList[0] = NULL;
11861                 }
11862             } else {
11863                 currentMove = forwardMostMove = backwardMostMove = 0;
11864             }
11865         }
11866         yyboardindex = forwardMostMove;
11867         cm = (ChessMove) Myylex();
11868     }
11869
11870     if (first.pr == NoProc) {
11871         StartChessProgram(&first);
11872     }
11873     InitChessProgram(&first, FALSE);
11874     SendToProgram("force\n", &first);
11875     if (startedFromSetupPosition) {
11876         SendBoard(&first, forwardMostMove);
11877     if (appData.debugMode) {
11878         fprintf(debugFP, "Load Game\n");
11879     }
11880         DisplayBothClocks();
11881     }
11882
11883     /* [HGM] server: flag to write setup moves in broadcast file as one */
11884     loadFlag = appData.suppressLoadMoves;
11885
11886     while (cm == Comment) {
11887         char *p;
11888         if (appData.debugMode)
11889           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11890         p = yy_text;
11891         AppendComment(currentMove, p, FALSE);
11892         yyboardindex = forwardMostMove;
11893         cm = (ChessMove) Myylex();
11894     }
11895
11896     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11897         cm == WhiteWins || cm == BlackWins ||
11898         cm == GameIsDrawn || cm == GameUnfinished) {
11899         DisplayMessage("", _("No moves in game"));
11900         if (cmailMsgLoaded) {
11901             if (appData.debugMode)
11902               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11903             ClearHighlights();
11904             flipView = FALSE;
11905         }
11906         DrawPosition(FALSE, boards[currentMove]);
11907         DisplayBothClocks();
11908         gameMode = EditGame;
11909         ModeHighlight();
11910         gameFileFP = NULL;
11911         cmailOldMove = 0;
11912         return TRUE;
11913     }
11914
11915     // [HGM] PV info: routine tests if comment empty
11916     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11917         DisplayComment(currentMove - 1, commentList[currentMove]);
11918     }
11919     if (!matchMode && appData.timeDelay != 0)
11920       DrawPosition(FALSE, boards[currentMove]);
11921
11922     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11923       programStats.ok_to_send = 1;
11924     }
11925
11926     /* if the first token after the PGN tags is a move
11927      * and not move number 1, retrieve it from the parser
11928      */
11929     if (cm != MoveNumberOne)
11930         LoadGameOneMove(cm);
11931
11932     /* load the remaining moves from the file */
11933     while (LoadGameOneMove(EndOfFile)) {
11934       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11935       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11936     }
11937
11938     /* rewind to the start of the game */
11939     currentMove = backwardMostMove;
11940
11941     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11942
11943     if (oldGameMode == AnalyzeFile ||
11944         oldGameMode == AnalyzeMode) {
11945       AnalyzeFileEvent();
11946     }
11947
11948     if (!matchMode && pos >= 0) {
11949         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11950     } else
11951     if (matchMode || appData.timeDelay == 0) {
11952       ToEndEvent();
11953     } else if (appData.timeDelay > 0) {
11954       AutoPlayGameLoop();
11955     }
11956
11957     if (appData.debugMode)
11958         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11959
11960     loadFlag = 0; /* [HGM] true game starts */
11961     return TRUE;
11962 }
11963
11964 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11965 int
11966 ReloadPosition(offset)
11967      int offset;
11968 {
11969     int positionNumber = lastLoadPositionNumber + offset;
11970     if (lastLoadPositionFP == NULL) {
11971         DisplayError(_("No position has been loaded yet"), 0);
11972         return FALSE;
11973     }
11974     if (positionNumber <= 0) {
11975         DisplayError(_("Can't back up any further"), 0);
11976         return FALSE;
11977     }
11978     return LoadPosition(lastLoadPositionFP, positionNumber,
11979                         lastLoadPositionTitle);
11980 }
11981
11982 /* Load the nth position from the given file */
11983 int
11984 LoadPositionFromFile(filename, n, title)
11985      char *filename;
11986      int n;
11987      char *title;
11988 {
11989     FILE *f;
11990     char buf[MSG_SIZ];
11991
11992     if (strcmp(filename, "-") == 0) {
11993         return LoadPosition(stdin, n, "stdin");
11994     } else {
11995         f = fopen(filename, "rb");
11996         if (f == NULL) {
11997             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11998             DisplayError(buf, errno);
11999             return FALSE;
12000         } else {
12001             return LoadPosition(f, n, title);
12002         }
12003     }
12004 }
12005
12006 /* Load the nth position from the given open file, and close it */
12007 int
12008 LoadPosition(f, positionNumber, title)
12009      FILE *f;
12010      int positionNumber;
12011      char *title;
12012 {
12013     char *p, line[MSG_SIZ];
12014     Board initial_position;
12015     int i, j, fenMode, pn;
12016
12017     if (gameMode == Training )
12018         SetTrainingModeOff();
12019
12020     if (gameMode != BeginningOfGame) {
12021         Reset(FALSE, TRUE);
12022     }
12023     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12024         fclose(lastLoadPositionFP);
12025     }
12026     if (positionNumber == 0) positionNumber = 1;
12027     lastLoadPositionFP = f;
12028     lastLoadPositionNumber = positionNumber;
12029     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12030     if (first.pr == NoProc && !appData.noChessProgram) {
12031       StartChessProgram(&first);
12032       InitChessProgram(&first, FALSE);
12033     }
12034     pn = positionNumber;
12035     if (positionNumber < 0) {
12036         /* Negative position number means to seek to that byte offset */
12037         if (fseek(f, -positionNumber, 0) == -1) {
12038             DisplayError(_("Can't seek on position file"), 0);
12039             return FALSE;
12040         };
12041         pn = 1;
12042     } else {
12043         if (fseek(f, 0, 0) == -1) {
12044             if (f == lastLoadPositionFP ?
12045                 positionNumber == lastLoadPositionNumber + 1 :
12046                 positionNumber == 1) {
12047                 pn = 1;
12048             } else {
12049                 DisplayError(_("Can't seek on position file"), 0);
12050                 return FALSE;
12051             }
12052         }
12053     }
12054     /* See if this file is FEN or old-style xboard */
12055     if (fgets(line, MSG_SIZ, f) == NULL) {
12056         DisplayError(_("Position not found in file"), 0);
12057         return FALSE;
12058     }
12059     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12060     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12061
12062     if (pn >= 2) {
12063         if (fenMode || line[0] == '#') pn--;
12064         while (pn > 0) {
12065             /* skip positions before number pn */
12066             if (fgets(line, MSG_SIZ, f) == NULL) {
12067                 Reset(TRUE, TRUE);
12068                 DisplayError(_("Position not found in file"), 0);
12069                 return FALSE;
12070             }
12071             if (fenMode || line[0] == '#') pn--;
12072         }
12073     }
12074
12075     if (fenMode) {
12076         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12077             DisplayError(_("Bad FEN position in file"), 0);
12078             return FALSE;
12079         }
12080     } else {
12081         (void) fgets(line, MSG_SIZ, f);
12082         (void) fgets(line, MSG_SIZ, f);
12083
12084         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12085             (void) fgets(line, MSG_SIZ, f);
12086             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12087                 if (*p == ' ')
12088                   continue;
12089                 initial_position[i][j++] = CharToPiece(*p);
12090             }
12091         }
12092
12093         blackPlaysFirst = FALSE;
12094         if (!feof(f)) {
12095             (void) fgets(line, MSG_SIZ, f);
12096             if (strncmp(line, "black", strlen("black"))==0)
12097               blackPlaysFirst = TRUE;
12098         }
12099     }
12100     startedFromSetupPosition = TRUE;
12101
12102     CopyBoard(boards[0], initial_position);
12103     if (blackPlaysFirst) {
12104         currentMove = forwardMostMove = backwardMostMove = 1;
12105         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12106         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12107         CopyBoard(boards[1], initial_position);
12108         DisplayMessage("", _("Black to play"));
12109     } else {
12110         currentMove = forwardMostMove = backwardMostMove = 0;
12111         DisplayMessage("", _("White to play"));
12112     }
12113     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12114     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12115         SendToProgram("force\n", &first);
12116         SendBoard(&first, forwardMostMove);
12117     }
12118     if (appData.debugMode) {
12119 int i, j;
12120   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12121   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12122         fprintf(debugFP, "Load Position\n");
12123     }
12124
12125     if (positionNumber > 1) {
12126       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12127         DisplayTitle(line);
12128     } else {
12129         DisplayTitle(title);
12130     }
12131     gameMode = EditGame;
12132     ModeHighlight();
12133     ResetClocks();
12134     timeRemaining[0][1] = whiteTimeRemaining;
12135     timeRemaining[1][1] = blackTimeRemaining;
12136     DrawPosition(FALSE, boards[currentMove]);
12137
12138     return TRUE;
12139 }
12140
12141
12142 void
12143 CopyPlayerNameIntoFileName(dest, src)
12144      char **dest, *src;
12145 {
12146     while (*src != NULLCHAR && *src != ',') {
12147         if (*src == ' ') {
12148             *(*dest)++ = '_';
12149             src++;
12150         } else {
12151             *(*dest)++ = *src++;
12152         }
12153     }
12154 }
12155
12156 char *DefaultFileName(ext)
12157      char *ext;
12158 {
12159     static char def[MSG_SIZ];
12160     char *p;
12161
12162     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12163         p = def;
12164         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12165         *p++ = '-';
12166         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12167         *p++ = '.';
12168         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12169     } else {
12170         def[0] = NULLCHAR;
12171     }
12172     return def;
12173 }
12174
12175 /* Save the current game to the given file */
12176 int
12177 SaveGameToFile(filename, append)
12178      char *filename;
12179      int append;
12180 {
12181     FILE *f;
12182     char buf[MSG_SIZ];
12183     int result, i, t,tot=0;
12184
12185     if (strcmp(filename, "-") == 0) {
12186         return SaveGame(stdout, 0, NULL);
12187     } else {
12188         for(i=0; i<10; i++) { // upto 10 tries
12189              f = fopen(filename, append ? "a" : "w");
12190              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12191              if(f || errno != 13) break;
12192              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12193              tot += t;
12194         }
12195         if (f == NULL) {
12196             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12197             DisplayError(buf, errno);
12198             return FALSE;
12199         } else {
12200             safeStrCpy(buf, lastMsg, MSG_SIZ);
12201             DisplayMessage(_("Waiting for access to save file"), "");
12202             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12203             DisplayMessage(_("Saving game"), "");
12204             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
12205             result = SaveGame(f, 0, NULL);
12206             DisplayMessage(buf, "");
12207             return result;
12208         }
12209     }
12210 }
12211
12212 char *
12213 SavePart(str)
12214      char *str;
12215 {
12216     static char buf[MSG_SIZ];
12217     char *p;
12218
12219     p = strchr(str, ' ');
12220     if (p == NULL) return str;
12221     strncpy(buf, str, p - str);
12222     buf[p - str] = NULLCHAR;
12223     return buf;
12224 }
12225
12226 #define PGN_MAX_LINE 75
12227
12228 #define PGN_SIDE_WHITE  0
12229 #define PGN_SIDE_BLACK  1
12230
12231 /* [AS] */
12232 static int FindFirstMoveOutOfBook( int side )
12233 {
12234     int result = -1;
12235
12236     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12237         int index = backwardMostMove;
12238         int has_book_hit = 0;
12239
12240         if( (index % 2) != side ) {
12241             index++;
12242         }
12243
12244         while( index < forwardMostMove ) {
12245             /* Check to see if engine is in book */
12246             int depth = pvInfoList[index].depth;
12247             int score = pvInfoList[index].score;
12248             int in_book = 0;
12249
12250             if( depth <= 2 ) {
12251                 in_book = 1;
12252             }
12253             else if( score == 0 && depth == 63 ) {
12254                 in_book = 1; /* Zappa */
12255             }
12256             else if( score == 2 && depth == 99 ) {
12257                 in_book = 1; /* Abrok */
12258             }
12259
12260             has_book_hit += in_book;
12261
12262             if( ! in_book ) {
12263                 result = index;
12264
12265                 break;
12266             }
12267
12268             index += 2;
12269         }
12270     }
12271
12272     return result;
12273 }
12274
12275 /* [AS] */
12276 void GetOutOfBookInfo( char * buf )
12277 {
12278     int oob[2];
12279     int i;
12280     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12281
12282     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12283     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12284
12285     *buf = '\0';
12286
12287     if( oob[0] >= 0 || oob[1] >= 0 ) {
12288         for( i=0; i<2; i++ ) {
12289             int idx = oob[i];
12290
12291             if( idx >= 0 ) {
12292                 if( i > 0 && oob[0] >= 0 ) {
12293                     strcat( buf, "   " );
12294                 }
12295
12296                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12297                 sprintf( buf+strlen(buf), "%s%.2f",
12298                     pvInfoList[idx].score >= 0 ? "+" : "",
12299                     pvInfoList[idx].score / 100.0 );
12300             }
12301         }
12302     }
12303 }
12304
12305 /* Save game in PGN style and close the file */
12306 int
12307 SaveGamePGN(f)
12308      FILE *f;
12309 {
12310     int i, offset, linelen, newblock;
12311     time_t tm;
12312 //    char *movetext;
12313     char numtext[32];
12314     int movelen, numlen, blank;
12315     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12316
12317     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12318
12319     tm = time((time_t *) NULL);
12320
12321     PrintPGNTags(f, &gameInfo);
12322
12323     if (backwardMostMove > 0 || startedFromSetupPosition) {
12324         char *fen = PositionToFEN(backwardMostMove, NULL);
12325         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12326         fprintf(f, "\n{--------------\n");
12327         PrintPosition(f, backwardMostMove);
12328         fprintf(f, "--------------}\n");
12329         free(fen);
12330     }
12331     else {
12332         /* [AS] Out of book annotation */
12333         if( appData.saveOutOfBookInfo ) {
12334             char buf[64];
12335
12336             GetOutOfBookInfo( buf );
12337
12338             if( buf[0] != '\0' ) {
12339                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12340             }
12341         }
12342
12343         fprintf(f, "\n");
12344     }
12345
12346     i = backwardMostMove;
12347     linelen = 0;
12348     newblock = TRUE;
12349
12350     while (i < forwardMostMove) {
12351         /* Print comments preceding this move */
12352         if (commentList[i] != NULL) {
12353             if (linelen > 0) fprintf(f, "\n");
12354             fprintf(f, "%s", commentList[i]);
12355             linelen = 0;
12356             newblock = TRUE;
12357         }
12358
12359         /* Format move number */
12360         if ((i % 2) == 0)
12361           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12362         else
12363           if (newblock)
12364             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12365           else
12366             numtext[0] = NULLCHAR;
12367
12368         numlen = strlen(numtext);
12369         newblock = FALSE;
12370
12371         /* Print move number */
12372         blank = linelen > 0 && numlen > 0;
12373         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12374             fprintf(f, "\n");
12375             linelen = 0;
12376             blank = 0;
12377         }
12378         if (blank) {
12379             fprintf(f, " ");
12380             linelen++;
12381         }
12382         fprintf(f, "%s", numtext);
12383         linelen += numlen;
12384
12385         /* Get move */
12386         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12387         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12388
12389         /* Print move */
12390         blank = linelen > 0 && movelen > 0;
12391         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12392             fprintf(f, "\n");
12393             linelen = 0;
12394             blank = 0;
12395         }
12396         if (blank) {
12397             fprintf(f, " ");
12398             linelen++;
12399         }
12400         fprintf(f, "%s", move_buffer);
12401         linelen += movelen;
12402
12403         /* [AS] Add PV info if present */
12404         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12405             /* [HGM] add time */
12406             char buf[MSG_SIZ]; int seconds;
12407
12408             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12409
12410             if( seconds <= 0)
12411               buf[0] = 0;
12412             else
12413               if( seconds < 30 )
12414                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12415               else
12416                 {
12417                   seconds = (seconds + 4)/10; // round to full seconds
12418                   if( seconds < 60 )
12419                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12420                   else
12421                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12422                 }
12423
12424             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12425                       pvInfoList[i].score >= 0 ? "+" : "",
12426                       pvInfoList[i].score / 100.0,
12427                       pvInfoList[i].depth,
12428                       buf );
12429
12430             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12431
12432             /* Print score/depth */
12433             blank = linelen > 0 && movelen > 0;
12434             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12435                 fprintf(f, "\n");
12436                 linelen = 0;
12437                 blank = 0;
12438             }
12439             if (blank) {
12440                 fprintf(f, " ");
12441                 linelen++;
12442             }
12443             fprintf(f, "%s", move_buffer);
12444             linelen += movelen;
12445         }
12446
12447         i++;
12448     }
12449
12450     /* Start a new line */
12451     if (linelen > 0) fprintf(f, "\n");
12452
12453     /* Print comments after last move */
12454     if (commentList[i] != NULL) {
12455         fprintf(f, "%s\n", commentList[i]);
12456     }
12457
12458     /* Print result */
12459     if (gameInfo.resultDetails != NULL &&
12460         gameInfo.resultDetails[0] != NULLCHAR) {
12461         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12462                 PGNResult(gameInfo.result));
12463     } else {
12464         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12465     }
12466
12467     fclose(f);
12468     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12469     return TRUE;
12470 }
12471
12472 /* Save game in old style and close the file */
12473 int
12474 SaveGameOldStyle(f)
12475      FILE *f;
12476 {
12477     int i, offset;
12478     time_t tm;
12479
12480     tm = time((time_t *) NULL);
12481
12482     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12483     PrintOpponents(f);
12484
12485     if (backwardMostMove > 0 || startedFromSetupPosition) {
12486         fprintf(f, "\n[--------------\n");
12487         PrintPosition(f, backwardMostMove);
12488         fprintf(f, "--------------]\n");
12489     } else {
12490         fprintf(f, "\n");
12491     }
12492
12493     i = backwardMostMove;
12494     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12495
12496     while (i < forwardMostMove) {
12497         if (commentList[i] != NULL) {
12498             fprintf(f, "[%s]\n", commentList[i]);
12499         }
12500
12501         if ((i % 2) == 1) {
12502             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12503             i++;
12504         } else {
12505             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12506             i++;
12507             if (commentList[i] != NULL) {
12508                 fprintf(f, "\n");
12509                 continue;
12510             }
12511             if (i >= forwardMostMove) {
12512                 fprintf(f, "\n");
12513                 break;
12514             }
12515             fprintf(f, "%s\n", parseList[i]);
12516             i++;
12517         }
12518     }
12519
12520     if (commentList[i] != NULL) {
12521         fprintf(f, "[%s]\n", commentList[i]);
12522     }
12523
12524     /* This isn't really the old style, but it's close enough */
12525     if (gameInfo.resultDetails != NULL &&
12526         gameInfo.resultDetails[0] != NULLCHAR) {
12527         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12528                 gameInfo.resultDetails);
12529     } else {
12530         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12531     }
12532
12533     fclose(f);
12534     return TRUE;
12535 }
12536
12537 /* Save the current game to open file f and close the file */
12538 int
12539 SaveGame(f, dummy, dummy2)
12540      FILE *f;
12541      int dummy;
12542      char *dummy2;
12543 {
12544     if (gameMode == EditPosition) EditPositionDone(TRUE);
12545     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12546     if (appData.oldSaveStyle)
12547       return SaveGameOldStyle(f);
12548     else
12549       return SaveGamePGN(f);
12550 }
12551
12552 /* Save the current position to the given file */
12553 int
12554 SavePositionToFile(filename)
12555      char *filename;
12556 {
12557     FILE *f;
12558     char buf[MSG_SIZ];
12559
12560     if (strcmp(filename, "-") == 0) {
12561         return SavePosition(stdout, 0, NULL);
12562     } else {
12563         f = fopen(filename, "a");
12564         if (f == NULL) {
12565             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12566             DisplayError(buf, errno);
12567             return FALSE;
12568         } else {
12569             safeStrCpy(buf, lastMsg, MSG_SIZ);
12570             DisplayMessage(_("Waiting for access to save file"), "");
12571             flock(fileno(f), LOCK_EX); // [HGM] lock
12572             DisplayMessage(_("Saving position"), "");
12573             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12574             SavePosition(f, 0, NULL);
12575             DisplayMessage(buf, "");
12576             return TRUE;
12577         }
12578     }
12579 }
12580
12581 /* Save the current position to the given open file and close the file */
12582 int
12583 SavePosition(f, dummy, dummy2)
12584      FILE *f;
12585      int dummy;
12586      char *dummy2;
12587 {
12588     time_t tm;
12589     char *fen;
12590
12591     if (gameMode == EditPosition) EditPositionDone(TRUE);
12592     if (appData.oldSaveStyle) {
12593         tm = time((time_t *) NULL);
12594
12595         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12596         PrintOpponents(f);
12597         fprintf(f, "[--------------\n");
12598         PrintPosition(f, currentMove);
12599         fprintf(f, "--------------]\n");
12600     } else {
12601         fen = PositionToFEN(currentMove, NULL);
12602         fprintf(f, "%s\n", fen);
12603         free(fen);
12604     }
12605     fclose(f);
12606     return TRUE;
12607 }
12608
12609 void
12610 ReloadCmailMsgEvent(unregister)
12611      int unregister;
12612 {
12613 #if !WIN32
12614     static char *inFilename = NULL;
12615     static char *outFilename;
12616     int i;
12617     struct stat inbuf, outbuf;
12618     int status;
12619
12620     /* Any registered moves are unregistered if unregister is set, */
12621     /* i.e. invoked by the signal handler */
12622     if (unregister) {
12623         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12624             cmailMoveRegistered[i] = FALSE;
12625             if (cmailCommentList[i] != NULL) {
12626                 free(cmailCommentList[i]);
12627                 cmailCommentList[i] = NULL;
12628             }
12629         }
12630         nCmailMovesRegistered = 0;
12631     }
12632
12633     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12634         cmailResult[i] = CMAIL_NOT_RESULT;
12635     }
12636     nCmailResults = 0;
12637
12638     if (inFilename == NULL) {
12639         /* Because the filenames are static they only get malloced once  */
12640         /* and they never get freed                                      */
12641         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12642         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12643
12644         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12645         sprintf(outFilename, "%s.out", appData.cmailGameName);
12646     }
12647
12648     status = stat(outFilename, &outbuf);
12649     if (status < 0) {
12650         cmailMailedMove = FALSE;
12651     } else {
12652         status = stat(inFilename, &inbuf);
12653         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12654     }
12655
12656     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12657        counts the games, notes how each one terminated, etc.
12658
12659        It would be nice to remove this kludge and instead gather all
12660        the information while building the game list.  (And to keep it
12661        in the game list nodes instead of having a bunch of fixed-size
12662        parallel arrays.)  Note this will require getting each game's
12663        termination from the PGN tags, as the game list builder does
12664        not process the game moves.  --mann
12665        */
12666     cmailMsgLoaded = TRUE;
12667     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12668
12669     /* Load first game in the file or popup game menu */
12670     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12671
12672 #endif /* !WIN32 */
12673     return;
12674 }
12675
12676 int
12677 RegisterMove()
12678 {
12679     FILE *f;
12680     char string[MSG_SIZ];
12681
12682     if (   cmailMailedMove
12683         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12684         return TRUE;            /* Allow free viewing  */
12685     }
12686
12687     /* Unregister move to ensure that we don't leave RegisterMove        */
12688     /* with the move registered when the conditions for registering no   */
12689     /* longer hold                                                       */
12690     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12691         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12692         nCmailMovesRegistered --;
12693
12694         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12695           {
12696               free(cmailCommentList[lastLoadGameNumber - 1]);
12697               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12698           }
12699     }
12700
12701     if (cmailOldMove == -1) {
12702         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12703         return FALSE;
12704     }
12705
12706     if (currentMove > cmailOldMove + 1) {
12707         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12708         return FALSE;
12709     }
12710
12711     if (currentMove < cmailOldMove) {
12712         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12713         return FALSE;
12714     }
12715
12716     if (forwardMostMove > currentMove) {
12717         /* Silently truncate extra moves */
12718         TruncateGame();
12719     }
12720
12721     if (   (currentMove == cmailOldMove + 1)
12722         || (   (currentMove == cmailOldMove)
12723             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12724                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12725         if (gameInfo.result != GameUnfinished) {
12726             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12727         }
12728
12729         if (commentList[currentMove] != NULL) {
12730             cmailCommentList[lastLoadGameNumber - 1]
12731               = StrSave(commentList[currentMove]);
12732         }
12733         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12734
12735         if (appData.debugMode)
12736           fprintf(debugFP, "Saving %s for game %d\n",
12737                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12738
12739         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12740
12741         f = fopen(string, "w");
12742         if (appData.oldSaveStyle) {
12743             SaveGameOldStyle(f); /* also closes the file */
12744
12745             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12746             f = fopen(string, "w");
12747             SavePosition(f, 0, NULL); /* also closes the file */
12748         } else {
12749             fprintf(f, "{--------------\n");
12750             PrintPosition(f, currentMove);
12751             fprintf(f, "--------------}\n\n");
12752
12753             SaveGame(f, 0, NULL); /* also closes the file*/
12754         }
12755
12756         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12757         nCmailMovesRegistered ++;
12758     } else if (nCmailGames == 1) {
12759         DisplayError(_("You have not made a move yet"), 0);
12760         return FALSE;
12761     }
12762
12763     return TRUE;
12764 }
12765
12766 void
12767 MailMoveEvent()
12768 {
12769 #if !WIN32
12770     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12771     FILE *commandOutput;
12772     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12773     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12774     int nBuffers;
12775     int i;
12776     int archived;
12777     char *arcDir;
12778
12779     if (! cmailMsgLoaded) {
12780         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12781         return;
12782     }
12783
12784     if (nCmailGames == nCmailResults) {
12785         DisplayError(_("No unfinished games"), 0);
12786         return;
12787     }
12788
12789 #if CMAIL_PROHIBIT_REMAIL
12790     if (cmailMailedMove) {
12791       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);
12792         DisplayError(msg, 0);
12793         return;
12794     }
12795 #endif
12796
12797     if (! (cmailMailedMove || RegisterMove())) return;
12798
12799     if (   cmailMailedMove
12800         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12801       snprintf(string, MSG_SIZ, partCommandString,
12802                appData.debugMode ? " -v" : "", appData.cmailGameName);
12803         commandOutput = popen(string, "r");
12804
12805         if (commandOutput == NULL) {
12806             DisplayError(_("Failed to invoke cmail"), 0);
12807         } else {
12808             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12809                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12810             }
12811             if (nBuffers > 1) {
12812                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12813                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12814                 nBytes = MSG_SIZ - 1;
12815             } else {
12816                 (void) memcpy(msg, buffer, nBytes);
12817             }
12818             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12819
12820             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12821                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12822
12823                 archived = TRUE;
12824                 for (i = 0; i < nCmailGames; i ++) {
12825                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12826                         archived = FALSE;
12827                     }
12828                 }
12829                 if (   archived
12830                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12831                         != NULL)) {
12832                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12833                            arcDir,
12834                            appData.cmailGameName,
12835                            gameInfo.date);
12836                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12837                     cmailMsgLoaded = FALSE;
12838                 }
12839             }
12840
12841             DisplayInformation(msg);
12842             pclose(commandOutput);
12843         }
12844     } else {
12845         if ((*cmailMsg) != '\0') {
12846             DisplayInformation(cmailMsg);
12847         }
12848     }
12849
12850     return;
12851 #endif /* !WIN32 */
12852 }
12853
12854 char *
12855 CmailMsg()
12856 {
12857 #if WIN32
12858     return NULL;
12859 #else
12860     int  prependComma = 0;
12861     char number[5];
12862     char string[MSG_SIZ];       /* Space for game-list */
12863     int  i;
12864
12865     if (!cmailMsgLoaded) return "";
12866
12867     if (cmailMailedMove) {
12868       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12869     } else {
12870         /* Create a list of games left */
12871       snprintf(string, MSG_SIZ, "[");
12872         for (i = 0; i < nCmailGames; i ++) {
12873             if (! (   cmailMoveRegistered[i]
12874                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12875                 if (prependComma) {
12876                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12877                 } else {
12878                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12879                     prependComma = 1;
12880                 }
12881
12882                 strcat(string, number);
12883             }
12884         }
12885         strcat(string, "]");
12886
12887         if (nCmailMovesRegistered + nCmailResults == 0) {
12888             switch (nCmailGames) {
12889               case 1:
12890                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12891                 break;
12892
12893               case 2:
12894                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12895                 break;
12896
12897               default:
12898                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12899                          nCmailGames);
12900                 break;
12901             }
12902         } else {
12903             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12904               case 1:
12905                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12906                          string);
12907                 break;
12908
12909               case 0:
12910                 if (nCmailResults == nCmailGames) {
12911                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12912                 } else {
12913                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12914                 }
12915                 break;
12916
12917               default:
12918                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12919                          string);
12920             }
12921         }
12922     }
12923     return cmailMsg;
12924 #endif /* WIN32 */
12925 }
12926
12927 void
12928 ResetGameEvent()
12929 {
12930     if (gameMode == Training)
12931       SetTrainingModeOff();
12932
12933     Reset(TRUE, TRUE);
12934     cmailMsgLoaded = FALSE;
12935     if (appData.icsActive) {
12936       SendToICS(ics_prefix);
12937       SendToICS("refresh\n");
12938     }
12939 }
12940
12941 void
12942 ExitEvent(status)
12943      int status;
12944 {
12945     exiting++;
12946     if (exiting > 2) {
12947       /* Give up on clean exit */
12948       exit(status);
12949     }
12950     if (exiting > 1) {
12951       /* Keep trying for clean exit */
12952       return;
12953     }
12954
12955     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12956
12957     if (telnetISR != NULL) {
12958       RemoveInputSource(telnetISR);
12959     }
12960     if (icsPR != NoProc) {
12961       DestroyChildProcess(icsPR, TRUE);
12962     }
12963
12964     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12965     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12966
12967     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12968     /* make sure this other one finishes before killing it!                  */
12969     if(endingGame) { int count = 0;
12970         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12971         while(endingGame && count++ < 10) DoSleep(1);
12972         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12973     }
12974
12975     /* Kill off chess programs */
12976     if (first.pr != NoProc) {
12977         ExitAnalyzeMode();
12978
12979         DoSleep( appData.delayBeforeQuit );
12980         SendToProgram("quit\n", &first);
12981         DoSleep( appData.delayAfterQuit );
12982         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12983     }
12984     if (second.pr != NoProc) {
12985         DoSleep( appData.delayBeforeQuit );
12986         SendToProgram("quit\n", &second);
12987         DoSleep( appData.delayAfterQuit );
12988         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12989     }
12990     if (first.isr != NULL) {
12991         RemoveInputSource(first.isr);
12992     }
12993     if (second.isr != NULL) {
12994         RemoveInputSource(second.isr);
12995     }
12996
12997     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12998     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12999
13000     ShutDownFrontEnd();
13001     exit(status);
13002 }
13003
13004 void
13005 PauseEvent()
13006 {
13007     if (appData.debugMode)
13008         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13009     if (pausing) {
13010         pausing = FALSE;
13011         ModeHighlight();
13012         if (gameMode == MachinePlaysWhite ||
13013             gameMode == MachinePlaysBlack) {
13014             StartClocks();
13015         } else {
13016             DisplayBothClocks();
13017         }
13018         if (gameMode == PlayFromGameFile) {
13019             if (appData.timeDelay >= 0)
13020                 AutoPlayGameLoop();
13021         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13022             Reset(FALSE, TRUE);
13023             SendToICS(ics_prefix);
13024             SendToICS("refresh\n");
13025         } else if (currentMove < forwardMostMove) {
13026             ForwardInner(forwardMostMove);
13027         }
13028         pauseExamInvalid = FALSE;
13029     } else {
13030         switch (gameMode) {
13031           default:
13032             return;
13033           case IcsExamining:
13034             pauseExamForwardMostMove = forwardMostMove;
13035             pauseExamInvalid = FALSE;
13036             /* fall through */
13037           case IcsObserving:
13038           case IcsPlayingWhite:
13039           case IcsPlayingBlack:
13040             pausing = TRUE;
13041             ModeHighlight();
13042             return;
13043           case PlayFromGameFile:
13044             (void) StopLoadGameTimer();
13045             pausing = TRUE;
13046             ModeHighlight();
13047             break;
13048           case BeginningOfGame:
13049             if (appData.icsActive) return;
13050             /* else fall through */
13051           case MachinePlaysWhite:
13052           case MachinePlaysBlack:
13053           case TwoMachinesPlay:
13054             if (forwardMostMove == 0)
13055               return;           /* don't pause if no one has moved */
13056             if ((gameMode == MachinePlaysWhite &&
13057                  !WhiteOnMove(forwardMostMove)) ||
13058                 (gameMode == MachinePlaysBlack &&
13059                  WhiteOnMove(forwardMostMove))) {
13060                 StopClocks();
13061             }
13062             pausing = TRUE;
13063             ModeHighlight();
13064             break;
13065         }
13066     }
13067 }
13068
13069 void
13070 EditCommentEvent()
13071 {
13072     char title[MSG_SIZ];
13073
13074     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13075       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13076     } else {
13077       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13078                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13079                parseList[currentMove - 1]);
13080     }
13081
13082     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13083 }
13084
13085
13086 void
13087 EditTagsEvent()
13088 {
13089     char *tags = PGNTags(&gameInfo);
13090     bookUp = FALSE;
13091     EditTagsPopUp(tags, NULL);
13092     free(tags);
13093 }
13094
13095 void
13096 AnalyzeModeEvent()
13097 {
13098     if (appData.noChessProgram || gameMode == AnalyzeMode)
13099       return;
13100
13101     if (gameMode != AnalyzeFile) {
13102         if (!appData.icsEngineAnalyze) {
13103                EditGameEvent();
13104                if (gameMode != EditGame) return;
13105         }
13106         ResurrectChessProgram();
13107         SendToProgram("analyze\n", &first);
13108         first.analyzing = TRUE;
13109         /*first.maybeThinking = TRUE;*/
13110         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13111         EngineOutputPopUp();
13112     }
13113     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13114     pausing = FALSE;
13115     ModeHighlight();
13116     SetGameInfo();
13117
13118     StartAnalysisClock();
13119     GetTimeMark(&lastNodeCountTime);
13120     lastNodeCount = 0;
13121 }
13122
13123 void
13124 AnalyzeFileEvent()
13125 {
13126     if (appData.noChessProgram || gameMode == AnalyzeFile)
13127       return;
13128
13129     if (gameMode != AnalyzeMode) {
13130         EditGameEvent();
13131         if (gameMode != EditGame) return;
13132         ResurrectChessProgram();
13133         SendToProgram("analyze\n", &first);
13134         first.analyzing = TRUE;
13135         /*first.maybeThinking = TRUE;*/
13136         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13137         EngineOutputPopUp();
13138     }
13139     gameMode = AnalyzeFile;
13140     pausing = FALSE;
13141     ModeHighlight();
13142     SetGameInfo();
13143
13144     StartAnalysisClock();
13145     GetTimeMark(&lastNodeCountTime);
13146     lastNodeCount = 0;
13147     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13148 }
13149
13150 void
13151 MachineWhiteEvent()
13152 {
13153     char buf[MSG_SIZ];
13154     char *bookHit = NULL;
13155
13156     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13157       return;
13158
13159
13160     if (gameMode == PlayFromGameFile ||
13161         gameMode == TwoMachinesPlay  ||
13162         gameMode == Training         ||
13163         gameMode == AnalyzeMode      ||
13164         gameMode == EndOfGame)
13165         EditGameEvent();
13166
13167     if (gameMode == EditPosition)
13168         EditPositionDone(TRUE);
13169
13170     if (!WhiteOnMove(currentMove)) {
13171         DisplayError(_("It is not White's turn"), 0);
13172         return;
13173     }
13174
13175     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13176       ExitAnalyzeMode();
13177
13178     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13179         gameMode == AnalyzeFile)
13180         TruncateGame();
13181
13182     ResurrectChessProgram();    /* in case it isn't running */
13183     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13184         gameMode = MachinePlaysWhite;
13185         ResetClocks();
13186     } else
13187     gameMode = MachinePlaysWhite;
13188     pausing = FALSE;
13189     ModeHighlight();
13190     SetGameInfo();
13191     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13192     DisplayTitle(buf);
13193     if (first.sendName) {
13194       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13195       SendToProgram(buf, &first);
13196     }
13197     if (first.sendTime) {
13198       if (first.useColors) {
13199         SendToProgram("black\n", &first); /*gnu kludge*/
13200       }
13201       SendTimeRemaining(&first, TRUE);
13202     }
13203     if (first.useColors) {
13204       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13205     }
13206     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13207     SetMachineThinkingEnables();
13208     first.maybeThinking = TRUE;
13209     StartClocks();
13210     firstMove = FALSE;
13211
13212     if (appData.autoFlipView && !flipView) {
13213       flipView = !flipView;
13214       DrawPosition(FALSE, NULL);
13215       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13216     }
13217
13218     if(bookHit) { // [HGM] book: simulate book reply
13219         static char bookMove[MSG_SIZ]; // a bit generous?
13220
13221         programStats.nodes = programStats.depth = programStats.time =
13222         programStats.score = programStats.got_only_move = 0;
13223         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13224
13225         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13226         strcat(bookMove, bookHit);
13227         HandleMachineMove(bookMove, &first);
13228     }
13229 }
13230
13231 void
13232 MachineBlackEvent()
13233 {
13234   char buf[MSG_SIZ];
13235   char *bookHit = NULL;
13236
13237     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13238         return;
13239
13240
13241     if (gameMode == PlayFromGameFile ||
13242         gameMode == TwoMachinesPlay  ||
13243         gameMode == Training         ||
13244         gameMode == AnalyzeMode      ||
13245         gameMode == EndOfGame)
13246         EditGameEvent();
13247
13248     if (gameMode == EditPosition)
13249         EditPositionDone(TRUE);
13250
13251     if (WhiteOnMove(currentMove)) {
13252         DisplayError(_("It is not Black's turn"), 0);
13253         return;
13254     }
13255
13256     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13257       ExitAnalyzeMode();
13258
13259     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13260         gameMode == AnalyzeFile)
13261         TruncateGame();
13262
13263     ResurrectChessProgram();    /* in case it isn't running */
13264     gameMode = MachinePlaysBlack;
13265     pausing = FALSE;
13266     ModeHighlight();
13267     SetGameInfo();
13268     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13269     DisplayTitle(buf);
13270     if (first.sendName) {
13271       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13272       SendToProgram(buf, &first);
13273     }
13274     if (first.sendTime) {
13275       if (first.useColors) {
13276         SendToProgram("white\n", &first); /*gnu kludge*/
13277       }
13278       SendTimeRemaining(&first, FALSE);
13279     }
13280     if (first.useColors) {
13281       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13282     }
13283     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13284     SetMachineThinkingEnables();
13285     first.maybeThinking = TRUE;
13286     StartClocks();
13287
13288     if (appData.autoFlipView && flipView) {
13289       flipView = !flipView;
13290       DrawPosition(FALSE, NULL);
13291       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13292     }
13293     if(bookHit) { // [HGM] book: simulate book reply
13294         static char bookMove[MSG_SIZ]; // a bit generous?
13295
13296         programStats.nodes = programStats.depth = programStats.time =
13297         programStats.score = programStats.got_only_move = 0;
13298         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13299
13300         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13301         strcat(bookMove, bookHit);
13302         HandleMachineMove(bookMove, &first);
13303     }
13304 }
13305
13306
13307 void
13308 DisplayTwoMachinesTitle()
13309 {
13310     char buf[MSG_SIZ];
13311     if (appData.matchGames > 0) {
13312         if(appData.tourneyFile[0]) {
13313           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13314                    gameInfo.white, gameInfo.black,
13315                    nextGame+1, appData.matchGames+1,
13316                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13317         } else 
13318         if (first.twoMachinesColor[0] == 'w') {
13319           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13320                    gameInfo.white, gameInfo.black,
13321                    first.matchWins, second.matchWins,
13322                    matchGame - 1 - (first.matchWins + second.matchWins));
13323         } else {
13324           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13325                    gameInfo.white, gameInfo.black,
13326                    second.matchWins, first.matchWins,
13327                    matchGame - 1 - (first.matchWins + second.matchWins));
13328         }
13329     } else {
13330       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13331     }
13332     DisplayTitle(buf);
13333 }
13334
13335 void
13336 SettingsMenuIfReady()
13337 {
13338   if (second.lastPing != second.lastPong) {
13339     DisplayMessage("", _("Waiting for second chess program"));
13340     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13341     return;
13342   }
13343   ThawUI();
13344   DisplayMessage("", "");
13345   SettingsPopUp(&second);
13346 }
13347
13348 int
13349 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13350 {
13351     char buf[MSG_SIZ];
13352     if (cps->pr == NoProc) {
13353         StartChessProgram(cps);
13354         if (cps->protocolVersion == 1) {
13355           retry();
13356         } else {
13357           /* kludge: allow timeout for initial "feature" command */
13358           FreezeUI();
13359           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13360           DisplayMessage("", buf);
13361           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13362         }
13363         return 1;
13364     }
13365     return 0;
13366 }
13367
13368 void
13369 TwoMachinesEvent P((void))
13370 {
13371     int i;
13372     char buf[MSG_SIZ];
13373     ChessProgramState *onmove;
13374     char *bookHit = NULL;
13375     static int stalling = 0;
13376     TimeMark now;
13377     long wait;
13378
13379     if (appData.noChessProgram) return;
13380
13381     switch (gameMode) {
13382       case TwoMachinesPlay:
13383         return;
13384       case MachinePlaysWhite:
13385       case MachinePlaysBlack:
13386         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13387             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13388             return;
13389         }
13390         /* fall through */
13391       case BeginningOfGame:
13392       case PlayFromGameFile:
13393       case EndOfGame:
13394         EditGameEvent();
13395         if (gameMode != EditGame) return;
13396         break;
13397       case EditPosition:
13398         EditPositionDone(TRUE);
13399         break;
13400       case AnalyzeMode:
13401       case AnalyzeFile:
13402         ExitAnalyzeMode();
13403         break;
13404       case EditGame:
13405       default:
13406         break;
13407     }
13408
13409 //    forwardMostMove = currentMove;
13410     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13411
13412     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13413
13414     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13415     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13416       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13417       return;
13418     }
13419     if(!stalling) {
13420       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13421       SendToProgram("force\n", &second);
13422       stalling = 1;
13423       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13424       return;
13425     }
13426     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13427     if(appData.matchPause>10000 || appData.matchPause<10)
13428                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13429     wait = SubtractTimeMarks(&now, &pauseStart);
13430     if(wait < appData.matchPause) {
13431         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13432         return;
13433     }
13434     stalling = 0;
13435     DisplayMessage("", "");
13436     if (startedFromSetupPosition) {
13437         SendBoard(&second, backwardMostMove);
13438     if (appData.debugMode) {
13439         fprintf(debugFP, "Two Machines\n");
13440     }
13441     }
13442     for (i = backwardMostMove; i < forwardMostMove; i++) {
13443         SendMoveToProgram(i, &second);
13444     }
13445
13446     gameMode = TwoMachinesPlay;
13447     pausing = FALSE;
13448     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13449     SetGameInfo();
13450     DisplayTwoMachinesTitle();
13451     firstMove = TRUE;
13452     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13453         onmove = &first;
13454     } else {
13455         onmove = &second;
13456     }
13457     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13458     SendToProgram(first.computerString, &first);
13459     if (first.sendName) {
13460       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13461       SendToProgram(buf, &first);
13462     }
13463     SendToProgram(second.computerString, &second);
13464     if (second.sendName) {
13465       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13466       SendToProgram(buf, &second);
13467     }
13468
13469     ResetClocks();
13470     if (!first.sendTime || !second.sendTime) {
13471         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13472         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13473     }
13474     if (onmove->sendTime) {
13475       if (onmove->useColors) {
13476         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13477       }
13478       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13479     }
13480     if (onmove->useColors) {
13481       SendToProgram(onmove->twoMachinesColor, onmove);
13482     }
13483     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13484 //    SendToProgram("go\n", onmove);
13485     onmove->maybeThinking = TRUE;
13486     SetMachineThinkingEnables();
13487
13488     StartClocks();
13489
13490     if(bookHit) { // [HGM] book: simulate book reply
13491         static char bookMove[MSG_SIZ]; // a bit generous?
13492
13493         programStats.nodes = programStats.depth = programStats.time =
13494         programStats.score = programStats.got_only_move = 0;
13495         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13496
13497         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13498         strcat(bookMove, bookHit);
13499         savedMessage = bookMove; // args for deferred call
13500         savedState = onmove;
13501         ScheduleDelayedEvent(DeferredBookMove, 1);
13502     }
13503 }
13504
13505 void
13506 TrainingEvent()
13507 {
13508     if (gameMode == Training) {
13509       SetTrainingModeOff();
13510       gameMode = PlayFromGameFile;
13511       DisplayMessage("", _("Training mode off"));
13512     } else {
13513       gameMode = Training;
13514       animateTraining = appData.animate;
13515
13516       /* make sure we are not already at the end of the game */
13517       if (currentMove < forwardMostMove) {
13518         SetTrainingModeOn();
13519         DisplayMessage("", _("Training mode on"));
13520       } else {
13521         gameMode = PlayFromGameFile;
13522         DisplayError(_("Already at end of game"), 0);
13523       }
13524     }
13525     ModeHighlight();
13526 }
13527
13528 void
13529 IcsClientEvent()
13530 {
13531     if (!appData.icsActive) return;
13532     switch (gameMode) {
13533       case IcsPlayingWhite:
13534       case IcsPlayingBlack:
13535       case IcsObserving:
13536       case IcsIdle:
13537       case BeginningOfGame:
13538       case IcsExamining:
13539         return;
13540
13541       case EditGame:
13542         break;
13543
13544       case EditPosition:
13545         EditPositionDone(TRUE);
13546         break;
13547
13548       case AnalyzeMode:
13549       case AnalyzeFile:
13550         ExitAnalyzeMode();
13551         break;
13552
13553       default:
13554         EditGameEvent();
13555         break;
13556     }
13557
13558     gameMode = IcsIdle;
13559     ModeHighlight();
13560     return;
13561 }
13562
13563
13564 void
13565 EditGameEvent()
13566 {
13567     int i;
13568
13569     switch (gameMode) {
13570       case Training:
13571         SetTrainingModeOff();
13572         break;
13573       case MachinePlaysWhite:
13574       case MachinePlaysBlack:
13575       case BeginningOfGame:
13576         SendToProgram("force\n", &first);
13577         SetUserThinkingEnables();
13578         break;
13579       case PlayFromGameFile:
13580         (void) StopLoadGameTimer();
13581         if (gameFileFP != NULL) {
13582             gameFileFP = NULL;
13583         }
13584         break;
13585       case EditPosition:
13586         EditPositionDone(TRUE);
13587         break;
13588       case AnalyzeMode:
13589       case AnalyzeFile:
13590         ExitAnalyzeMode();
13591         SendToProgram("force\n", &first);
13592         break;
13593       case TwoMachinesPlay:
13594         GameEnds(EndOfFile, NULL, GE_PLAYER);
13595         ResurrectChessProgram();
13596         SetUserThinkingEnables();
13597         break;
13598       case EndOfGame:
13599         ResurrectChessProgram();
13600         break;
13601       case IcsPlayingBlack:
13602       case IcsPlayingWhite:
13603         DisplayError(_("Warning: You are still playing a game"), 0);
13604         break;
13605       case IcsObserving:
13606         DisplayError(_("Warning: You are still observing a game"), 0);
13607         break;
13608       case IcsExamining:
13609         DisplayError(_("Warning: You are still examining a game"), 0);
13610         break;
13611       case IcsIdle:
13612         break;
13613       case EditGame:
13614       default:
13615         return;
13616     }
13617
13618     pausing = FALSE;
13619     StopClocks();
13620     first.offeredDraw = second.offeredDraw = 0;
13621
13622     if (gameMode == PlayFromGameFile) {
13623         whiteTimeRemaining = timeRemaining[0][currentMove];
13624         blackTimeRemaining = timeRemaining[1][currentMove];
13625         DisplayTitle("");
13626     }
13627
13628     if (gameMode == MachinePlaysWhite ||
13629         gameMode == MachinePlaysBlack ||
13630         gameMode == TwoMachinesPlay ||
13631         gameMode == EndOfGame) {
13632         i = forwardMostMove;
13633         while (i > currentMove) {
13634             SendToProgram("undo\n", &first);
13635             i--;
13636         }
13637         if(!adjustedClock) {
13638         whiteTimeRemaining = timeRemaining[0][currentMove];
13639         blackTimeRemaining = timeRemaining[1][currentMove];
13640         DisplayBothClocks();
13641         }
13642         if (whiteFlag || blackFlag) {
13643             whiteFlag = blackFlag = 0;
13644         }
13645         DisplayTitle("");
13646     }
13647
13648     gameMode = EditGame;
13649     ModeHighlight();
13650     SetGameInfo();
13651 }
13652
13653
13654 void
13655 EditPositionEvent()
13656 {
13657     if (gameMode == EditPosition) {
13658         EditGameEvent();
13659         return;
13660     }
13661
13662     EditGameEvent();
13663     if (gameMode != EditGame) return;
13664
13665     gameMode = EditPosition;
13666     ModeHighlight();
13667     SetGameInfo();
13668     if (currentMove > 0)
13669       CopyBoard(boards[0], boards[currentMove]);
13670
13671     blackPlaysFirst = !WhiteOnMove(currentMove);
13672     ResetClocks();
13673     currentMove = forwardMostMove = backwardMostMove = 0;
13674     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13675     DisplayMove(-1);
13676 }
13677
13678 void
13679 ExitAnalyzeMode()
13680 {
13681     /* [DM] icsEngineAnalyze - possible call from other functions */
13682     if (appData.icsEngineAnalyze) {
13683         appData.icsEngineAnalyze = FALSE;
13684
13685         DisplayMessage("",_("Close ICS engine analyze..."));
13686     }
13687     if (first.analysisSupport && first.analyzing) {
13688       SendToProgram("exit\n", &first);
13689       first.analyzing = FALSE;
13690     }
13691     thinkOutput[0] = NULLCHAR;
13692 }
13693
13694 void
13695 EditPositionDone(Boolean fakeRights)
13696 {
13697     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13698
13699     startedFromSetupPosition = TRUE;
13700     InitChessProgram(&first, FALSE);
13701     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13702       boards[0][EP_STATUS] = EP_NONE;
13703       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13704     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13705         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13706         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13707       } else boards[0][CASTLING][2] = NoRights;
13708     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13709         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13710         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13711       } else boards[0][CASTLING][5] = NoRights;
13712     }
13713     SendToProgram("force\n", &first);
13714     if (blackPlaysFirst) {
13715         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13716         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13717         currentMove = forwardMostMove = backwardMostMove = 1;
13718         CopyBoard(boards[1], boards[0]);
13719     } else {
13720         currentMove = forwardMostMove = backwardMostMove = 0;
13721     }
13722     SendBoard(&first, forwardMostMove);
13723     if (appData.debugMode) {
13724         fprintf(debugFP, "EditPosDone\n");
13725     }
13726     DisplayTitle("");
13727     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13728     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13729     gameMode = EditGame;
13730     ModeHighlight();
13731     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13732     ClearHighlights(); /* [AS] */
13733 }
13734
13735 /* Pause for `ms' milliseconds */
13736 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13737 void
13738 TimeDelay(ms)
13739      long ms;
13740 {
13741     TimeMark m1, m2;
13742
13743     GetTimeMark(&m1);
13744     do {
13745         GetTimeMark(&m2);
13746     } while (SubtractTimeMarks(&m2, &m1) < ms);
13747 }
13748
13749 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13750 void
13751 SendMultiLineToICS(buf)
13752      char *buf;
13753 {
13754     char temp[MSG_SIZ+1], *p;
13755     int len;
13756
13757     len = strlen(buf);
13758     if (len > MSG_SIZ)
13759       len = MSG_SIZ;
13760
13761     strncpy(temp, buf, len);
13762     temp[len] = 0;
13763
13764     p = temp;
13765     while (*p) {
13766         if (*p == '\n' || *p == '\r')
13767           *p = ' ';
13768         ++p;
13769     }
13770
13771     strcat(temp, "\n");
13772     SendToICS(temp);
13773     SendToPlayer(temp, strlen(temp));
13774 }
13775
13776 void
13777 SetWhiteToPlayEvent()
13778 {
13779     if (gameMode == EditPosition) {
13780         blackPlaysFirst = FALSE;
13781         DisplayBothClocks();    /* works because currentMove is 0 */
13782     } else if (gameMode == IcsExamining) {
13783         SendToICS(ics_prefix);
13784         SendToICS("tomove white\n");
13785     }
13786 }
13787
13788 void
13789 SetBlackToPlayEvent()
13790 {
13791     if (gameMode == EditPosition) {
13792         blackPlaysFirst = TRUE;
13793         currentMove = 1;        /* kludge */
13794         DisplayBothClocks();
13795         currentMove = 0;
13796     } else if (gameMode == IcsExamining) {
13797         SendToICS(ics_prefix);
13798         SendToICS("tomove black\n");
13799     }
13800 }
13801
13802 void
13803 EditPositionMenuEvent(selection, x, y)
13804      ChessSquare selection;
13805      int x, y;
13806 {
13807     char buf[MSG_SIZ];
13808     ChessSquare piece = boards[0][y][x];
13809
13810     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13811
13812     switch (selection) {
13813       case ClearBoard:
13814         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13815             SendToICS(ics_prefix);
13816             SendToICS("bsetup clear\n");
13817         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13818             SendToICS(ics_prefix);
13819             SendToICS("clearboard\n");
13820         } else {
13821             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13822                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13823                 for (y = 0; y < BOARD_HEIGHT; y++) {
13824                     if (gameMode == IcsExamining) {
13825                         if (boards[currentMove][y][x] != EmptySquare) {
13826                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13827                                     AAA + x, ONE + y);
13828                             SendToICS(buf);
13829                         }
13830                     } else {
13831                         boards[0][y][x] = p;
13832                     }
13833                 }
13834             }
13835         }
13836         if (gameMode == EditPosition) {
13837             DrawPosition(FALSE, boards[0]);
13838         }
13839         break;
13840
13841       case WhitePlay:
13842         SetWhiteToPlayEvent();
13843         break;
13844
13845       case BlackPlay:
13846         SetBlackToPlayEvent();
13847         break;
13848
13849       case EmptySquare:
13850         if (gameMode == IcsExamining) {
13851             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13852             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13853             SendToICS(buf);
13854         } else {
13855             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13856                 if(x == BOARD_LEFT-2) {
13857                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13858                     boards[0][y][1] = 0;
13859                 } else
13860                 if(x == BOARD_RGHT+1) {
13861                     if(y >= gameInfo.holdingsSize) break;
13862                     boards[0][y][BOARD_WIDTH-2] = 0;
13863                 } else break;
13864             }
13865             boards[0][y][x] = EmptySquare;
13866             DrawPosition(FALSE, boards[0]);
13867         }
13868         break;
13869
13870       case PromotePiece:
13871         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13872            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13873             selection = (ChessSquare) (PROMOTED piece);
13874         } else if(piece == EmptySquare) selection = WhiteSilver;
13875         else selection = (ChessSquare)((int)piece - 1);
13876         goto defaultlabel;
13877
13878       case DemotePiece:
13879         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13880            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13881             selection = (ChessSquare) (DEMOTED piece);
13882         } else if(piece == EmptySquare) selection = BlackSilver;
13883         else selection = (ChessSquare)((int)piece + 1);
13884         goto defaultlabel;
13885
13886       case WhiteQueen:
13887       case BlackQueen:
13888         if(gameInfo.variant == VariantShatranj ||
13889            gameInfo.variant == VariantXiangqi  ||
13890            gameInfo.variant == VariantCourier  ||
13891            gameInfo.variant == VariantMakruk     )
13892             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13893         goto defaultlabel;
13894
13895       case WhiteKing:
13896       case BlackKing:
13897         if(gameInfo.variant == VariantXiangqi)
13898             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13899         if(gameInfo.variant == VariantKnightmate)
13900             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13901       default:
13902         defaultlabel:
13903         if (gameMode == IcsExamining) {
13904             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13905             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13906                      PieceToChar(selection), AAA + x, ONE + y);
13907             SendToICS(buf);
13908         } else {
13909             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13910                 int n;
13911                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13912                     n = PieceToNumber(selection - BlackPawn);
13913                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13914                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13915                     boards[0][BOARD_HEIGHT-1-n][1]++;
13916                 } else
13917                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13918                     n = PieceToNumber(selection);
13919                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13920                     boards[0][n][BOARD_WIDTH-1] = selection;
13921                     boards[0][n][BOARD_WIDTH-2]++;
13922                 }
13923             } else
13924             boards[0][y][x] = selection;
13925             DrawPosition(TRUE, boards[0]);
13926         }
13927         break;
13928     }
13929 }
13930
13931
13932 void
13933 DropMenuEvent(selection, x, y)
13934      ChessSquare selection;
13935      int x, y;
13936 {
13937     ChessMove moveType;
13938
13939     switch (gameMode) {
13940       case IcsPlayingWhite:
13941       case MachinePlaysBlack:
13942         if (!WhiteOnMove(currentMove)) {
13943             DisplayMoveError(_("It is Black's turn"));
13944             return;
13945         }
13946         moveType = WhiteDrop;
13947         break;
13948       case IcsPlayingBlack:
13949       case MachinePlaysWhite:
13950         if (WhiteOnMove(currentMove)) {
13951             DisplayMoveError(_("It is White's turn"));
13952             return;
13953         }
13954         moveType = BlackDrop;
13955         break;
13956       case EditGame:
13957         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13958         break;
13959       default:
13960         return;
13961     }
13962
13963     if (moveType == BlackDrop && selection < BlackPawn) {
13964       selection = (ChessSquare) ((int) selection
13965                                  + (int) BlackPawn - (int) WhitePawn);
13966     }
13967     if (boards[currentMove][y][x] != EmptySquare) {
13968         DisplayMoveError(_("That square is occupied"));
13969         return;
13970     }
13971
13972     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13973 }
13974
13975 void
13976 AcceptEvent()
13977 {
13978     /* Accept a pending offer of any kind from opponent */
13979
13980     if (appData.icsActive) {
13981         SendToICS(ics_prefix);
13982         SendToICS("accept\n");
13983     } else if (cmailMsgLoaded) {
13984         if (currentMove == cmailOldMove &&
13985             commentList[cmailOldMove] != NULL &&
13986             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13987                    "Black offers a draw" : "White offers a draw")) {
13988             TruncateGame();
13989             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13990             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13991         } else {
13992             DisplayError(_("There is no pending offer on this move"), 0);
13993             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13994         }
13995     } else {
13996         /* Not used for offers from chess program */
13997     }
13998 }
13999
14000 void
14001 DeclineEvent()
14002 {
14003     /* Decline a pending offer of any kind from opponent */
14004
14005     if (appData.icsActive) {
14006         SendToICS(ics_prefix);
14007         SendToICS("decline\n");
14008     } else if (cmailMsgLoaded) {
14009         if (currentMove == cmailOldMove &&
14010             commentList[cmailOldMove] != NULL &&
14011             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14012                    "Black offers a draw" : "White offers a draw")) {
14013 #ifdef NOTDEF
14014             AppendComment(cmailOldMove, "Draw declined", TRUE);
14015             DisplayComment(cmailOldMove - 1, "Draw declined");
14016 #endif /*NOTDEF*/
14017         } else {
14018             DisplayError(_("There is no pending offer on this move"), 0);
14019         }
14020     } else {
14021         /* Not used for offers from chess program */
14022     }
14023 }
14024
14025 void
14026 RematchEvent()
14027 {
14028     /* Issue ICS rematch command */
14029     if (appData.icsActive) {
14030         SendToICS(ics_prefix);
14031         SendToICS("rematch\n");
14032     }
14033 }
14034
14035 void
14036 CallFlagEvent()
14037 {
14038     /* Call your opponent's flag (claim a win on time) */
14039     if (appData.icsActive) {
14040         SendToICS(ics_prefix);
14041         SendToICS("flag\n");
14042     } else {
14043         switch (gameMode) {
14044           default:
14045             return;
14046           case MachinePlaysWhite:
14047             if (whiteFlag) {
14048                 if (blackFlag)
14049                   GameEnds(GameIsDrawn, "Both players ran out of time",
14050                            GE_PLAYER);
14051                 else
14052                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14053             } else {
14054                 DisplayError(_("Your opponent is not out of time"), 0);
14055             }
14056             break;
14057           case MachinePlaysBlack:
14058             if (blackFlag) {
14059                 if (whiteFlag)
14060                   GameEnds(GameIsDrawn, "Both players ran out of time",
14061                            GE_PLAYER);
14062                 else
14063                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14064             } else {
14065                 DisplayError(_("Your opponent is not out of time"), 0);
14066             }
14067             break;
14068         }
14069     }
14070 }
14071
14072 void
14073 ClockClick(int which)
14074 {       // [HGM] code moved to back-end from winboard.c
14075         if(which) { // black clock
14076           if (gameMode == EditPosition || gameMode == IcsExamining) {
14077             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14078             SetBlackToPlayEvent();
14079           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14080           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14081           } else if (shiftKey) {
14082             AdjustClock(which, -1);
14083           } else if (gameMode == IcsPlayingWhite ||
14084                      gameMode == MachinePlaysBlack) {
14085             CallFlagEvent();
14086           }
14087         } else { // white clock
14088           if (gameMode == EditPosition || gameMode == IcsExamining) {
14089             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14090             SetWhiteToPlayEvent();
14091           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14092           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14093           } else if (shiftKey) {
14094             AdjustClock(which, -1);
14095           } else if (gameMode == IcsPlayingBlack ||
14096                    gameMode == MachinePlaysWhite) {
14097             CallFlagEvent();
14098           }
14099         }
14100 }
14101
14102 void
14103 DrawEvent()
14104 {
14105     /* Offer draw or accept pending draw offer from opponent */
14106
14107     if (appData.icsActive) {
14108         /* Note: tournament rules require draw offers to be
14109            made after you make your move but before you punch
14110            your clock.  Currently ICS doesn't let you do that;
14111            instead, you immediately punch your clock after making
14112            a move, but you can offer a draw at any time. */
14113
14114         SendToICS(ics_prefix);
14115         SendToICS("draw\n");
14116         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14117     } else if (cmailMsgLoaded) {
14118         if (currentMove == cmailOldMove &&
14119             commentList[cmailOldMove] != NULL &&
14120             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14121                    "Black offers a draw" : "White offers a draw")) {
14122             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14123             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14124         } else if (currentMove == cmailOldMove + 1) {
14125             char *offer = WhiteOnMove(cmailOldMove) ?
14126               "White offers a draw" : "Black offers a draw";
14127             AppendComment(currentMove, offer, TRUE);
14128             DisplayComment(currentMove - 1, offer);
14129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14130         } else {
14131             DisplayError(_("You must make your move before offering a draw"), 0);
14132             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14133         }
14134     } else if (first.offeredDraw) {
14135         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14136     } else {
14137         if (first.sendDrawOffers) {
14138             SendToProgram("draw\n", &first);
14139             userOfferedDraw = TRUE;
14140         }
14141     }
14142 }
14143
14144 void
14145 AdjournEvent()
14146 {
14147     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14148
14149     if (appData.icsActive) {
14150         SendToICS(ics_prefix);
14151         SendToICS("adjourn\n");
14152     } else {
14153         /* Currently GNU Chess doesn't offer or accept Adjourns */
14154     }
14155 }
14156
14157
14158 void
14159 AbortEvent()
14160 {
14161     /* Offer Abort or accept pending Abort offer from opponent */
14162
14163     if (appData.icsActive) {
14164         SendToICS(ics_prefix);
14165         SendToICS("abort\n");
14166     } else {
14167         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14168     }
14169 }
14170
14171 void
14172 ResignEvent()
14173 {
14174     /* Resign.  You can do this even if it's not your turn. */
14175
14176     if (appData.icsActive) {
14177         SendToICS(ics_prefix);
14178         SendToICS("resign\n");
14179     } else {
14180         switch (gameMode) {
14181           case MachinePlaysWhite:
14182             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14183             break;
14184           case MachinePlaysBlack:
14185             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14186             break;
14187           case EditGame:
14188             if (cmailMsgLoaded) {
14189                 TruncateGame();
14190                 if (WhiteOnMove(cmailOldMove)) {
14191                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14192                 } else {
14193                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14194                 }
14195                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14196             }
14197             break;
14198           default:
14199             break;
14200         }
14201     }
14202 }
14203
14204
14205 void
14206 StopObservingEvent()
14207 {
14208     /* Stop observing current games */
14209     SendToICS(ics_prefix);
14210     SendToICS("unobserve\n");
14211 }
14212
14213 void
14214 StopExaminingEvent()
14215 {
14216     /* Stop observing current game */
14217     SendToICS(ics_prefix);
14218     SendToICS("unexamine\n");
14219 }
14220
14221 void
14222 ForwardInner(target)
14223      int target;
14224 {
14225     int limit;
14226
14227     if (appData.debugMode)
14228         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14229                 target, currentMove, forwardMostMove);
14230
14231     if (gameMode == EditPosition)
14232       return;
14233
14234     MarkTargetSquares(1);
14235
14236     if (gameMode == PlayFromGameFile && !pausing)
14237       PauseEvent();
14238
14239     if (gameMode == IcsExamining && pausing)
14240       limit = pauseExamForwardMostMove;
14241     else
14242       limit = forwardMostMove;
14243
14244     if (target > limit) target = limit;
14245
14246     if (target > 0 && moveList[target - 1][0]) {
14247         int fromX, fromY, toX, toY;
14248         toX = moveList[target - 1][2] - AAA;
14249         toY = moveList[target - 1][3] - ONE;
14250         if (moveList[target - 1][1] == '@') {
14251             if (appData.highlightLastMove) {
14252                 SetHighlights(-1, -1, toX, toY);
14253             }
14254         } else {
14255             fromX = moveList[target - 1][0] - AAA;
14256             fromY = moveList[target - 1][1] - ONE;
14257             if (target == currentMove + 1) {
14258                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14259             }
14260             if (appData.highlightLastMove) {
14261                 SetHighlights(fromX, fromY, toX, toY);
14262             }
14263         }
14264     }
14265     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14266         gameMode == Training || gameMode == PlayFromGameFile ||
14267         gameMode == AnalyzeFile) {
14268         while (currentMove < target) {
14269             SendMoveToProgram(currentMove++, &first);
14270         }
14271     } else {
14272         currentMove = target;
14273     }
14274
14275     if (gameMode == EditGame || gameMode == EndOfGame) {
14276         whiteTimeRemaining = timeRemaining[0][currentMove];
14277         blackTimeRemaining = timeRemaining[1][currentMove];
14278     }
14279     DisplayBothClocks();
14280     DisplayMove(currentMove - 1);
14281     DrawPosition(FALSE, boards[currentMove]);
14282     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14283     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14284         DisplayComment(currentMove - 1, commentList[currentMove]);
14285     }
14286 }
14287
14288
14289 void
14290 ForwardEvent()
14291 {
14292     if (gameMode == IcsExamining && !pausing) {
14293         SendToICS(ics_prefix);
14294         SendToICS("forward\n");
14295     } else {
14296         ForwardInner(currentMove + 1);
14297     }
14298 }
14299
14300 void
14301 ToEndEvent()
14302 {
14303     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14304         /* to optimze, we temporarily turn off analysis mode while we feed
14305          * the remaining moves to the engine. Otherwise we get analysis output
14306          * after each move.
14307          */
14308         if (first.analysisSupport) {
14309           SendToProgram("exit\nforce\n", &first);
14310           first.analyzing = FALSE;
14311         }
14312     }
14313
14314     if (gameMode == IcsExamining && !pausing) {
14315         SendToICS(ics_prefix);
14316         SendToICS("forward 999999\n");
14317     } else {
14318         ForwardInner(forwardMostMove);
14319     }
14320
14321     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14322         /* we have fed all the moves, so reactivate analysis mode */
14323         SendToProgram("analyze\n", &first);
14324         first.analyzing = TRUE;
14325         /*first.maybeThinking = TRUE;*/
14326         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14327     }
14328 }
14329
14330 void
14331 BackwardInner(target)
14332      int target;
14333 {
14334     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14335
14336     if (appData.debugMode)
14337         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14338                 target, currentMove, forwardMostMove);
14339
14340     if (gameMode == EditPosition) return;
14341     MarkTargetSquares(1);
14342     if (currentMove <= backwardMostMove) {
14343         ClearHighlights();
14344         DrawPosition(full_redraw, boards[currentMove]);
14345         return;
14346     }
14347     if (gameMode == PlayFromGameFile && !pausing)
14348       PauseEvent();
14349
14350     if (moveList[target][0]) {
14351         int fromX, fromY, toX, toY;
14352         toX = moveList[target][2] - AAA;
14353         toY = moveList[target][3] - ONE;
14354         if (moveList[target][1] == '@') {
14355             if (appData.highlightLastMove) {
14356                 SetHighlights(-1, -1, toX, toY);
14357             }
14358         } else {
14359             fromX = moveList[target][0] - AAA;
14360             fromY = moveList[target][1] - ONE;
14361             if (target == currentMove - 1) {
14362                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14363             }
14364             if (appData.highlightLastMove) {
14365                 SetHighlights(fromX, fromY, toX, toY);
14366             }
14367         }
14368     }
14369     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14370         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14371         while (currentMove > target) {
14372             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14373                 // null move cannot be undone. Reload program with move history before it.
14374                 int i;
14375                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14376                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14377                 }
14378                 SendBoard(&first, i); 
14379                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14380                 break;
14381             }
14382             SendToProgram("undo\n", &first);
14383             currentMove--;
14384         }
14385     } else {
14386         currentMove = target;
14387     }
14388
14389     if (gameMode == EditGame || gameMode == EndOfGame) {
14390         whiteTimeRemaining = timeRemaining[0][currentMove];
14391         blackTimeRemaining = timeRemaining[1][currentMove];
14392     }
14393     DisplayBothClocks();
14394     DisplayMove(currentMove - 1);
14395     DrawPosition(full_redraw, boards[currentMove]);
14396     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14397     // [HGM] PV info: routine tests if comment empty
14398     DisplayComment(currentMove - 1, commentList[currentMove]);
14399 }
14400
14401 void
14402 BackwardEvent()
14403 {
14404     if (gameMode == IcsExamining && !pausing) {
14405         SendToICS(ics_prefix);
14406         SendToICS("backward\n");
14407     } else {
14408         BackwardInner(currentMove - 1);
14409     }
14410 }
14411
14412 void
14413 ToStartEvent()
14414 {
14415     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14416         /* to optimize, we temporarily turn off analysis mode while we undo
14417          * all the moves. Otherwise we get analysis output after each undo.
14418          */
14419         if (first.analysisSupport) {
14420           SendToProgram("exit\nforce\n", &first);
14421           first.analyzing = FALSE;
14422         }
14423     }
14424
14425     if (gameMode == IcsExamining && !pausing) {
14426         SendToICS(ics_prefix);
14427         SendToICS("backward 999999\n");
14428     } else {
14429         BackwardInner(backwardMostMove);
14430     }
14431
14432     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14433         /* we have fed all the moves, so reactivate analysis mode */
14434         SendToProgram("analyze\n", &first);
14435         first.analyzing = TRUE;
14436         /*first.maybeThinking = TRUE;*/
14437         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14438     }
14439 }
14440
14441 void
14442 ToNrEvent(int to)
14443 {
14444   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14445   if (to >= forwardMostMove) to = forwardMostMove;
14446   if (to <= backwardMostMove) to = backwardMostMove;
14447   if (to < currentMove) {
14448     BackwardInner(to);
14449   } else {
14450     ForwardInner(to);
14451   }
14452 }
14453
14454 void
14455 RevertEvent(Boolean annotate)
14456 {
14457     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14458         return;
14459     }
14460     if (gameMode != IcsExamining) {
14461         DisplayError(_("You are not examining a game"), 0);
14462         return;
14463     }
14464     if (pausing) {
14465         DisplayError(_("You can't revert while pausing"), 0);
14466         return;
14467     }
14468     SendToICS(ics_prefix);
14469     SendToICS("revert\n");
14470 }
14471
14472 void
14473 RetractMoveEvent()
14474 {
14475     switch (gameMode) {
14476       case MachinePlaysWhite:
14477       case MachinePlaysBlack:
14478         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14479             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14480             return;
14481         }
14482         if (forwardMostMove < 2) return;
14483         currentMove = forwardMostMove = forwardMostMove - 2;
14484         whiteTimeRemaining = timeRemaining[0][currentMove];
14485         blackTimeRemaining = timeRemaining[1][currentMove];
14486         DisplayBothClocks();
14487         DisplayMove(currentMove - 1);
14488         ClearHighlights();/*!! could figure this out*/
14489         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14490         SendToProgram("remove\n", &first);
14491         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14492         break;
14493
14494       case BeginningOfGame:
14495       default:
14496         break;
14497
14498       case IcsPlayingWhite:
14499       case IcsPlayingBlack:
14500         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14501             SendToICS(ics_prefix);
14502             SendToICS("takeback 2\n");
14503         } else {
14504             SendToICS(ics_prefix);
14505             SendToICS("takeback 1\n");
14506         }
14507         break;
14508     }
14509 }
14510
14511 void
14512 MoveNowEvent()
14513 {
14514     ChessProgramState *cps;
14515
14516     switch (gameMode) {
14517       case MachinePlaysWhite:
14518         if (!WhiteOnMove(forwardMostMove)) {
14519             DisplayError(_("It is your turn"), 0);
14520             return;
14521         }
14522         cps = &first;
14523         break;
14524       case MachinePlaysBlack:
14525         if (WhiteOnMove(forwardMostMove)) {
14526             DisplayError(_("It is your turn"), 0);
14527             return;
14528         }
14529         cps = &first;
14530         break;
14531       case TwoMachinesPlay:
14532         if (WhiteOnMove(forwardMostMove) ==
14533             (first.twoMachinesColor[0] == 'w')) {
14534             cps = &first;
14535         } else {
14536             cps = &second;
14537         }
14538         break;
14539       case BeginningOfGame:
14540       default:
14541         return;
14542     }
14543     SendToProgram("?\n", cps);
14544 }
14545
14546 void
14547 TruncateGameEvent()
14548 {
14549     EditGameEvent();
14550     if (gameMode != EditGame) return;
14551     TruncateGame();
14552 }
14553
14554 void
14555 TruncateGame()
14556 {
14557     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14558     if (forwardMostMove > currentMove) {
14559         if (gameInfo.resultDetails != NULL) {
14560             free(gameInfo.resultDetails);
14561             gameInfo.resultDetails = NULL;
14562             gameInfo.result = GameUnfinished;
14563         }
14564         forwardMostMove = currentMove;
14565         HistorySet(parseList, backwardMostMove, forwardMostMove,
14566                    currentMove-1);
14567     }
14568 }
14569
14570 void
14571 HintEvent()
14572 {
14573     if (appData.noChessProgram) return;
14574     switch (gameMode) {
14575       case MachinePlaysWhite:
14576         if (WhiteOnMove(forwardMostMove)) {
14577             DisplayError(_("Wait until your turn"), 0);
14578             return;
14579         }
14580         break;
14581       case BeginningOfGame:
14582       case MachinePlaysBlack:
14583         if (!WhiteOnMove(forwardMostMove)) {
14584             DisplayError(_("Wait until your turn"), 0);
14585             return;
14586         }
14587         break;
14588       default:
14589         DisplayError(_("No hint available"), 0);
14590         return;
14591     }
14592     SendToProgram("hint\n", &first);
14593     hintRequested = TRUE;
14594 }
14595
14596 void
14597 BookEvent()
14598 {
14599     if (appData.noChessProgram) return;
14600     switch (gameMode) {
14601       case MachinePlaysWhite:
14602         if (WhiteOnMove(forwardMostMove)) {
14603             DisplayError(_("Wait until your turn"), 0);
14604             return;
14605         }
14606         break;
14607       case BeginningOfGame:
14608       case MachinePlaysBlack:
14609         if (!WhiteOnMove(forwardMostMove)) {
14610             DisplayError(_("Wait until your turn"), 0);
14611             return;
14612         }
14613         break;
14614       case EditPosition:
14615         EditPositionDone(TRUE);
14616         break;
14617       case TwoMachinesPlay:
14618         return;
14619       default:
14620         break;
14621     }
14622     SendToProgram("bk\n", &first);
14623     bookOutput[0] = NULLCHAR;
14624     bookRequested = TRUE;
14625 }
14626
14627 void
14628 AboutGameEvent()
14629 {
14630     char *tags = PGNTags(&gameInfo);
14631     TagsPopUp(tags, CmailMsg());
14632     free(tags);
14633 }
14634
14635 /* end button procedures */
14636
14637 void
14638 PrintPosition(fp, move)
14639      FILE *fp;
14640      int move;
14641 {
14642     int i, j;
14643
14644     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14645         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14646             char c = PieceToChar(boards[move][i][j]);
14647             fputc(c == 'x' ? '.' : c, fp);
14648             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14649         }
14650     }
14651     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14652       fprintf(fp, "white to play\n");
14653     else
14654       fprintf(fp, "black to play\n");
14655 }
14656
14657 void
14658 PrintOpponents(fp)
14659      FILE *fp;
14660 {
14661     if (gameInfo.white != NULL) {
14662         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14663     } else {
14664         fprintf(fp, "\n");
14665     }
14666 }
14667
14668 /* Find last component of program's own name, using some heuristics */
14669 void
14670 TidyProgramName(prog, host, buf)
14671      char *prog, *host, buf[MSG_SIZ];
14672 {
14673     char *p, *q;
14674     int local = (strcmp(host, "localhost") == 0);
14675     while (!local && (p = strchr(prog, ';')) != NULL) {
14676         p++;
14677         while (*p == ' ') p++;
14678         prog = p;
14679     }
14680     if (*prog == '"' || *prog == '\'') {
14681         q = strchr(prog + 1, *prog);
14682     } else {
14683         q = strchr(prog, ' ');
14684     }
14685     if (q == NULL) q = prog + strlen(prog);
14686     p = q;
14687     while (p >= prog && *p != '/' && *p != '\\') p--;
14688     p++;
14689     if(p == prog && *p == '"') p++;
14690     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14691     memcpy(buf, p, q - p);
14692     buf[q - p] = NULLCHAR;
14693     if (!local) {
14694         strcat(buf, "@");
14695         strcat(buf, host);
14696     }
14697 }
14698
14699 char *
14700 TimeControlTagValue()
14701 {
14702     char buf[MSG_SIZ];
14703     if (!appData.clockMode) {
14704       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14705     } else if (movesPerSession > 0) {
14706       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14707     } else if (timeIncrement == 0) {
14708       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14709     } else {
14710       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14711     }
14712     return StrSave(buf);
14713 }
14714
14715 void
14716 SetGameInfo()
14717 {
14718     /* This routine is used only for certain modes */
14719     VariantClass v = gameInfo.variant;
14720     ChessMove r = GameUnfinished;
14721     char *p = NULL;
14722
14723     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14724         r = gameInfo.result;
14725         p = gameInfo.resultDetails;
14726         gameInfo.resultDetails = NULL;
14727     }
14728     ClearGameInfo(&gameInfo);
14729     gameInfo.variant = v;
14730
14731     switch (gameMode) {
14732       case MachinePlaysWhite:
14733         gameInfo.event = StrSave( appData.pgnEventHeader );
14734         gameInfo.site = StrSave(HostName());
14735         gameInfo.date = PGNDate();
14736         gameInfo.round = StrSave("-");
14737         gameInfo.white = StrSave(first.tidy);
14738         gameInfo.black = StrSave(UserName());
14739         gameInfo.timeControl = TimeControlTagValue();
14740         break;
14741
14742       case MachinePlaysBlack:
14743         gameInfo.event = StrSave( appData.pgnEventHeader );
14744         gameInfo.site = StrSave(HostName());
14745         gameInfo.date = PGNDate();
14746         gameInfo.round = StrSave("-");
14747         gameInfo.white = StrSave(UserName());
14748         gameInfo.black = StrSave(first.tidy);
14749         gameInfo.timeControl = TimeControlTagValue();
14750         break;
14751
14752       case TwoMachinesPlay:
14753         gameInfo.event = StrSave( appData.pgnEventHeader );
14754         gameInfo.site = StrSave(HostName());
14755         gameInfo.date = PGNDate();
14756         if (roundNr > 0) {
14757             char buf[MSG_SIZ];
14758             snprintf(buf, MSG_SIZ, "%d", roundNr);
14759             gameInfo.round = StrSave(buf);
14760         } else {
14761             gameInfo.round = StrSave("-");
14762         }
14763         if (first.twoMachinesColor[0] == 'w') {
14764             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14765             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14766         } else {
14767             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14768             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14769         }
14770         gameInfo.timeControl = TimeControlTagValue();
14771         break;
14772
14773       case EditGame:
14774         gameInfo.event = StrSave("Edited game");
14775         gameInfo.site = StrSave(HostName());
14776         gameInfo.date = PGNDate();
14777         gameInfo.round = StrSave("-");
14778         gameInfo.white = StrSave("-");
14779         gameInfo.black = StrSave("-");
14780         gameInfo.result = r;
14781         gameInfo.resultDetails = p;
14782         break;
14783
14784       case EditPosition:
14785         gameInfo.event = StrSave("Edited position");
14786         gameInfo.site = StrSave(HostName());
14787         gameInfo.date = PGNDate();
14788         gameInfo.round = StrSave("-");
14789         gameInfo.white = StrSave("-");
14790         gameInfo.black = StrSave("-");
14791         break;
14792
14793       case IcsPlayingWhite:
14794       case IcsPlayingBlack:
14795       case IcsObserving:
14796       case IcsExamining:
14797         break;
14798
14799       case PlayFromGameFile:
14800         gameInfo.event = StrSave("Game from non-PGN file");
14801         gameInfo.site = StrSave(HostName());
14802         gameInfo.date = PGNDate();
14803         gameInfo.round = StrSave("-");
14804         gameInfo.white = StrSave("?");
14805         gameInfo.black = StrSave("?");
14806         break;
14807
14808       default:
14809         break;
14810     }
14811 }
14812
14813 void
14814 ReplaceComment(index, text)
14815      int index;
14816      char *text;
14817 {
14818     int len;
14819     char *p;
14820     float score;
14821
14822     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14823        pvInfoList[index-1].depth == len &&
14824        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14825        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14826     while (*text == '\n') text++;
14827     len = strlen(text);
14828     while (len > 0 && text[len - 1] == '\n') len--;
14829
14830     if (commentList[index] != NULL)
14831       free(commentList[index]);
14832
14833     if (len == 0) {
14834         commentList[index] = NULL;
14835         return;
14836     }
14837   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14838       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14839       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14840     commentList[index] = (char *) malloc(len + 2);
14841     strncpy(commentList[index], text, len);
14842     commentList[index][len] = '\n';
14843     commentList[index][len + 1] = NULLCHAR;
14844   } else {
14845     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14846     char *p;
14847     commentList[index] = (char *) malloc(len + 7);
14848     safeStrCpy(commentList[index], "{\n", 3);
14849     safeStrCpy(commentList[index]+2, text, len+1);
14850     commentList[index][len+2] = NULLCHAR;
14851     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14852     strcat(commentList[index], "\n}\n");
14853   }
14854 }
14855
14856 void
14857 CrushCRs(text)
14858      char *text;
14859 {
14860   char *p = text;
14861   char *q = text;
14862   char ch;
14863
14864   do {
14865     ch = *p++;
14866     if (ch == '\r') continue;
14867     *q++ = ch;
14868   } while (ch != '\0');
14869 }
14870
14871 void
14872 AppendComment(index, text, addBraces)
14873      int index;
14874      char *text;
14875      Boolean addBraces; // [HGM] braces: tells if we should add {}
14876 {
14877     int oldlen, len;
14878     char *old;
14879
14880 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14881     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14882
14883     CrushCRs(text);
14884     while (*text == '\n') text++;
14885     len = strlen(text);
14886     while (len > 0 && text[len - 1] == '\n') len--;
14887
14888     if (len == 0) return;
14889
14890     if (commentList[index] != NULL) {
14891       Boolean addClosingBrace = addBraces;
14892         old = commentList[index];
14893         oldlen = strlen(old);
14894         while(commentList[index][oldlen-1] ==  '\n')
14895           commentList[index][--oldlen] = NULLCHAR;
14896         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14897         safeStrCpy(commentList[index], old, oldlen + len + 6);
14898         free(old);
14899         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14900         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14901           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14902           while (*text == '\n') { text++; len--; }
14903           commentList[index][--oldlen] = NULLCHAR;
14904       }
14905         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14906         else          strcat(commentList[index], "\n");
14907         strcat(commentList[index], text);
14908         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14909         else          strcat(commentList[index], "\n");
14910     } else {
14911         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14912         if(addBraces)
14913           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14914         else commentList[index][0] = NULLCHAR;
14915         strcat(commentList[index], text);
14916         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14917         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14918     }
14919 }
14920
14921 static char * FindStr( char * text, char * sub_text )
14922 {
14923     char * result = strstr( text, sub_text );
14924
14925     if( result != NULL ) {
14926         result += strlen( sub_text );
14927     }
14928
14929     return result;
14930 }
14931
14932 /* [AS] Try to extract PV info from PGN comment */
14933 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14934 char *GetInfoFromComment( int index, char * text )
14935 {
14936     char * sep = text, *p;
14937
14938     if( text != NULL && index > 0 ) {
14939         int score = 0;
14940         int depth = 0;
14941         int time = -1, sec = 0, deci;
14942         char * s_eval = FindStr( text, "[%eval " );
14943         char * s_emt = FindStr( text, "[%emt " );
14944
14945         if( s_eval != NULL || s_emt != NULL ) {
14946             /* New style */
14947             char delim;
14948
14949             if( s_eval != NULL ) {
14950                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14951                     return text;
14952                 }
14953
14954                 if( delim != ']' ) {
14955                     return text;
14956                 }
14957             }
14958
14959             if( s_emt != NULL ) {
14960             }
14961                 return text;
14962         }
14963         else {
14964             /* We expect something like: [+|-]nnn.nn/dd */
14965             int score_lo = 0;
14966
14967             if(*text != '{') return text; // [HGM] braces: must be normal comment
14968
14969             sep = strchr( text, '/' );
14970             if( sep == NULL || sep < (text+4) ) {
14971                 return text;
14972             }
14973
14974             p = text;
14975             if(p[1] == '(') { // comment starts with PV
14976                p = strchr(p, ')'); // locate end of PV
14977                if(p == NULL || sep < p+5) return text;
14978                // at this point we have something like "{(.*) +0.23/6 ..."
14979                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14980                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14981                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14982             }
14983             time = -1; sec = -1; deci = -1;
14984             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14985                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14986                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14987                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14988                 return text;
14989             }
14990
14991             if( score_lo < 0 || score_lo >= 100 ) {
14992                 return text;
14993             }
14994
14995             if(sec >= 0) time = 600*time + 10*sec; else
14996             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14997
14998             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14999
15000             /* [HGM] PV time: now locate end of PV info */
15001             while( *++sep >= '0' && *sep <= '9'); // strip depth
15002             if(time >= 0)
15003             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15004             if(sec >= 0)
15005             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15006             if(deci >= 0)
15007             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15008             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15009         }
15010
15011         if( depth <= 0 ) {
15012             return text;
15013         }
15014
15015         if( time < 0 ) {
15016             time = -1;
15017         }
15018
15019         pvInfoList[index-1].depth = depth;
15020         pvInfoList[index-1].score = score;
15021         pvInfoList[index-1].time  = 10*time; // centi-sec
15022         if(*sep == '}') *sep = 0; else *--sep = '{';
15023         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15024     }
15025     return sep;
15026 }
15027
15028 void
15029 SendToProgram(message, cps)
15030      char *message;
15031      ChessProgramState *cps;
15032 {
15033     int count, outCount, error;
15034     char buf[MSG_SIZ];
15035
15036     if (cps->pr == NoProc) return;
15037     Attention(cps);
15038
15039     if (appData.debugMode) {
15040         TimeMark now;
15041         GetTimeMark(&now);
15042         fprintf(debugFP, "%ld >%-6s: %s",
15043                 SubtractTimeMarks(&now, &programStartTime),
15044                 cps->which, message);
15045     }
15046
15047     count = strlen(message);
15048     outCount = OutputToProcess(cps->pr, message, count, &error);
15049     if (outCount < count && !exiting
15050                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15051       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15052       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15053         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15054             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15055                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15056                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15057                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15058             } else {
15059                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15060                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15061                 gameInfo.result = res;
15062             }
15063             gameInfo.resultDetails = StrSave(buf);
15064         }
15065         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15066         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15067     }
15068 }
15069
15070 void
15071 ReceiveFromProgram(isr, closure, message, count, error)
15072      InputSourceRef isr;
15073      VOIDSTAR closure;
15074      char *message;
15075      int count;
15076      int error;
15077 {
15078     char *end_str;
15079     char buf[MSG_SIZ];
15080     ChessProgramState *cps = (ChessProgramState *)closure;
15081
15082     if (isr != cps->isr) return; /* Killed intentionally */
15083     if (count <= 0) {
15084         if (count == 0) {
15085             RemoveInputSource(cps->isr);
15086             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15087             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15088                     _(cps->which), cps->program);
15089         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15090                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15091                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15092                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15093                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15094                 } else {
15095                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15096                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15097                     gameInfo.result = res;
15098                 }
15099                 gameInfo.resultDetails = StrSave(buf);
15100             }
15101             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15102             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15103         } else {
15104             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15105                     _(cps->which), cps->program);
15106             RemoveInputSource(cps->isr);
15107
15108             /* [AS] Program is misbehaving badly... kill it */
15109             if( count == -2 ) {
15110                 DestroyChildProcess( cps->pr, 9 );
15111                 cps->pr = NoProc;
15112             }
15113
15114             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15115         }
15116         return;
15117     }
15118
15119     if ((end_str = strchr(message, '\r')) != NULL)
15120       *end_str = NULLCHAR;
15121     if ((end_str = strchr(message, '\n')) != NULL)
15122       *end_str = NULLCHAR;
15123
15124     if (appData.debugMode) {
15125         TimeMark now; int print = 1;
15126         char *quote = ""; char c; int i;
15127
15128         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15129                 char start = message[0];
15130                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15131                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15132                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15133                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15134                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15135                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15136                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15137                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15138                    sscanf(message, "hint: %c", &c)!=1 && 
15139                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15140                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15141                     print = (appData.engineComments >= 2);
15142                 }
15143                 message[0] = start; // restore original message
15144         }
15145         if(print) {
15146                 GetTimeMark(&now);
15147                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15148                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15149                         quote,
15150                         message);
15151         }
15152     }
15153
15154     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15155     if (appData.icsEngineAnalyze) {
15156         if (strstr(message, "whisper") != NULL ||
15157              strstr(message, "kibitz") != NULL ||
15158             strstr(message, "tellics") != NULL) return;
15159     }
15160
15161     HandleMachineMove(message, cps);
15162 }
15163
15164
15165 void
15166 SendTimeControl(cps, mps, tc, inc, sd, st)
15167      ChessProgramState *cps;
15168      int mps, inc, sd, st;
15169      long tc;
15170 {
15171     char buf[MSG_SIZ];
15172     int seconds;
15173
15174     if( timeControl_2 > 0 ) {
15175         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15176             tc = timeControl_2;
15177         }
15178     }
15179     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15180     inc /= cps->timeOdds;
15181     st  /= cps->timeOdds;
15182
15183     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15184
15185     if (st > 0) {
15186       /* Set exact time per move, normally using st command */
15187       if (cps->stKludge) {
15188         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15189         seconds = st % 60;
15190         if (seconds == 0) {
15191           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15192         } else {
15193           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15194         }
15195       } else {
15196         snprintf(buf, MSG_SIZ, "st %d\n", st);
15197       }
15198     } else {
15199       /* Set conventional or incremental time control, using level command */
15200       if (seconds == 0) {
15201         /* Note old gnuchess bug -- minutes:seconds used to not work.
15202            Fixed in later versions, but still avoid :seconds
15203            when seconds is 0. */
15204         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15205       } else {
15206         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15207                  seconds, inc/1000.);
15208       }
15209     }
15210     SendToProgram(buf, cps);
15211
15212     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15213     /* Orthogonally, limit search to given depth */
15214     if (sd > 0) {
15215       if (cps->sdKludge) {
15216         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15217       } else {
15218         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15219       }
15220       SendToProgram(buf, cps);
15221     }
15222
15223     if(cps->nps >= 0) { /* [HGM] nps */
15224         if(cps->supportsNPS == FALSE)
15225           cps->nps = -1; // don't use if engine explicitly says not supported!
15226         else {
15227           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15228           SendToProgram(buf, cps);
15229         }
15230     }
15231 }
15232
15233 ChessProgramState *WhitePlayer()
15234 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15235 {
15236     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15237        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15238         return &second;
15239     return &first;
15240 }
15241
15242 void
15243 SendTimeRemaining(cps, machineWhite)
15244      ChessProgramState *cps;
15245      int /*boolean*/ machineWhite;
15246 {
15247     char message[MSG_SIZ];
15248     long time, otime;
15249
15250     /* Note: this routine must be called when the clocks are stopped
15251        or when they have *just* been set or switched; otherwise
15252        it will be off by the time since the current tick started.
15253     */
15254     if (machineWhite) {
15255         time = whiteTimeRemaining / 10;
15256         otime = blackTimeRemaining / 10;
15257     } else {
15258         time = blackTimeRemaining / 10;
15259         otime = whiteTimeRemaining / 10;
15260     }
15261     /* [HGM] translate opponent's time by time-odds factor */
15262     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15263     if (appData.debugMode) {
15264         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15265     }
15266
15267     if (time <= 0) time = 1;
15268     if (otime <= 0) otime = 1;
15269
15270     snprintf(message, MSG_SIZ, "time %ld\n", time);
15271     SendToProgram(message, cps);
15272
15273     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15274     SendToProgram(message, cps);
15275 }
15276
15277 int
15278 BoolFeature(p, name, loc, cps)
15279      char **p;
15280      char *name;
15281      int *loc;
15282      ChessProgramState *cps;
15283 {
15284   char buf[MSG_SIZ];
15285   int len = strlen(name);
15286   int val;
15287
15288   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15289     (*p) += len + 1;
15290     sscanf(*p, "%d", &val);
15291     *loc = (val != 0);
15292     while (**p && **p != ' ')
15293       (*p)++;
15294     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15295     SendToProgram(buf, cps);
15296     return TRUE;
15297   }
15298   return FALSE;
15299 }
15300
15301 int
15302 IntFeature(p, name, loc, cps)
15303      char **p;
15304      char *name;
15305      int *loc;
15306      ChessProgramState *cps;
15307 {
15308   char buf[MSG_SIZ];
15309   int len = strlen(name);
15310   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15311     (*p) += len + 1;
15312     sscanf(*p, "%d", loc);
15313     while (**p && **p != ' ') (*p)++;
15314     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15315     SendToProgram(buf, cps);
15316     return TRUE;
15317   }
15318   return FALSE;
15319 }
15320
15321 int
15322 StringFeature(p, name, loc, cps)
15323      char **p;
15324      char *name;
15325      char loc[];
15326      ChessProgramState *cps;
15327 {
15328   char buf[MSG_SIZ];
15329   int len = strlen(name);
15330   if (strncmp((*p), name, len) == 0
15331       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15332     (*p) += len + 2;
15333     sscanf(*p, "%[^\"]", loc);
15334     while (**p && **p != '\"') (*p)++;
15335     if (**p == '\"') (*p)++;
15336     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15337     SendToProgram(buf, cps);
15338     return TRUE;
15339   }
15340   return FALSE;
15341 }
15342
15343 int
15344 ParseOption(Option *opt, ChessProgramState *cps)
15345 // [HGM] options: process the string that defines an engine option, and determine
15346 // name, type, default value, and allowed value range
15347 {
15348         char *p, *q, buf[MSG_SIZ];
15349         int n, min = (-1)<<31, max = 1<<31, def;
15350
15351         if(p = strstr(opt->name, " -spin ")) {
15352             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15353             if(max < min) max = min; // enforce consistency
15354             if(def < min) def = min;
15355             if(def > max) def = max;
15356             opt->value = def;
15357             opt->min = min;
15358             opt->max = max;
15359             opt->type = Spin;
15360         } else if((p = strstr(opt->name, " -slider "))) {
15361             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15362             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15363             if(max < min) max = min; // enforce consistency
15364             if(def < min) def = min;
15365             if(def > max) def = max;
15366             opt->value = def;
15367             opt->min = min;
15368             opt->max = max;
15369             opt->type = Spin; // Slider;
15370         } else if((p = strstr(opt->name, " -string "))) {
15371             opt->textValue = p+9;
15372             opt->type = TextBox;
15373         } else if((p = strstr(opt->name, " -file "))) {
15374             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15375             opt->textValue = p+7;
15376             opt->type = FileName; // FileName;
15377         } else if((p = strstr(opt->name, " -path "))) {
15378             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15379             opt->textValue = p+7;
15380             opt->type = PathName; // PathName;
15381         } else if(p = strstr(opt->name, " -check ")) {
15382             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15383             opt->value = (def != 0);
15384             opt->type = CheckBox;
15385         } else if(p = strstr(opt->name, " -combo ")) {
15386             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15387             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15388             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15389             opt->value = n = 0;
15390             while(q = StrStr(q, " /// ")) {
15391                 n++; *q = 0;    // count choices, and null-terminate each of them
15392                 q += 5;
15393                 if(*q == '*') { // remember default, which is marked with * prefix
15394                     q++;
15395                     opt->value = n;
15396                 }
15397                 cps->comboList[cps->comboCnt++] = q;
15398             }
15399             cps->comboList[cps->comboCnt++] = NULL;
15400             opt->max = n + 1;
15401             opt->type = ComboBox;
15402         } else if(p = strstr(opt->name, " -button")) {
15403             opt->type = Button;
15404         } else if(p = strstr(opt->name, " -save")) {
15405             opt->type = SaveButton;
15406         } else return FALSE;
15407         *p = 0; // terminate option name
15408         // now look if the command-line options define a setting for this engine option.
15409         if(cps->optionSettings && cps->optionSettings[0])
15410             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15411         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15412           snprintf(buf, MSG_SIZ, "option %s", p);
15413                 if(p = strstr(buf, ",")) *p = 0;
15414                 if(q = strchr(buf, '=')) switch(opt->type) {
15415                     case ComboBox:
15416                         for(n=0; n<opt->max; n++)
15417                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15418                         break;
15419                     case TextBox:
15420                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15421                         break;
15422                     case Spin:
15423                     case CheckBox:
15424                         opt->value = atoi(q+1);
15425                     default:
15426                         break;
15427                 }
15428                 strcat(buf, "\n");
15429                 SendToProgram(buf, cps);
15430         }
15431         return TRUE;
15432 }
15433
15434 void
15435 FeatureDone(cps, val)
15436      ChessProgramState* cps;
15437      int val;
15438 {
15439   DelayedEventCallback cb = GetDelayedEvent();
15440   if ((cb == InitBackEnd3 && cps == &first) ||
15441       (cb == SettingsMenuIfReady && cps == &second) ||
15442       (cb == LoadEngine) ||
15443       (cb == TwoMachinesEventIfReady)) {
15444     CancelDelayedEvent();
15445     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15446   }
15447   cps->initDone = val;
15448 }
15449
15450 /* Parse feature command from engine */
15451 void
15452 ParseFeatures(args, cps)
15453      char* args;
15454      ChessProgramState *cps;
15455 {
15456   char *p = args;
15457   char *q;
15458   int val;
15459   char buf[MSG_SIZ];
15460
15461   for (;;) {
15462     while (*p == ' ') p++;
15463     if (*p == NULLCHAR) return;
15464
15465     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15466     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15467     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15468     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15469     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15470     if (BoolFeature(&p, "reuse", &val, cps)) {
15471       /* Engine can disable reuse, but can't enable it if user said no */
15472       if (!val) cps->reuse = FALSE;
15473       continue;
15474     }
15475     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15476     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15477       if (gameMode == TwoMachinesPlay) {
15478         DisplayTwoMachinesTitle();
15479       } else {
15480         DisplayTitle("");
15481       }
15482       continue;
15483     }
15484     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15485     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15486     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15487     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15488     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15489     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15490     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15491     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15492     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15493     if (IntFeature(&p, "done", &val, cps)) {
15494       FeatureDone(cps, val);
15495       continue;
15496     }
15497     /* Added by Tord: */
15498     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15499     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15500     /* End of additions by Tord */
15501
15502     /* [HGM] added features: */
15503     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15504     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15505     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15506     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15507     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15508     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15509     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15510         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15511           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15512             SendToProgram(buf, cps);
15513             continue;
15514         }
15515         if(cps->nrOptions >= MAX_OPTIONS) {
15516             cps->nrOptions--;
15517             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15518             DisplayError(buf, 0);
15519         }
15520         continue;
15521     }
15522     /* End of additions by HGM */
15523
15524     /* unknown feature: complain and skip */
15525     q = p;
15526     while (*q && *q != '=') q++;
15527     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15528     SendToProgram(buf, cps);
15529     p = q;
15530     if (*p == '=') {
15531       p++;
15532       if (*p == '\"') {
15533         p++;
15534         while (*p && *p != '\"') p++;
15535         if (*p == '\"') p++;
15536       } else {
15537         while (*p && *p != ' ') p++;
15538       }
15539     }
15540   }
15541
15542 }
15543
15544 void
15545 PeriodicUpdatesEvent(newState)
15546      int newState;
15547 {
15548     if (newState == appData.periodicUpdates)
15549       return;
15550
15551     appData.periodicUpdates=newState;
15552
15553     /* Display type changes, so update it now */
15554 //    DisplayAnalysis();
15555
15556     /* Get the ball rolling again... */
15557     if (newState) {
15558         AnalysisPeriodicEvent(1);
15559         StartAnalysisClock();
15560     }
15561 }
15562
15563 void
15564 PonderNextMoveEvent(newState)
15565      int newState;
15566 {
15567     if (newState == appData.ponderNextMove) return;
15568     if (gameMode == EditPosition) EditPositionDone(TRUE);
15569     if (newState) {
15570         SendToProgram("hard\n", &first);
15571         if (gameMode == TwoMachinesPlay) {
15572             SendToProgram("hard\n", &second);
15573         }
15574     } else {
15575         SendToProgram("easy\n", &first);
15576         thinkOutput[0] = NULLCHAR;
15577         if (gameMode == TwoMachinesPlay) {
15578             SendToProgram("easy\n", &second);
15579         }
15580     }
15581     appData.ponderNextMove = newState;
15582 }
15583
15584 void
15585 NewSettingEvent(option, feature, command, value)
15586      char *command;
15587      int option, value, *feature;
15588 {
15589     char buf[MSG_SIZ];
15590
15591     if (gameMode == EditPosition) EditPositionDone(TRUE);
15592     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15593     if(feature == NULL || *feature) SendToProgram(buf, &first);
15594     if (gameMode == TwoMachinesPlay) {
15595         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15596     }
15597 }
15598
15599 void
15600 ShowThinkingEvent()
15601 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15602 {
15603     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15604     int newState = appData.showThinking
15605         // [HGM] thinking: other features now need thinking output as well
15606         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15607
15608     if (oldState == newState) return;
15609     oldState = newState;
15610     if (gameMode == EditPosition) EditPositionDone(TRUE);
15611     if (oldState) {
15612         SendToProgram("post\n", &first);
15613         if (gameMode == TwoMachinesPlay) {
15614             SendToProgram("post\n", &second);
15615         }
15616     } else {
15617         SendToProgram("nopost\n", &first);
15618         thinkOutput[0] = NULLCHAR;
15619         if (gameMode == TwoMachinesPlay) {
15620             SendToProgram("nopost\n", &second);
15621         }
15622     }
15623 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15624 }
15625
15626 void
15627 AskQuestionEvent(title, question, replyPrefix, which)
15628      char *title; char *question; char *replyPrefix; char *which;
15629 {
15630   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15631   if (pr == NoProc) return;
15632   AskQuestion(title, question, replyPrefix, pr);
15633 }
15634
15635 void
15636 TypeInEvent(char firstChar)
15637 {
15638     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15639         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15640         gameMode == AnalyzeMode || gameMode == EditGame || 
15641         gameMode == EditPosition || gameMode == IcsExamining ||
15642         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15643         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15644                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15645                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15646         gameMode == Training) PopUpMoveDialog(firstChar);
15647 }
15648
15649 void
15650 TypeInDoneEvent(char *move)
15651 {
15652         Board board;
15653         int n, fromX, fromY, toX, toY;
15654         char promoChar;
15655         ChessMove moveType;
15656
15657         // [HGM] FENedit
15658         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15659                 EditPositionPasteFEN(move);
15660                 return;
15661         }
15662         // [HGM] movenum: allow move number to be typed in any mode
15663         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15664           ToNrEvent(2*n-1);
15665           return;
15666         }
15667
15668       if (gameMode != EditGame && currentMove != forwardMostMove && 
15669         gameMode != Training) {
15670         DisplayMoveError(_("Displayed move is not current"));
15671       } else {
15672         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15673           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15674         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15675         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15676           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15677           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15678         } else {
15679           DisplayMoveError(_("Could not parse move"));
15680         }
15681       }
15682 }
15683
15684 void
15685 DisplayMove(moveNumber)
15686      int moveNumber;
15687 {
15688     char message[MSG_SIZ];
15689     char res[MSG_SIZ];
15690     char cpThinkOutput[MSG_SIZ];
15691
15692     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15693
15694     if (moveNumber == forwardMostMove - 1 ||
15695         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15696
15697         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15698
15699         if (strchr(cpThinkOutput, '\n')) {
15700             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15701         }
15702     } else {
15703         *cpThinkOutput = NULLCHAR;
15704     }
15705
15706     /* [AS] Hide thinking from human user */
15707     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15708         *cpThinkOutput = NULLCHAR;
15709         if( thinkOutput[0] != NULLCHAR ) {
15710             int i;
15711
15712             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15713                 cpThinkOutput[i] = '.';
15714             }
15715             cpThinkOutput[i] = NULLCHAR;
15716             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15717         }
15718     }
15719
15720     if (moveNumber == forwardMostMove - 1 &&
15721         gameInfo.resultDetails != NULL) {
15722         if (gameInfo.resultDetails[0] == NULLCHAR) {
15723           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15724         } else {
15725           snprintf(res, MSG_SIZ, " {%s} %s",
15726                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15727         }
15728     } else {
15729         res[0] = NULLCHAR;
15730     }
15731
15732     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15733         DisplayMessage(res, cpThinkOutput);
15734     } else {
15735       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15736                 WhiteOnMove(moveNumber) ? " " : ".. ",
15737                 parseList[moveNumber], res);
15738         DisplayMessage(message, cpThinkOutput);
15739     }
15740 }
15741
15742 void
15743 DisplayComment(moveNumber, text)
15744      int moveNumber;
15745      char *text;
15746 {
15747     char title[MSG_SIZ];
15748
15749     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15750       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15751     } else {
15752       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15753               WhiteOnMove(moveNumber) ? " " : ".. ",
15754               parseList[moveNumber]);
15755     }
15756     if (text != NULL && (appData.autoDisplayComment || commentUp))
15757         CommentPopUp(title, text);
15758 }
15759
15760 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15761  * might be busy thinking or pondering.  It can be omitted if your
15762  * gnuchess is configured to stop thinking immediately on any user
15763  * input.  However, that gnuchess feature depends on the FIONREAD
15764  * ioctl, which does not work properly on some flavors of Unix.
15765  */
15766 void
15767 Attention(cps)
15768      ChessProgramState *cps;
15769 {
15770 #if ATTENTION
15771     if (!cps->useSigint) return;
15772     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15773     switch (gameMode) {
15774       case MachinePlaysWhite:
15775       case MachinePlaysBlack:
15776       case TwoMachinesPlay:
15777       case IcsPlayingWhite:
15778       case IcsPlayingBlack:
15779       case AnalyzeMode:
15780       case AnalyzeFile:
15781         /* Skip if we know it isn't thinking */
15782         if (!cps->maybeThinking) return;
15783         if (appData.debugMode)
15784           fprintf(debugFP, "Interrupting %s\n", cps->which);
15785         InterruptChildProcess(cps->pr);
15786         cps->maybeThinking = FALSE;
15787         break;
15788       default:
15789         break;
15790     }
15791 #endif /*ATTENTION*/
15792 }
15793
15794 int
15795 CheckFlags()
15796 {
15797     if (whiteTimeRemaining <= 0) {
15798         if (!whiteFlag) {
15799             whiteFlag = TRUE;
15800             if (appData.icsActive) {
15801                 if (appData.autoCallFlag &&
15802                     gameMode == IcsPlayingBlack && !blackFlag) {
15803                   SendToICS(ics_prefix);
15804                   SendToICS("flag\n");
15805                 }
15806             } else {
15807                 if (blackFlag) {
15808                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15809                 } else {
15810                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15811                     if (appData.autoCallFlag) {
15812                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15813                         return TRUE;
15814                     }
15815                 }
15816             }
15817         }
15818     }
15819     if (blackTimeRemaining <= 0) {
15820         if (!blackFlag) {
15821             blackFlag = TRUE;
15822             if (appData.icsActive) {
15823                 if (appData.autoCallFlag &&
15824                     gameMode == IcsPlayingWhite && !whiteFlag) {
15825                   SendToICS(ics_prefix);
15826                   SendToICS("flag\n");
15827                 }
15828             } else {
15829                 if (whiteFlag) {
15830                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15831                 } else {
15832                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15833                     if (appData.autoCallFlag) {
15834                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15835                         return TRUE;
15836                     }
15837                 }
15838             }
15839         }
15840     }
15841     return FALSE;
15842 }
15843
15844 void
15845 CheckTimeControl()
15846 {
15847     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15848         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15849
15850     /*
15851      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15852      */
15853     if ( !WhiteOnMove(forwardMostMove) ) {
15854         /* White made time control */
15855         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15856         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15857         /* [HGM] time odds: correct new time quota for time odds! */
15858                                             / WhitePlayer()->timeOdds;
15859         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15860     } else {
15861         lastBlack -= blackTimeRemaining;
15862         /* Black made time control */
15863         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15864                                             / WhitePlayer()->other->timeOdds;
15865         lastWhite = whiteTimeRemaining;
15866     }
15867 }
15868
15869 void
15870 DisplayBothClocks()
15871 {
15872     int wom = gameMode == EditPosition ?
15873       !blackPlaysFirst : WhiteOnMove(currentMove);
15874     DisplayWhiteClock(whiteTimeRemaining, wom);
15875     DisplayBlackClock(blackTimeRemaining, !wom);
15876 }
15877
15878
15879 /* Timekeeping seems to be a portability nightmare.  I think everyone
15880    has ftime(), but I'm really not sure, so I'm including some ifdefs
15881    to use other calls if you don't.  Clocks will be less accurate if
15882    you have neither ftime nor gettimeofday.
15883 */
15884
15885 /* VS 2008 requires the #include outside of the function */
15886 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15887 #include <sys/timeb.h>
15888 #endif
15889
15890 /* Get the current time as a TimeMark */
15891 void
15892 GetTimeMark(tm)
15893      TimeMark *tm;
15894 {
15895 #if HAVE_GETTIMEOFDAY
15896
15897     struct timeval timeVal;
15898     struct timezone timeZone;
15899
15900     gettimeofday(&timeVal, &timeZone);
15901     tm->sec = (long) timeVal.tv_sec;
15902     tm->ms = (int) (timeVal.tv_usec / 1000L);
15903
15904 #else /*!HAVE_GETTIMEOFDAY*/
15905 #if HAVE_FTIME
15906
15907 // include <sys/timeb.h> / moved to just above start of function
15908     struct timeb timeB;
15909
15910     ftime(&timeB);
15911     tm->sec = (long) timeB.time;
15912     tm->ms = (int) timeB.millitm;
15913
15914 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15915     tm->sec = (long) time(NULL);
15916     tm->ms = 0;
15917 #endif
15918 #endif
15919 }
15920
15921 /* Return the difference in milliseconds between two
15922    time marks.  We assume the difference will fit in a long!
15923 */
15924 long
15925 SubtractTimeMarks(tm2, tm1)
15926      TimeMark *tm2, *tm1;
15927 {
15928     return 1000L*(tm2->sec - tm1->sec) +
15929            (long) (tm2->ms - tm1->ms);
15930 }
15931
15932
15933 /*
15934  * Code to manage the game clocks.
15935  *
15936  * In tournament play, black starts the clock and then white makes a move.
15937  * We give the human user a slight advantage if he is playing white---the
15938  * clocks don't run until he makes his first move, so it takes zero time.
15939  * Also, we don't account for network lag, so we could get out of sync
15940  * with GNU Chess's clock -- but then, referees are always right.
15941  */
15942
15943 static TimeMark tickStartTM;
15944 static long intendedTickLength;
15945
15946 long
15947 NextTickLength(timeRemaining)
15948      long timeRemaining;
15949 {
15950     long nominalTickLength, nextTickLength;
15951
15952     if (timeRemaining > 0L && timeRemaining <= 10000L)
15953       nominalTickLength = 100L;
15954     else
15955       nominalTickLength = 1000L;
15956     nextTickLength = timeRemaining % nominalTickLength;
15957     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15958
15959     return nextTickLength;
15960 }
15961
15962 /* Adjust clock one minute up or down */
15963 void
15964 AdjustClock(Boolean which, int dir)
15965 {
15966     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15967     if(which) blackTimeRemaining += 60000*dir;
15968     else      whiteTimeRemaining += 60000*dir;
15969     DisplayBothClocks();
15970     adjustedClock = TRUE;
15971 }
15972
15973 /* Stop clocks and reset to a fresh time control */
15974 void
15975 ResetClocks()
15976 {
15977     (void) StopClockTimer();
15978     if (appData.icsActive) {
15979         whiteTimeRemaining = blackTimeRemaining = 0;
15980     } else if (searchTime) {
15981         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15982         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15983     } else { /* [HGM] correct new time quote for time odds */
15984         whiteTC = blackTC = fullTimeControlString;
15985         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15986         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15987     }
15988     if (whiteFlag || blackFlag) {
15989         DisplayTitle("");
15990         whiteFlag = blackFlag = FALSE;
15991     }
15992     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15993     DisplayBothClocks();
15994     adjustedClock = FALSE;
15995 }
15996
15997 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15998
15999 /* Decrement running clock by amount of time that has passed */
16000 void
16001 DecrementClocks()
16002 {
16003     long timeRemaining;
16004     long lastTickLength, fudge;
16005     TimeMark now;
16006
16007     if (!appData.clockMode) return;
16008     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16009
16010     GetTimeMark(&now);
16011
16012     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16013
16014     /* Fudge if we woke up a little too soon */
16015     fudge = intendedTickLength - lastTickLength;
16016     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16017
16018     if (WhiteOnMove(forwardMostMove)) {
16019         if(whiteNPS >= 0) lastTickLength = 0;
16020         timeRemaining = whiteTimeRemaining -= lastTickLength;
16021         if(timeRemaining < 0 && !appData.icsActive) {
16022             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16023             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16024                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16025                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16026             }
16027         }
16028         DisplayWhiteClock(whiteTimeRemaining - fudge,
16029                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16030     } else {
16031         if(blackNPS >= 0) lastTickLength = 0;
16032         timeRemaining = blackTimeRemaining -= lastTickLength;
16033         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16034             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16035             if(suddenDeath) {
16036                 blackStartMove = forwardMostMove;
16037                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16038             }
16039         }
16040         DisplayBlackClock(blackTimeRemaining - fudge,
16041                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16042     }
16043     if (CheckFlags()) return;
16044
16045     tickStartTM = now;
16046     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16047     StartClockTimer(intendedTickLength);
16048
16049     /* if the time remaining has fallen below the alarm threshold, sound the
16050      * alarm. if the alarm has sounded and (due to a takeback or time control
16051      * with increment) the time remaining has increased to a level above the
16052      * threshold, reset the alarm so it can sound again.
16053      */
16054
16055     if (appData.icsActive && appData.icsAlarm) {
16056
16057         /* make sure we are dealing with the user's clock */
16058         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16059                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16060            )) return;
16061
16062         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16063             alarmSounded = FALSE;
16064         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16065             PlayAlarmSound();
16066             alarmSounded = TRUE;
16067         }
16068     }
16069 }
16070
16071
16072 /* A player has just moved, so stop the previously running
16073    clock and (if in clock mode) start the other one.
16074    We redisplay both clocks in case we're in ICS mode, because
16075    ICS gives us an update to both clocks after every move.
16076    Note that this routine is called *after* forwardMostMove
16077    is updated, so the last fractional tick must be subtracted
16078    from the color that is *not* on move now.
16079 */
16080 void
16081 SwitchClocks(int newMoveNr)
16082 {
16083     long lastTickLength;
16084     TimeMark now;
16085     int flagged = FALSE;
16086
16087     GetTimeMark(&now);
16088
16089     if (StopClockTimer() && appData.clockMode) {
16090         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16091         if (!WhiteOnMove(forwardMostMove)) {
16092             if(blackNPS >= 0) lastTickLength = 0;
16093             blackTimeRemaining -= lastTickLength;
16094            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16095 //         if(pvInfoList[forwardMostMove].time == -1)
16096                  pvInfoList[forwardMostMove].time =               // use GUI time
16097                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16098         } else {
16099            if(whiteNPS >= 0) lastTickLength = 0;
16100            whiteTimeRemaining -= lastTickLength;
16101            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16102 //         if(pvInfoList[forwardMostMove].time == -1)
16103                  pvInfoList[forwardMostMove].time =
16104                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16105         }
16106         flagged = CheckFlags();
16107     }
16108     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16109     CheckTimeControl();
16110
16111     if (flagged || !appData.clockMode) return;
16112
16113     switch (gameMode) {
16114       case MachinePlaysBlack:
16115       case MachinePlaysWhite:
16116       case BeginningOfGame:
16117         if (pausing) return;
16118         break;
16119
16120       case EditGame:
16121       case PlayFromGameFile:
16122       case IcsExamining:
16123         return;
16124
16125       default:
16126         break;
16127     }
16128
16129     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16130         if(WhiteOnMove(forwardMostMove))
16131              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16132         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16133     }
16134
16135     tickStartTM = now;
16136     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16137       whiteTimeRemaining : blackTimeRemaining);
16138     StartClockTimer(intendedTickLength);
16139 }
16140
16141
16142 /* Stop both clocks */
16143 void
16144 StopClocks()
16145 {
16146     long lastTickLength;
16147     TimeMark now;
16148
16149     if (!StopClockTimer()) return;
16150     if (!appData.clockMode) return;
16151
16152     GetTimeMark(&now);
16153
16154     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16155     if (WhiteOnMove(forwardMostMove)) {
16156         if(whiteNPS >= 0) lastTickLength = 0;
16157         whiteTimeRemaining -= lastTickLength;
16158         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16159     } else {
16160         if(blackNPS >= 0) lastTickLength = 0;
16161         blackTimeRemaining -= lastTickLength;
16162         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16163     }
16164     CheckFlags();
16165 }
16166
16167 /* Start clock of player on move.  Time may have been reset, so
16168    if clock is already running, stop and restart it. */
16169 void
16170 StartClocks()
16171 {
16172     (void) StopClockTimer(); /* in case it was running already */
16173     DisplayBothClocks();
16174     if (CheckFlags()) return;
16175
16176     if (!appData.clockMode) return;
16177     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16178
16179     GetTimeMark(&tickStartTM);
16180     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16181       whiteTimeRemaining : blackTimeRemaining);
16182
16183    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16184     whiteNPS = blackNPS = -1;
16185     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16186        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16187         whiteNPS = first.nps;
16188     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16189        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16190         blackNPS = first.nps;
16191     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16192         whiteNPS = second.nps;
16193     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16194         blackNPS = second.nps;
16195     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16196
16197     StartClockTimer(intendedTickLength);
16198 }
16199
16200 char *
16201 TimeString(ms)
16202      long ms;
16203 {
16204     long second, minute, hour, day;
16205     char *sign = "";
16206     static char buf[32];
16207
16208     if (ms > 0 && ms <= 9900) {
16209       /* convert milliseconds to tenths, rounding up */
16210       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16211
16212       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16213       return buf;
16214     }
16215
16216     /* convert milliseconds to seconds, rounding up */
16217     /* use floating point to avoid strangeness of integer division
16218        with negative dividends on many machines */
16219     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16220
16221     if (second < 0) {
16222         sign = "-";
16223         second = -second;
16224     }
16225
16226     day = second / (60 * 60 * 24);
16227     second = second % (60 * 60 * 24);
16228     hour = second / (60 * 60);
16229     second = second % (60 * 60);
16230     minute = second / 60;
16231     second = second % 60;
16232
16233     if (day > 0)
16234       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16235               sign, day, hour, minute, second);
16236     else if (hour > 0)
16237       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16238     else
16239       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16240
16241     return buf;
16242 }
16243
16244
16245 /*
16246  * This is necessary because some C libraries aren't ANSI C compliant yet.
16247  */
16248 char *
16249 StrStr(string, match)
16250      char *string, *match;
16251 {
16252     int i, length;
16253
16254     length = strlen(match);
16255
16256     for (i = strlen(string) - length; i >= 0; i--, string++)
16257       if (!strncmp(match, string, length))
16258         return string;
16259
16260     return NULL;
16261 }
16262
16263 char *
16264 StrCaseStr(string, match)
16265      char *string, *match;
16266 {
16267     int i, j, length;
16268
16269     length = strlen(match);
16270
16271     for (i = strlen(string) - length; i >= 0; i--, string++) {
16272         for (j = 0; j < length; j++) {
16273             if (ToLower(match[j]) != ToLower(string[j]))
16274               break;
16275         }
16276         if (j == length) return string;
16277     }
16278
16279     return NULL;
16280 }
16281
16282 #ifndef _amigados
16283 int
16284 StrCaseCmp(s1, s2)
16285      char *s1, *s2;
16286 {
16287     char c1, c2;
16288
16289     for (;;) {
16290         c1 = ToLower(*s1++);
16291         c2 = ToLower(*s2++);
16292         if (c1 > c2) return 1;
16293         if (c1 < c2) return -1;
16294         if (c1 == NULLCHAR) return 0;
16295     }
16296 }
16297
16298
16299 int
16300 ToLower(c)
16301      int c;
16302 {
16303     return isupper(c) ? tolower(c) : c;
16304 }
16305
16306
16307 int
16308 ToUpper(c)
16309      int c;
16310 {
16311     return islower(c) ? toupper(c) : c;
16312 }
16313 #endif /* !_amigados    */
16314
16315 char *
16316 StrSave(s)
16317      char *s;
16318 {
16319   char *ret;
16320
16321   if ((ret = (char *) malloc(strlen(s) + 1)))
16322     {
16323       safeStrCpy(ret, s, strlen(s)+1);
16324     }
16325   return ret;
16326 }
16327
16328 char *
16329 StrSavePtr(s, savePtr)
16330      char *s, **savePtr;
16331 {
16332     if (*savePtr) {
16333         free(*savePtr);
16334     }
16335     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16336       safeStrCpy(*savePtr, s, strlen(s)+1);
16337     }
16338     return(*savePtr);
16339 }
16340
16341 char *
16342 PGNDate()
16343 {
16344     time_t clock;
16345     struct tm *tm;
16346     char buf[MSG_SIZ];
16347
16348     clock = time((time_t *)NULL);
16349     tm = localtime(&clock);
16350     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16351             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16352     return StrSave(buf);
16353 }
16354
16355
16356 char *
16357 PositionToFEN(move, overrideCastling)
16358      int move;
16359      char *overrideCastling;
16360 {
16361     int i, j, fromX, fromY, toX, toY;
16362     int whiteToPlay;
16363     char buf[MSG_SIZ];
16364     char *p, *q;
16365     int emptycount;
16366     ChessSquare piece;
16367
16368     whiteToPlay = (gameMode == EditPosition) ?
16369       !blackPlaysFirst : (move % 2 == 0);
16370     p = buf;
16371
16372     /* Piece placement data */
16373     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16374         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16375         emptycount = 0;
16376         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16377             if (boards[move][i][j] == EmptySquare) {
16378                 emptycount++;
16379             } else { ChessSquare piece = boards[move][i][j];
16380                 if (emptycount > 0) {
16381                     if(emptycount<10) /* [HGM] can be >= 10 */
16382                         *p++ = '0' + emptycount;
16383                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16384                     emptycount = 0;
16385                 }
16386                 if(PieceToChar(piece) == '+') {
16387                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16388                     *p++ = '+';
16389                     piece = (ChessSquare)(DEMOTED piece);
16390                 }
16391                 *p++ = PieceToChar(piece);
16392                 if(p[-1] == '~') {
16393                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16394                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16395                     *p++ = '~';
16396                 }
16397             }
16398         }
16399         if (emptycount > 0) {
16400             if(emptycount<10) /* [HGM] can be >= 10 */
16401                 *p++ = '0' + emptycount;
16402             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16403             emptycount = 0;
16404         }
16405         *p++ = '/';
16406     }
16407     *(p - 1) = ' ';
16408
16409     /* [HGM] print Crazyhouse or Shogi holdings */
16410     if( gameInfo.holdingsWidth ) {
16411         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16412         q = p;
16413         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16414             piece = boards[move][i][BOARD_WIDTH-1];
16415             if( piece != EmptySquare )
16416               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16417                   *p++ = PieceToChar(piece);
16418         }
16419         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16420             piece = boards[move][BOARD_HEIGHT-i-1][0];
16421             if( piece != EmptySquare )
16422               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16423                   *p++ = PieceToChar(piece);
16424         }
16425
16426         if( q == p ) *p++ = '-';
16427         *p++ = ']';
16428         *p++ = ' ';
16429     }
16430
16431     /* Active color */
16432     *p++ = whiteToPlay ? 'w' : 'b';
16433     *p++ = ' ';
16434
16435   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16436     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16437   } else {
16438   if(nrCastlingRights) {
16439      q = p;
16440      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16441        /* [HGM] write directly from rights */
16442            if(boards[move][CASTLING][2] != NoRights &&
16443               boards[move][CASTLING][0] != NoRights   )
16444                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16445            if(boards[move][CASTLING][2] != NoRights &&
16446               boards[move][CASTLING][1] != NoRights   )
16447                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16448            if(boards[move][CASTLING][5] != NoRights &&
16449               boards[move][CASTLING][3] != NoRights   )
16450                 *p++ = boards[move][CASTLING][3] + AAA;
16451            if(boards[move][CASTLING][5] != NoRights &&
16452               boards[move][CASTLING][4] != NoRights   )
16453                 *p++ = boards[move][CASTLING][4] + AAA;
16454      } else {
16455
16456         /* [HGM] write true castling rights */
16457         if( nrCastlingRights == 6 ) {
16458             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16459                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16460             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16461                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16462             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16463                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16464             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16465                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16466         }
16467      }
16468      if (q == p) *p++ = '-'; /* No castling rights */
16469      *p++ = ' ';
16470   }
16471
16472   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16473      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16474     /* En passant target square */
16475     if (move > backwardMostMove) {
16476         fromX = moveList[move - 1][0] - AAA;
16477         fromY = moveList[move - 1][1] - ONE;
16478         toX = moveList[move - 1][2] - AAA;
16479         toY = moveList[move - 1][3] - ONE;
16480         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16481             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16482             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16483             fromX == toX) {
16484             /* 2-square pawn move just happened */
16485             *p++ = toX + AAA;
16486             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16487         } else {
16488             *p++ = '-';
16489         }
16490     } else if(move == backwardMostMove) {
16491         // [HGM] perhaps we should always do it like this, and forget the above?
16492         if((signed char)boards[move][EP_STATUS] >= 0) {
16493             *p++ = boards[move][EP_STATUS] + AAA;
16494             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16495         } else {
16496             *p++ = '-';
16497         }
16498     } else {
16499         *p++ = '-';
16500     }
16501     *p++ = ' ';
16502   }
16503   }
16504
16505     /* [HGM] find reversible plies */
16506     {   int i = 0, j=move;
16507
16508         if (appData.debugMode) { int k;
16509             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16510             for(k=backwardMostMove; k<=forwardMostMove; k++)
16511                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16512
16513         }
16514
16515         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16516         if( j == backwardMostMove ) i += initialRulePlies;
16517         sprintf(p, "%d ", i);
16518         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16519     }
16520     /* Fullmove number */
16521     sprintf(p, "%d", (move / 2) + 1);
16522
16523     return StrSave(buf);
16524 }
16525
16526 Boolean
16527 ParseFEN(board, blackPlaysFirst, fen)
16528     Board board;
16529      int *blackPlaysFirst;
16530      char *fen;
16531 {
16532     int i, j;
16533     char *p, c;
16534     int emptycount;
16535     ChessSquare piece;
16536
16537     p = fen;
16538
16539     /* [HGM] by default clear Crazyhouse holdings, if present */
16540     if(gameInfo.holdingsWidth) {
16541        for(i=0; i<BOARD_HEIGHT; i++) {
16542            board[i][0]             = EmptySquare; /* black holdings */
16543            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16544            board[i][1]             = (ChessSquare) 0; /* black counts */
16545            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16546        }
16547     }
16548
16549     /* Piece placement data */
16550     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16551         j = 0;
16552         for (;;) {
16553             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16554                 if (*p == '/') p++;
16555                 emptycount = gameInfo.boardWidth - j;
16556                 while (emptycount--)
16557                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16558                 break;
16559 #if(BOARD_FILES >= 10)
16560             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16561                 p++; emptycount=10;
16562                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16563                 while (emptycount--)
16564                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16565 #endif
16566             } else if (isdigit(*p)) {
16567                 emptycount = *p++ - '0';
16568                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16569                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16570                 while (emptycount--)
16571                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16572             } else if (*p == '+' || isalpha(*p)) {
16573                 if (j >= gameInfo.boardWidth) return FALSE;
16574                 if(*p=='+') {
16575                     piece = CharToPiece(*++p);
16576                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16577                     piece = (ChessSquare) (PROMOTED piece ); p++;
16578                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16579                 } else piece = CharToPiece(*p++);
16580
16581                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16582                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16583                     piece = (ChessSquare) (PROMOTED piece);
16584                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16585                     p++;
16586                 }
16587                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16588             } else {
16589                 return FALSE;
16590             }
16591         }
16592     }
16593     while (*p == '/' || *p == ' ') p++;
16594
16595     /* [HGM] look for Crazyhouse holdings here */
16596     while(*p==' ') p++;
16597     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16598         if(*p == '[') p++;
16599         if(*p == '-' ) p++; /* empty holdings */ else {
16600             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16601             /* if we would allow FEN reading to set board size, we would   */
16602             /* have to add holdings and shift the board read so far here   */
16603             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16604                 p++;
16605                 if((int) piece >= (int) BlackPawn ) {
16606                     i = (int)piece - (int)BlackPawn;
16607                     i = PieceToNumber((ChessSquare)i);
16608                     if( i >= gameInfo.holdingsSize ) return FALSE;
16609                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16610                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16611                 } else {
16612                     i = (int)piece - (int)WhitePawn;
16613                     i = PieceToNumber((ChessSquare)i);
16614                     if( i >= gameInfo.holdingsSize ) return FALSE;
16615                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16616                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16617                 }
16618             }
16619         }
16620         if(*p == ']') p++;
16621     }
16622
16623     while(*p == ' ') p++;
16624
16625     /* Active color */
16626     c = *p++;
16627     if(appData.colorNickNames) {
16628       if( c == appData.colorNickNames[0] ) c = 'w'; else
16629       if( c == appData.colorNickNames[1] ) c = 'b';
16630     }
16631     switch (c) {
16632       case 'w':
16633         *blackPlaysFirst = FALSE;
16634         break;
16635       case 'b':
16636         *blackPlaysFirst = TRUE;
16637         break;
16638       default:
16639         return FALSE;
16640     }
16641
16642     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16643     /* return the extra info in global variiables             */
16644
16645     /* set defaults in case FEN is incomplete */
16646     board[EP_STATUS] = EP_UNKNOWN;
16647     for(i=0; i<nrCastlingRights; i++ ) {
16648         board[CASTLING][i] =
16649             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16650     }   /* assume possible unless obviously impossible */
16651     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16652     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16653     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16654                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16655     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16656     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16657     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16658                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16659     FENrulePlies = 0;
16660
16661     while(*p==' ') p++;
16662     if(nrCastlingRights) {
16663       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16664           /* castling indicator present, so default becomes no castlings */
16665           for(i=0; i<nrCastlingRights; i++ ) {
16666                  board[CASTLING][i] = NoRights;
16667           }
16668       }
16669       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16670              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16671              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16672              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16673         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16674
16675         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16676             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16677             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16678         }
16679         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16680             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16681         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16682                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16683         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16684                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16685         switch(c) {
16686           case'K':
16687               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16688               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16689               board[CASTLING][2] = whiteKingFile;
16690               break;
16691           case'Q':
16692               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16693               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16694               board[CASTLING][2] = whiteKingFile;
16695               break;
16696           case'k':
16697               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16698               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16699               board[CASTLING][5] = blackKingFile;
16700               break;
16701           case'q':
16702               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16703               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16704               board[CASTLING][5] = blackKingFile;
16705           case '-':
16706               break;
16707           default: /* FRC castlings */
16708               if(c >= 'a') { /* black rights */
16709                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16710                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16711                   if(i == BOARD_RGHT) break;
16712                   board[CASTLING][5] = i;
16713                   c -= AAA;
16714                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16715                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16716                   if(c > i)
16717                       board[CASTLING][3] = c;
16718                   else
16719                       board[CASTLING][4] = c;
16720               } else { /* white rights */
16721                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16722                     if(board[0][i] == WhiteKing) break;
16723                   if(i == BOARD_RGHT) break;
16724                   board[CASTLING][2] = i;
16725                   c -= AAA - 'a' + 'A';
16726                   if(board[0][c] >= WhiteKing) break;
16727                   if(c > i)
16728                       board[CASTLING][0] = c;
16729                   else
16730                       board[CASTLING][1] = c;
16731               }
16732         }
16733       }
16734       for(i=0; i<nrCastlingRights; i++)
16735         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16736     if (appData.debugMode) {
16737         fprintf(debugFP, "FEN castling rights:");
16738         for(i=0; i<nrCastlingRights; i++)
16739         fprintf(debugFP, " %d", board[CASTLING][i]);
16740         fprintf(debugFP, "\n");
16741     }
16742
16743       while(*p==' ') p++;
16744     }
16745
16746     /* read e.p. field in games that know e.p. capture */
16747     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16748        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16749       if(*p=='-') {
16750         p++; board[EP_STATUS] = EP_NONE;
16751       } else {
16752          char c = *p++ - AAA;
16753
16754          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16755          if(*p >= '0' && *p <='9') p++;
16756          board[EP_STATUS] = c;
16757       }
16758     }
16759
16760
16761     if(sscanf(p, "%d", &i) == 1) {
16762         FENrulePlies = i; /* 50-move ply counter */
16763         /* (The move number is still ignored)    */
16764     }
16765
16766     return TRUE;
16767 }
16768
16769 void
16770 EditPositionPasteFEN(char *fen)
16771 {
16772   if (fen != NULL) {
16773     Board initial_position;
16774
16775     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16776       DisplayError(_("Bad FEN position in clipboard"), 0);
16777       return ;
16778     } else {
16779       int savedBlackPlaysFirst = blackPlaysFirst;
16780       EditPositionEvent();
16781       blackPlaysFirst = savedBlackPlaysFirst;
16782       CopyBoard(boards[0], initial_position);
16783       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16784       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16785       DisplayBothClocks();
16786       DrawPosition(FALSE, boards[currentMove]);
16787     }
16788   }
16789 }
16790
16791 static char cseq[12] = "\\   ";
16792
16793 Boolean set_cont_sequence(char *new_seq)
16794 {
16795     int len;
16796     Boolean ret;
16797
16798     // handle bad attempts to set the sequence
16799         if (!new_seq)
16800                 return 0; // acceptable error - no debug
16801
16802     len = strlen(new_seq);
16803     ret = (len > 0) && (len < sizeof(cseq));
16804     if (ret)
16805       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16806     else if (appData.debugMode)
16807       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16808     return ret;
16809 }
16810
16811 /*
16812     reformat a source message so words don't cross the width boundary.  internal
16813     newlines are not removed.  returns the wrapped size (no null character unless
16814     included in source message).  If dest is NULL, only calculate the size required
16815     for the dest buffer.  lp argument indicats line position upon entry, and it's
16816     passed back upon exit.
16817 */
16818 int wrap(char *dest, char *src, int count, int width, int *lp)
16819 {
16820     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16821
16822     cseq_len = strlen(cseq);
16823     old_line = line = *lp;
16824     ansi = len = clen = 0;
16825
16826     for (i=0; i < count; i++)
16827     {
16828         if (src[i] == '\033')
16829             ansi = 1;
16830
16831         // if we hit the width, back up
16832         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16833         {
16834             // store i & len in case the word is too long
16835             old_i = i, old_len = len;
16836
16837             // find the end of the last word
16838             while (i && src[i] != ' ' && src[i] != '\n')
16839             {
16840                 i--;
16841                 len--;
16842             }
16843
16844             // word too long?  restore i & len before splitting it
16845             if ((old_i-i+clen) >= width)
16846             {
16847                 i = old_i;
16848                 len = old_len;
16849             }
16850
16851             // extra space?
16852             if (i && src[i-1] == ' ')
16853                 len--;
16854
16855             if (src[i] != ' ' && src[i] != '\n')
16856             {
16857                 i--;
16858                 if (len)
16859                     len--;
16860             }
16861
16862             // now append the newline and continuation sequence
16863             if (dest)
16864                 dest[len] = '\n';
16865             len++;
16866             if (dest)
16867                 strncpy(dest+len, cseq, cseq_len);
16868             len += cseq_len;
16869             line = cseq_len;
16870             clen = cseq_len;
16871             continue;
16872         }
16873
16874         if (dest)
16875             dest[len] = src[i];
16876         len++;
16877         if (!ansi)
16878             line++;
16879         if (src[i] == '\n')
16880             line = 0;
16881         if (src[i] == 'm')
16882             ansi = 0;
16883     }
16884     if (dest && appData.debugMode)
16885     {
16886         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16887             count, width, line, len, *lp);
16888         show_bytes(debugFP, src, count);
16889         fprintf(debugFP, "\ndest: ");
16890         show_bytes(debugFP, dest, len);
16891         fprintf(debugFP, "\n");
16892     }
16893     *lp = dest ? line : old_line;
16894
16895     return len;
16896 }
16897
16898 // [HGM] vari: routines for shelving variations
16899 Boolean modeRestore = FALSE;
16900
16901 void
16902 PushInner(int firstMove, int lastMove)
16903 {
16904         int i, j, nrMoves = lastMove - firstMove;
16905
16906         // push current tail of game on stack
16907         savedResult[storedGames] = gameInfo.result;
16908         savedDetails[storedGames] = gameInfo.resultDetails;
16909         gameInfo.resultDetails = NULL;
16910         savedFirst[storedGames] = firstMove;
16911         savedLast [storedGames] = lastMove;
16912         savedFramePtr[storedGames] = framePtr;
16913         framePtr -= nrMoves; // reserve space for the boards
16914         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16915             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16916             for(j=0; j<MOVE_LEN; j++)
16917                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16918             for(j=0; j<2*MOVE_LEN; j++)
16919                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16920             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16921             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16922             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16923             pvInfoList[firstMove+i-1].depth = 0;
16924             commentList[framePtr+i] = commentList[firstMove+i];
16925             commentList[firstMove+i] = NULL;
16926         }
16927
16928         storedGames++;
16929         forwardMostMove = firstMove; // truncate game so we can start variation
16930 }
16931
16932 void
16933 PushTail(int firstMove, int lastMove)
16934 {
16935         if(appData.icsActive) { // only in local mode
16936                 forwardMostMove = currentMove; // mimic old ICS behavior
16937                 return;
16938         }
16939         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16940
16941         PushInner(firstMove, lastMove);
16942         if(storedGames == 1) GreyRevert(FALSE);
16943         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16944 }
16945
16946 void
16947 PopInner(Boolean annotate)
16948 {
16949         int i, j, nrMoves;
16950         char buf[8000], moveBuf[20];
16951
16952         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16953         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16954         nrMoves = savedLast[storedGames] - currentMove;
16955         if(annotate) {
16956                 int cnt = 10;
16957                 if(!WhiteOnMove(currentMove))
16958                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16959                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16960                 for(i=currentMove; i<forwardMostMove; i++) {
16961                         if(WhiteOnMove(i))
16962                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16963                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16964                         strcat(buf, moveBuf);
16965                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16966                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16967                 }
16968                 strcat(buf, ")");
16969         }
16970         for(i=1; i<=nrMoves; i++) { // copy last variation back
16971             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16972             for(j=0; j<MOVE_LEN; j++)
16973                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16974             for(j=0; j<2*MOVE_LEN; j++)
16975                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16976             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16977             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16978             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16979             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16980             commentList[currentMove+i] = commentList[framePtr+i];
16981             commentList[framePtr+i] = NULL;
16982         }
16983         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16984         framePtr = savedFramePtr[storedGames];
16985         gameInfo.result = savedResult[storedGames];
16986         if(gameInfo.resultDetails != NULL) {
16987             free(gameInfo.resultDetails);
16988       }
16989         gameInfo.resultDetails = savedDetails[storedGames];
16990         forwardMostMove = currentMove + nrMoves;
16991 }
16992
16993 Boolean
16994 PopTail(Boolean annotate)
16995 {
16996         if(appData.icsActive) return FALSE; // only in local mode
16997         if(!storedGames) return FALSE; // sanity
16998         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16999
17000         PopInner(annotate);
17001         if(currentMove < forwardMostMove) ForwardEvent(); else
17002         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17003
17004         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17005         return TRUE;
17006 }
17007
17008 void
17009 CleanupTail()
17010 {       // remove all shelved variations
17011         int i;
17012         for(i=0; i<storedGames; i++) {
17013             if(savedDetails[i])
17014                 free(savedDetails[i]);
17015             savedDetails[i] = NULL;
17016         }
17017         for(i=framePtr; i<MAX_MOVES; i++) {
17018                 if(commentList[i]) free(commentList[i]);
17019                 commentList[i] = NULL;
17020         }
17021         framePtr = MAX_MOVES-1;
17022         storedGames = 0;
17023 }
17024
17025 void
17026 LoadVariation(int index, char *text)
17027 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17028         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17029         int level = 0, move;
17030
17031         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17032         // first find outermost bracketing variation
17033         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17034             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17035                 if(*p == '{') wait = '}'; else
17036                 if(*p == '[') wait = ']'; else
17037                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17038                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17039             }
17040             if(*p == wait) wait = NULLCHAR; // closing ]} found
17041             p++;
17042         }
17043         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17044         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17045         end[1] = NULLCHAR; // clip off comment beyond variation
17046         ToNrEvent(currentMove-1);
17047         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17048         // kludge: use ParsePV() to append variation to game
17049         move = currentMove;
17050         ParsePV(start, TRUE, TRUE);
17051         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17052         ClearPremoveHighlights();
17053         CommentPopDown();
17054         ToNrEvent(currentMove+1);
17055 }
17056