9c96909321d5745f6a19d621f367ededccdcc089
[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 }
838
839 ChessProgramState *savCps;
840
841 void
842 LoadEngine()
843 {
844     int i;
845     if(WaitForEngine(savCps, LoadEngine)) return;
846     CommonEngineInit(); // recalculate time odds
847     if(gameInfo.variant != StringToVariant(appData.variant)) {
848         // we changed variant when loading the engine; this forces us to reset
849         Reset(TRUE, savCps != &first);
850         EditGameEvent(); // for consistency with other path, as Reset changes mode
851     }
852     InitChessProgram(savCps, FALSE);
853     SendToProgram("force\n", savCps);
854     DisplayMessage("", "");
855     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
856     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
857     ThawUI();
858     SetGNUMode();
859 }
860
861 void
862 ReplaceEngine(ChessProgramState *cps, int n)
863 {
864     EditGameEvent();
865     UnloadEngine(cps);
866     appData.noChessProgram = FALSE;
867     appData.clockMode = TRUE;
868     InitEngine(cps, n);
869     UpdateLogos(TRUE);
870     if(n) return; // only startup first engine immediately; second can wait
871     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
872     LoadEngine();
873 }
874
875 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
876 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
877
878 static char resetOptions[] = 
879         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
880         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
881         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
882
883 void
884 Load(ChessProgramState *cps, int i)
885 {
886     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
887     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
888         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
889         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
890         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
891         ParseArgsFromString(buf);
892         SwapEngines(i);
893         ReplaceEngine(cps, i);
894         return;
895     }
896     p = engineName;
897     while(q = strchr(p, SLASH)) p = q+1;
898     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
899     if(engineDir[0] != NULLCHAR)
900         appData.directory[i] = engineDir;
901     else if(p != engineName) { // derive directory from engine path, when not given
902         p[-1] = 0;
903         appData.directory[i] = strdup(engineName);
904         p[-1] = SLASH;
905     } else appData.directory[i] = ".";
906     if(params[0]) {
907         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
908         snprintf(command, MSG_SIZ, "%s %s", p, params);
909         p = command;
910     }
911     appData.chessProgram[i] = strdup(p);
912     appData.isUCI[i] = isUCI;
913     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
914     appData.hasOwnBookUCI[i] = hasBook;
915     if(!nickName[0]) useNick = FALSE;
916     if(useNick) ASSIGN(appData.pgnName[i], nickName);
917     if(addToList) {
918         int len;
919         char quote;
920         q = firstChessProgramNames;
921         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
922         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
923         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
924                         quote, p, quote, appData.directory[i], 
925                         useNick ? " -fn \"" : "",
926                         useNick ? nickName : "",
927                         useNick ? "\"" : "",
928                         v1 ? " -firstProtocolVersion 1" : "",
929                         hasBook ? "" : " -fNoOwnBookUCI",
930                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
931                         storeVariant ? " -variant " : "",
932                         storeVariant ? VariantName(gameInfo.variant) : "");
933         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
934         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
935         if(q)   free(q);
936     }
937     ReplaceEngine(cps, i);
938 }
939
940 void
941 InitTimeControls()
942 {
943     int matched, min, sec;
944     /*
945      * Parse timeControl resource
946      */
947     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
948                           appData.movesPerSession)) {
949         char buf[MSG_SIZ];
950         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
951         DisplayFatalError(buf, 0, 2);
952     }
953
954     /*
955      * Parse searchTime resource
956      */
957     if (*appData.searchTime != NULLCHAR) {
958         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
959         if (matched == 1) {
960             searchTime = min * 60;
961         } else if (matched == 2) {
962             searchTime = min * 60 + sec;
963         } else {
964             char buf[MSG_SIZ];
965             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
966             DisplayFatalError(buf, 0, 2);
967         }
968     }
969 }
970
971 void
972 InitBackEnd1()
973 {
974
975     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
976     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
977
978     GetTimeMark(&programStartTime);
979     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
980     appData.seedBase = random() + (random()<<15);
981     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
982
983     ClearProgramStats();
984     programStats.ok_to_send = 1;
985     programStats.seen_stat = 0;
986
987     /*
988      * Initialize game list
989      */
990     ListNew(&gameList);
991
992
993     /*
994      * Internet chess server status
995      */
996     if (appData.icsActive) {
997         appData.matchMode = FALSE;
998         appData.matchGames = 0;
999 #if ZIPPY
1000         appData.noChessProgram = !appData.zippyPlay;
1001 #else
1002         appData.zippyPlay = FALSE;
1003         appData.zippyTalk = FALSE;
1004         appData.noChessProgram = TRUE;
1005 #endif
1006         if (*appData.icsHelper != NULLCHAR) {
1007             appData.useTelnet = TRUE;
1008             appData.telnetProgram = appData.icsHelper;
1009         }
1010     } else {
1011         appData.zippyTalk = appData.zippyPlay = FALSE;
1012     }
1013
1014     /* [AS] Initialize pv info list [HGM] and game state */
1015     {
1016         int i, j;
1017
1018         for( i=0; i<=framePtr; i++ ) {
1019             pvInfoList[i].depth = -1;
1020             boards[i][EP_STATUS] = EP_NONE;
1021             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1022         }
1023     }
1024
1025     InitTimeControls();
1026
1027     /* [AS] Adjudication threshold */
1028     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1029
1030     InitEngine(&first, 0);
1031     InitEngine(&second, 1);
1032     CommonEngineInit();
1033
1034     pairing.which = "pairing"; // pairing engine
1035     pairing.pr = NoProc;
1036     pairing.isr = NULL;
1037     pairing.program = appData.pairingEngine;
1038     pairing.host = "localhost";
1039     pairing.dir = ".";
1040
1041     if (appData.icsActive) {
1042         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1043     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1044         appData.clockMode = FALSE;
1045         first.sendTime = second.sendTime = 0;
1046     }
1047
1048 #if ZIPPY
1049     /* Override some settings from environment variables, for backward
1050        compatibility.  Unfortunately it's not feasible to have the env
1051        vars just set defaults, at least in xboard.  Ugh.
1052     */
1053     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1054       ZippyInit();
1055     }
1056 #endif
1057
1058     if (!appData.icsActive) {
1059       char buf[MSG_SIZ];
1060       int len;
1061
1062       /* Check for variants that are supported only in ICS mode,
1063          or not at all.  Some that are accepted here nevertheless
1064          have bugs; see comments below.
1065       */
1066       VariantClass variant = StringToVariant(appData.variant);
1067       switch (variant) {
1068       case VariantBughouse:     /* need four players and two boards */
1069       case VariantKriegspiel:   /* need to hide pieces and move details */
1070         /* case VariantFischeRandom: (Fabien: moved below) */
1071         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1072         if( (len > MSG_SIZ) && appData.debugMode )
1073           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1074
1075         DisplayFatalError(buf, 0, 2);
1076         return;
1077
1078       case VariantUnknown:
1079       case VariantLoadable:
1080       case Variant29:
1081       case Variant30:
1082       case Variant31:
1083       case Variant32:
1084       case Variant33:
1085       case Variant34:
1086       case Variant35:
1087       case Variant36:
1088       default:
1089         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1090         if( (len > MSG_SIZ) && appData.debugMode )
1091           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1092
1093         DisplayFatalError(buf, 0, 2);
1094         return;
1095
1096       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1097       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1098       case VariantGothic:     /* [HGM] should work */
1099       case VariantCapablanca: /* [HGM] should work */
1100       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1101       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1102       case VariantKnightmate: /* [HGM] should work */
1103       case VariantCylinder:   /* [HGM] untested */
1104       case VariantFalcon:     /* [HGM] untested */
1105       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1106                                  offboard interposition not understood */
1107       case VariantNormal:     /* definitely works! */
1108       case VariantWildCastle: /* pieces not automatically shuffled */
1109       case VariantNoCastle:   /* pieces not automatically shuffled */
1110       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1111       case VariantLosers:     /* should work except for win condition,
1112                                  and doesn't know captures are mandatory */
1113       case VariantSuicide:    /* should work except for win condition,
1114                                  and doesn't know captures are mandatory */
1115       case VariantGiveaway:   /* should work except for win condition,
1116                                  and doesn't know captures are mandatory */
1117       case VariantTwoKings:   /* should work */
1118       case VariantAtomic:     /* should work except for win condition */
1119       case Variant3Check:     /* should work except for win condition */
1120       case VariantShatranj:   /* should work except for all win conditions */
1121       case VariantMakruk:     /* should work except for draw countdown */
1122       case VariantBerolina:   /* might work if TestLegality is off */
1123       case VariantCapaRandom: /* should work */
1124       case VariantJanus:      /* should work */
1125       case VariantSuper:      /* experimental */
1126       case VariantGreat:      /* experimental, requires legality testing to be off */
1127       case VariantSChess:     /* S-Chess, should work */
1128       case VariantGrand:      /* should work */
1129       case VariantSpartan:    /* should work */
1130         break;
1131       }
1132     }
1133
1134 }
1135
1136 int NextIntegerFromString( char ** str, long * value )
1137 {
1138     int result = -1;
1139     char * s = *str;
1140
1141     while( *s == ' ' || *s == '\t' ) {
1142         s++;
1143     }
1144
1145     *value = 0;
1146
1147     if( *s >= '0' && *s <= '9' ) {
1148         while( *s >= '0' && *s <= '9' ) {
1149             *value = *value * 10 + (*s - '0');
1150             s++;
1151         }
1152
1153         result = 0;
1154     }
1155
1156     *str = s;
1157
1158     return result;
1159 }
1160
1161 int NextTimeControlFromString( char ** str, long * value )
1162 {
1163     long temp;
1164     int result = NextIntegerFromString( str, &temp );
1165
1166     if( result == 0 ) {
1167         *value = temp * 60; /* Minutes */
1168         if( **str == ':' ) {
1169             (*str)++;
1170             result = NextIntegerFromString( str, &temp );
1171             *value += temp; /* Seconds */
1172         }
1173     }
1174
1175     return result;
1176 }
1177
1178 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1179 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1180     int result = -1, type = 0; long temp, temp2;
1181
1182     if(**str != ':') return -1; // old params remain in force!
1183     (*str)++;
1184     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1185     if( NextIntegerFromString( str, &temp ) ) return -1;
1186     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1187
1188     if(**str != '/') {
1189         /* time only: incremental or sudden-death time control */
1190         if(**str == '+') { /* increment follows; read it */
1191             (*str)++;
1192             if(**str == '!') type = *(*str)++; // Bronstein TC
1193             if(result = NextIntegerFromString( str, &temp2)) return -1;
1194             *inc = temp2 * 1000;
1195             if(**str == '.') { // read fraction of increment
1196                 char *start = ++(*str);
1197                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1198                 temp2 *= 1000;
1199                 while(start++ < *str) temp2 /= 10;
1200                 *inc += temp2;
1201             }
1202         } else *inc = 0;
1203         *moves = 0; *tc = temp * 1000; *incType = type;
1204         return 0;
1205     }
1206
1207     (*str)++; /* classical time control */
1208     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1209
1210     if(result == 0) {
1211         *moves = temp;
1212         *tc    = temp2 * 1000;
1213         *inc   = 0;
1214         *incType = type;
1215     }
1216     return result;
1217 }
1218
1219 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1220 {   /* [HGM] get time to add from the multi-session time-control string */
1221     int incType, moves=1; /* kludge to force reading of first session */
1222     long time, increment;
1223     char *s = tcString;
1224
1225     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1226     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1227     do {
1228         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1229         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1230         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1231         if(movenr == -1) return time;    /* last move before new session     */
1232         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1233         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1234         if(!moves) return increment;     /* current session is incremental   */
1235         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1236     } while(movenr >= -1);               /* try again for next session       */
1237
1238     return 0; // no new time quota on this move
1239 }
1240
1241 int
1242 ParseTimeControl(tc, ti, mps)
1243      char *tc;
1244      float ti;
1245      int mps;
1246 {
1247   long tc1;
1248   long tc2;
1249   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1250   int min, sec=0;
1251
1252   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1253   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1254       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1255   if(ti > 0) {
1256
1257     if(mps)
1258       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1259     else 
1260       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1261   } else {
1262     if(mps)
1263       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1264     else 
1265       snprintf(buf, MSG_SIZ, ":%s", mytc);
1266   }
1267   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1268   
1269   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1270     return FALSE;
1271   }
1272
1273   if( *tc == '/' ) {
1274     /* Parse second time control */
1275     tc++;
1276
1277     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1278       return FALSE;
1279     }
1280
1281     if( tc2 == 0 ) {
1282       return FALSE;
1283     }
1284
1285     timeControl_2 = tc2 * 1000;
1286   }
1287   else {
1288     timeControl_2 = 0;
1289   }
1290
1291   if( tc1 == 0 ) {
1292     return FALSE;
1293   }
1294
1295   timeControl = tc1 * 1000;
1296
1297   if (ti >= 0) {
1298     timeIncrement = ti * 1000;  /* convert to ms */
1299     movesPerSession = 0;
1300   } else {
1301     timeIncrement = 0;
1302     movesPerSession = mps;
1303   }
1304   return TRUE;
1305 }
1306
1307 void
1308 InitBackEnd2()
1309 {
1310     if (appData.debugMode) {
1311         fprintf(debugFP, "%s\n", programVersion);
1312     }
1313
1314     set_cont_sequence(appData.wrapContSeq);
1315     if (appData.matchGames > 0) {
1316         appData.matchMode = TRUE;
1317     } else if (appData.matchMode) {
1318         appData.matchGames = 1;
1319     }
1320     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1321         appData.matchGames = appData.sameColorGames;
1322     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1323         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1324         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1325     }
1326     Reset(TRUE, FALSE);
1327     if (appData.noChessProgram || first.protocolVersion == 1) {
1328       InitBackEnd3();
1329     } else {
1330       /* kludge: allow timeout for initial "feature" commands */
1331       FreezeUI();
1332       DisplayMessage("", _("Starting chess program"));
1333       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1334     }
1335 }
1336
1337 int
1338 CalculateIndex(int index, int gameNr)
1339 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1340     int res;
1341     if(index > 0) return index; // fixed nmber
1342     if(index == 0) return 1;
1343     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1344     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1345     return res;
1346 }
1347
1348 int
1349 LoadGameOrPosition(int gameNr)
1350 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1351     if (*appData.loadGameFile != NULLCHAR) {
1352         if (!LoadGameFromFile(appData.loadGameFile,
1353                 CalculateIndex(appData.loadGameIndex, gameNr),
1354                               appData.loadGameFile, FALSE)) {
1355             DisplayFatalError(_("Bad game file"), 0, 1);
1356             return 0;
1357         }
1358     } else if (*appData.loadPositionFile != NULLCHAR) {
1359         if (!LoadPositionFromFile(appData.loadPositionFile,
1360                 CalculateIndex(appData.loadPositionIndex, gameNr),
1361                                   appData.loadPositionFile)) {
1362             DisplayFatalError(_("Bad position file"), 0, 1);
1363             return 0;
1364         }
1365     }
1366     return 1;
1367 }
1368
1369 void
1370 ReserveGame(int gameNr, char resChar)
1371 {
1372     FILE *tf = fopen(appData.tourneyFile, "r+");
1373     char *p, *q, c, buf[MSG_SIZ];
1374     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1375     safeStrCpy(buf, lastMsg, MSG_SIZ);
1376     DisplayMessage(_("Pick new game"), "");
1377     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1378     ParseArgsFromFile(tf);
1379     p = q = appData.results;
1380     if(appData.debugMode) {
1381       char *r = appData.participants;
1382       fprintf(debugFP, "results = '%s'\n", p);
1383       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1384       fprintf(debugFP, "\n");
1385     }
1386     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1387     nextGame = q - p;
1388     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1389     safeStrCpy(q, p, strlen(p) + 2);
1390     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1391     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1392     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1393         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1394         q[nextGame] = '*';
1395     }
1396     fseek(tf, -(strlen(p)+4), SEEK_END);
1397     c = fgetc(tf);
1398     if(c != '"') // depending on DOS or Unix line endings we can be one off
1399          fseek(tf, -(strlen(p)+2), SEEK_END);
1400     else fseek(tf, -(strlen(p)+3), SEEK_END);
1401     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1402     DisplayMessage(buf, "");
1403     free(p); appData.results = q;
1404     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1405        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1406         UnloadEngine(&first);  // next game belongs to other pairing;
1407         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1408     }
1409 }
1410
1411 void
1412 MatchEvent(int mode)
1413 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1414         int dummy;
1415         if(matchMode) { // already in match mode: switch it off
1416             abortMatch = TRUE;
1417             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1418             return;
1419         }
1420 //      if(gameMode != BeginningOfGame) {
1421 //          DisplayError(_("You can only start a match from the initial position."), 0);
1422 //          return;
1423 //      }
1424         abortMatch = FALSE;
1425         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1426         /* Set up machine vs. machine match */
1427         nextGame = 0;
1428         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1429         if(appData.tourneyFile[0]) {
1430             ReserveGame(-1, 0);
1431             if(nextGame > appData.matchGames) {
1432                 char buf[MSG_SIZ];
1433                 if(strchr(appData.results, '*') == NULL) {
1434                     FILE *f;
1435                     appData.tourneyCycles++;
1436                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1437                         fclose(f);
1438                         NextTourneyGame(-1, &dummy);
1439                         ReserveGame(-1, 0);
1440                         if(nextGame <= appData.matchGames) {
1441                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1442                             matchMode = mode;
1443                             ScheduleDelayedEvent(NextMatchGame, 10000);
1444                             return;
1445                         }
1446                     }
1447                 }
1448                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1449                 DisplayError(buf, 0);
1450                 appData.tourneyFile[0] = 0;
1451                 return;
1452             }
1453         } else
1454         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1455             DisplayFatalError(_("Can't have a match with no chess programs"),
1456                               0, 2);
1457             return;
1458         }
1459         matchMode = mode;
1460         matchGame = roundNr = 1;
1461         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1462         NextMatchGame();
1463 }
1464
1465 void
1466 InitBackEnd3 P((void))
1467 {
1468     GameMode initialMode;
1469     char buf[MSG_SIZ];
1470     int err, len;
1471
1472     InitChessProgram(&first, startedFromSetupPosition);
1473
1474     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1475         free(programVersion);
1476         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1477         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1478     }
1479
1480     if (appData.icsActive) {
1481 #ifdef WIN32
1482         /* [DM] Make a console window if needed [HGM] merged ifs */
1483         ConsoleCreate();
1484 #endif
1485         err = establish();
1486         if (err != 0)
1487           {
1488             if (*appData.icsCommPort != NULLCHAR)
1489               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1490                              appData.icsCommPort);
1491             else
1492               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1493                         appData.icsHost, appData.icsPort);
1494
1495             if( (len > MSG_SIZ) && appData.debugMode )
1496               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1497
1498             DisplayFatalError(buf, err, 1);
1499             return;
1500         }
1501         SetICSMode();
1502         telnetISR =
1503           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1504         fromUserISR =
1505           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1506         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1507             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1508     } else if (appData.noChessProgram) {
1509         SetNCPMode();
1510     } else {
1511         SetGNUMode();
1512     }
1513
1514     if (*appData.cmailGameName != NULLCHAR) {
1515         SetCmailMode();
1516         OpenLoopback(&cmailPR);
1517         cmailISR =
1518           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1519     }
1520
1521     ThawUI();
1522     DisplayMessage("", "");
1523     if (StrCaseCmp(appData.initialMode, "") == 0) {
1524       initialMode = BeginningOfGame;
1525       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1526         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1527         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1528         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1529         ModeHighlight();
1530       }
1531     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1532       initialMode = TwoMachinesPlay;
1533     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1534       initialMode = AnalyzeFile;
1535     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1536       initialMode = AnalyzeMode;
1537     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1538       initialMode = MachinePlaysWhite;
1539     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1540       initialMode = MachinePlaysBlack;
1541     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1542       initialMode = EditGame;
1543     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1544       initialMode = EditPosition;
1545     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1546       initialMode = Training;
1547     } else {
1548       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1549       if( (len > MSG_SIZ) && appData.debugMode )
1550         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1551
1552       DisplayFatalError(buf, 0, 2);
1553       return;
1554     }
1555
1556     if (appData.matchMode) {
1557         if(appData.tourneyFile[0]) { // start tourney from command line
1558             FILE *f;
1559             if(f = fopen(appData.tourneyFile, "r")) {
1560                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1561                 fclose(f);
1562                 appData.clockMode = TRUE;
1563                 SetGNUMode();
1564             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1565         }
1566         MatchEvent(TRUE);
1567     } else if (*appData.cmailGameName != NULLCHAR) {
1568         /* Set up cmail mode */
1569         ReloadCmailMsgEvent(TRUE);
1570     } else {
1571         /* Set up other modes */
1572         if (initialMode == AnalyzeFile) {
1573           if (*appData.loadGameFile == NULLCHAR) {
1574             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1575             return;
1576           }
1577         }
1578         if (*appData.loadGameFile != NULLCHAR) {
1579             (void) LoadGameFromFile(appData.loadGameFile,
1580                                     appData.loadGameIndex,
1581                                     appData.loadGameFile, TRUE);
1582         } else if (*appData.loadPositionFile != NULLCHAR) {
1583             (void) LoadPositionFromFile(appData.loadPositionFile,
1584                                         appData.loadPositionIndex,
1585                                         appData.loadPositionFile);
1586             /* [HGM] try to make self-starting even after FEN load */
1587             /* to allow automatic setup of fairy variants with wtm */
1588             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1589                 gameMode = BeginningOfGame;
1590                 setboardSpoiledMachineBlack = 1;
1591             }
1592             /* [HGM] loadPos: make that every new game uses the setup */
1593             /* from file as long as we do not switch variant          */
1594             if(!blackPlaysFirst) {
1595                 startedFromPositionFile = TRUE;
1596                 CopyBoard(filePosition, boards[0]);
1597             }
1598         }
1599         if (initialMode == AnalyzeMode) {
1600           if (appData.noChessProgram) {
1601             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1602             return;
1603           }
1604           if (appData.icsActive) {
1605             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1606             return;
1607           }
1608           AnalyzeModeEvent();
1609         } else if (initialMode == AnalyzeFile) {
1610           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1611           ShowThinkingEvent();
1612           AnalyzeFileEvent();
1613           AnalysisPeriodicEvent(1);
1614         } else if (initialMode == MachinePlaysWhite) {
1615           if (appData.noChessProgram) {
1616             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1617                               0, 2);
1618             return;
1619           }
1620           if (appData.icsActive) {
1621             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1622                               0, 2);
1623             return;
1624           }
1625           MachineWhiteEvent();
1626         } else if (initialMode == MachinePlaysBlack) {
1627           if (appData.noChessProgram) {
1628             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1629                               0, 2);
1630             return;
1631           }
1632           if (appData.icsActive) {
1633             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1634                               0, 2);
1635             return;
1636           }
1637           MachineBlackEvent();
1638         } else if (initialMode == TwoMachinesPlay) {
1639           if (appData.noChessProgram) {
1640             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1641                               0, 2);
1642             return;
1643           }
1644           if (appData.icsActive) {
1645             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1646                               0, 2);
1647             return;
1648           }
1649           TwoMachinesEvent();
1650         } else if (initialMode == EditGame) {
1651           EditGameEvent();
1652         } else if (initialMode == EditPosition) {
1653           EditPositionEvent();
1654         } else if (initialMode == Training) {
1655           if (*appData.loadGameFile == NULLCHAR) {
1656             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1657             return;
1658           }
1659           TrainingEvent();
1660         }
1661     }
1662 }
1663
1664 void
1665 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1666 {
1667     DisplayBook(current+1);
1668
1669     MoveHistorySet( movelist, first, last, current, pvInfoList );
1670
1671     EvalGraphSet( first, last, current, pvInfoList );
1672
1673     MakeEngineOutputTitle();
1674 }
1675
1676 /*
1677  * Establish will establish a contact to a remote host.port.
1678  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1679  *  used to talk to the host.
1680  * Returns 0 if okay, error code if not.
1681  */
1682 int
1683 establish()
1684 {
1685     char buf[MSG_SIZ];
1686
1687     if (*appData.icsCommPort != NULLCHAR) {
1688         /* Talk to the host through a serial comm port */
1689         return OpenCommPort(appData.icsCommPort, &icsPR);
1690
1691     } else if (*appData.gateway != NULLCHAR) {
1692         if (*appData.remoteShell == NULLCHAR) {
1693             /* Use the rcmd protocol to run telnet program on a gateway host */
1694             snprintf(buf, sizeof(buf), "%s %s %s",
1695                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1696             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1697
1698         } else {
1699             /* Use the rsh program to run telnet program on a gateway host */
1700             if (*appData.remoteUser == NULLCHAR) {
1701                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1702                         appData.gateway, appData.telnetProgram,
1703                         appData.icsHost, appData.icsPort);
1704             } else {
1705                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1706                         appData.remoteShell, appData.gateway,
1707                         appData.remoteUser, appData.telnetProgram,
1708                         appData.icsHost, appData.icsPort);
1709             }
1710             return StartChildProcess(buf, "", &icsPR);
1711
1712         }
1713     } else if (appData.useTelnet) {
1714         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1715
1716     } else {
1717         /* TCP socket interface differs somewhat between
1718            Unix and NT; handle details in the front end.
1719            */
1720         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1721     }
1722 }
1723
1724 void EscapeExpand(char *p, char *q)
1725 {       // [HGM] initstring: routine to shape up string arguments
1726         while(*p++ = *q++) if(p[-1] == '\\')
1727             switch(*q++) {
1728                 case 'n': p[-1] = '\n'; break;
1729                 case 'r': p[-1] = '\r'; break;
1730                 case 't': p[-1] = '\t'; break;
1731                 case '\\': p[-1] = '\\'; break;
1732                 case 0: *p = 0; return;
1733                 default: p[-1] = q[-1]; break;
1734             }
1735 }
1736
1737 void
1738 show_bytes(fp, buf, count)
1739      FILE *fp;
1740      char *buf;
1741      int count;
1742 {
1743     while (count--) {
1744         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1745             fprintf(fp, "\\%03o", *buf & 0xff);
1746         } else {
1747             putc(*buf, fp);
1748         }
1749         buf++;
1750     }
1751     fflush(fp);
1752 }
1753
1754 /* Returns an errno value */
1755 int
1756 OutputMaybeTelnet(pr, message, count, outError)
1757      ProcRef pr;
1758      char *message;
1759      int count;
1760      int *outError;
1761 {
1762     char buf[8192], *p, *q, *buflim;
1763     int left, newcount, outcount;
1764
1765     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1766         *appData.gateway != NULLCHAR) {
1767         if (appData.debugMode) {
1768             fprintf(debugFP, ">ICS: ");
1769             show_bytes(debugFP, message, count);
1770             fprintf(debugFP, "\n");
1771         }
1772         return OutputToProcess(pr, message, count, outError);
1773     }
1774
1775     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1776     p = message;
1777     q = buf;
1778     left = count;
1779     newcount = 0;
1780     while (left) {
1781         if (q >= buflim) {
1782             if (appData.debugMode) {
1783                 fprintf(debugFP, ">ICS: ");
1784                 show_bytes(debugFP, buf, newcount);
1785                 fprintf(debugFP, "\n");
1786             }
1787             outcount = OutputToProcess(pr, buf, newcount, outError);
1788             if (outcount < newcount) return -1; /* to be sure */
1789             q = buf;
1790             newcount = 0;
1791         }
1792         if (*p == '\n') {
1793             *q++ = '\r';
1794             newcount++;
1795         } else if (((unsigned char) *p) == TN_IAC) {
1796             *q++ = (char) TN_IAC;
1797             newcount ++;
1798         }
1799         *q++ = *p++;
1800         newcount++;
1801         left--;
1802     }
1803     if (appData.debugMode) {
1804         fprintf(debugFP, ">ICS: ");
1805         show_bytes(debugFP, buf, newcount);
1806         fprintf(debugFP, "\n");
1807     }
1808     outcount = OutputToProcess(pr, buf, newcount, outError);
1809     if (outcount < newcount) return -1; /* to be sure */
1810     return count;
1811 }
1812
1813 void
1814 read_from_player(isr, closure, message, count, error)
1815      InputSourceRef isr;
1816      VOIDSTAR closure;
1817      char *message;
1818      int count;
1819      int error;
1820 {
1821     int outError, outCount;
1822     static int gotEof = 0;
1823
1824     /* Pass data read from player on to ICS */
1825     if (count > 0) {
1826         gotEof = 0;
1827         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1828         if (outCount < count) {
1829             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1830         }
1831     } else if (count < 0) {
1832         RemoveInputSource(isr);
1833         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1834     } else if (gotEof++ > 0) {
1835         RemoveInputSource(isr);
1836         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1837     }
1838 }
1839
1840 void
1841 KeepAlive()
1842 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1843     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1844     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1845     SendToICS("date\n");
1846     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1847 }
1848
1849 /* added routine for printf style output to ics */
1850 void ics_printf(char *format, ...)
1851 {
1852     char buffer[MSG_SIZ];
1853     va_list args;
1854
1855     va_start(args, format);
1856     vsnprintf(buffer, sizeof(buffer), format, args);
1857     buffer[sizeof(buffer)-1] = '\0';
1858     SendToICS(buffer);
1859     va_end(args);
1860 }
1861
1862 void
1863 SendToICS(s)
1864      char *s;
1865 {
1866     int count, outCount, outError;
1867
1868     if (icsPR == NoProc) return;
1869
1870     count = strlen(s);
1871     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1872     if (outCount < count) {
1873         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1874     }
1875 }
1876
1877 /* This is used for sending logon scripts to the ICS. Sending
1878    without a delay causes problems when using timestamp on ICC
1879    (at least on my machine). */
1880 void
1881 SendToICSDelayed(s,msdelay)
1882      char *s;
1883      long msdelay;
1884 {
1885     int count, outCount, outError;
1886
1887     if (icsPR == NoProc) return;
1888
1889     count = strlen(s);
1890     if (appData.debugMode) {
1891         fprintf(debugFP, ">ICS: ");
1892         show_bytes(debugFP, s, count);
1893         fprintf(debugFP, "\n");
1894     }
1895     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1896                                       msdelay);
1897     if (outCount < count) {
1898         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1899     }
1900 }
1901
1902
1903 /* Remove all highlighting escape sequences in s
1904    Also deletes any suffix starting with '('
1905    */
1906 char *
1907 StripHighlightAndTitle(s)
1908      char *s;
1909 {
1910     static char retbuf[MSG_SIZ];
1911     char *p = retbuf;
1912
1913     while (*s != NULLCHAR) {
1914         while (*s == '\033') {
1915             while (*s != NULLCHAR && !isalpha(*s)) s++;
1916             if (*s != NULLCHAR) s++;
1917         }
1918         while (*s != NULLCHAR && *s != '\033') {
1919             if (*s == '(' || *s == '[') {
1920                 *p = NULLCHAR;
1921                 return retbuf;
1922             }
1923             *p++ = *s++;
1924         }
1925     }
1926     *p = NULLCHAR;
1927     return retbuf;
1928 }
1929
1930 /* Remove all highlighting escape sequences in s */
1931 char *
1932 StripHighlight(s)
1933      char *s;
1934 {
1935     static char retbuf[MSG_SIZ];
1936     char *p = retbuf;
1937
1938     while (*s != NULLCHAR) {
1939         while (*s == '\033') {
1940             while (*s != NULLCHAR && !isalpha(*s)) s++;
1941             if (*s != NULLCHAR) s++;
1942         }
1943         while (*s != NULLCHAR && *s != '\033') {
1944             *p++ = *s++;
1945         }
1946     }
1947     *p = NULLCHAR;
1948     return retbuf;
1949 }
1950
1951 char *variantNames[] = VARIANT_NAMES;
1952 char *
1953 VariantName(v)
1954      VariantClass v;
1955 {
1956     return variantNames[v];
1957 }
1958
1959
1960 /* Identify a variant from the strings the chess servers use or the
1961    PGN Variant tag names we use. */
1962 VariantClass
1963 StringToVariant(e)
1964      char *e;
1965 {
1966     char *p;
1967     int wnum = -1;
1968     VariantClass v = VariantNormal;
1969     int i, found = FALSE;
1970     char buf[MSG_SIZ];
1971     int len;
1972
1973     if (!e) return v;
1974
1975     /* [HGM] skip over optional board-size prefixes */
1976     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1977         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1978         while( *e++ != '_');
1979     }
1980
1981     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1982         v = VariantNormal;
1983         found = TRUE;
1984     } else
1985     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1986       if (StrCaseStr(e, variantNames[i])) {
1987         v = (VariantClass) i;
1988         found = TRUE;
1989         break;
1990       }
1991     }
1992
1993     if (!found) {
1994       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1995           || StrCaseStr(e, "wild/fr")
1996           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1997         v = VariantFischeRandom;
1998       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1999                  (i = 1, p = StrCaseStr(e, "w"))) {
2000         p += i;
2001         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2002         if (isdigit(*p)) {
2003           wnum = atoi(p);
2004         } else {
2005           wnum = -1;
2006         }
2007         switch (wnum) {
2008         case 0: /* FICS only, actually */
2009         case 1:
2010           /* Castling legal even if K starts on d-file */
2011           v = VariantWildCastle;
2012           break;
2013         case 2:
2014         case 3:
2015         case 4:
2016           /* Castling illegal even if K & R happen to start in
2017              normal positions. */
2018           v = VariantNoCastle;
2019           break;
2020         case 5:
2021         case 7:
2022         case 8:
2023         case 10:
2024         case 11:
2025         case 12:
2026         case 13:
2027         case 14:
2028         case 15:
2029         case 18:
2030         case 19:
2031           /* Castling legal iff K & R start in normal positions */
2032           v = VariantNormal;
2033           break;
2034         case 6:
2035         case 20:
2036         case 21:
2037           /* Special wilds for position setup; unclear what to do here */
2038           v = VariantLoadable;
2039           break;
2040         case 9:
2041           /* Bizarre ICC game */
2042           v = VariantTwoKings;
2043           break;
2044         case 16:
2045           v = VariantKriegspiel;
2046           break;
2047         case 17:
2048           v = VariantLosers;
2049           break;
2050         case 22:
2051           v = VariantFischeRandom;
2052           break;
2053         case 23:
2054           v = VariantCrazyhouse;
2055           break;
2056         case 24:
2057           v = VariantBughouse;
2058           break;
2059         case 25:
2060           v = Variant3Check;
2061           break;
2062         case 26:
2063           /* Not quite the same as FICS suicide! */
2064           v = VariantGiveaway;
2065           break;
2066         case 27:
2067           v = VariantAtomic;
2068           break;
2069         case 28:
2070           v = VariantShatranj;
2071           break;
2072
2073         /* Temporary names for future ICC types.  The name *will* change in
2074            the next xboard/WinBoard release after ICC defines it. */
2075         case 29:
2076           v = Variant29;
2077           break;
2078         case 30:
2079           v = Variant30;
2080           break;
2081         case 31:
2082           v = Variant31;
2083           break;
2084         case 32:
2085           v = Variant32;
2086           break;
2087         case 33:
2088           v = Variant33;
2089           break;
2090         case 34:
2091           v = Variant34;
2092           break;
2093         case 35:
2094           v = Variant35;
2095           break;
2096         case 36:
2097           v = Variant36;
2098           break;
2099         case 37:
2100           v = VariantShogi;
2101           break;
2102         case 38:
2103           v = VariantXiangqi;
2104           break;
2105         case 39:
2106           v = VariantCourier;
2107           break;
2108         case 40:
2109           v = VariantGothic;
2110           break;
2111         case 41:
2112           v = VariantCapablanca;
2113           break;
2114         case 42:
2115           v = VariantKnightmate;
2116           break;
2117         case 43:
2118           v = VariantFairy;
2119           break;
2120         case 44:
2121           v = VariantCylinder;
2122           break;
2123         case 45:
2124           v = VariantFalcon;
2125           break;
2126         case 46:
2127           v = VariantCapaRandom;
2128           break;
2129         case 47:
2130           v = VariantBerolina;
2131           break;
2132         case 48:
2133           v = VariantJanus;
2134           break;
2135         case 49:
2136           v = VariantSuper;
2137           break;
2138         case 50:
2139           v = VariantGreat;
2140           break;
2141         case -1:
2142           /* Found "wild" or "w" in the string but no number;
2143              must assume it's normal chess. */
2144           v = VariantNormal;
2145           break;
2146         default:
2147           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2148           if( (len > MSG_SIZ) && appData.debugMode )
2149             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2150
2151           DisplayError(buf, 0);
2152           v = VariantUnknown;
2153           break;
2154         }
2155       }
2156     }
2157     if (appData.debugMode) {
2158       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2159               e, wnum, VariantName(v));
2160     }
2161     return v;
2162 }
2163
2164 static int leftover_start = 0, leftover_len = 0;
2165 char star_match[STAR_MATCH_N][MSG_SIZ];
2166
2167 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2168    advance *index beyond it, and set leftover_start to the new value of
2169    *index; else return FALSE.  If pattern contains the character '*', it
2170    matches any sequence of characters not containing '\r', '\n', or the
2171    character following the '*' (if any), and the matched sequence(s) are
2172    copied into star_match.
2173    */
2174 int
2175 looking_at(buf, index, pattern)
2176      char *buf;
2177      int *index;
2178      char *pattern;
2179 {
2180     char *bufp = &buf[*index], *patternp = pattern;
2181     int star_count = 0;
2182     char *matchp = star_match[0];
2183
2184     for (;;) {
2185         if (*patternp == NULLCHAR) {
2186             *index = leftover_start = bufp - buf;
2187             *matchp = NULLCHAR;
2188             return TRUE;
2189         }
2190         if (*bufp == NULLCHAR) return FALSE;
2191         if (*patternp == '*') {
2192             if (*bufp == *(patternp + 1)) {
2193                 *matchp = NULLCHAR;
2194                 matchp = star_match[++star_count];
2195                 patternp += 2;
2196                 bufp++;
2197                 continue;
2198             } else if (*bufp == '\n' || *bufp == '\r') {
2199                 patternp++;
2200                 if (*patternp == NULLCHAR)
2201                   continue;
2202                 else
2203                   return FALSE;
2204             } else {
2205                 *matchp++ = *bufp++;
2206                 continue;
2207             }
2208         }
2209         if (*patternp != *bufp) return FALSE;
2210         patternp++;
2211         bufp++;
2212     }
2213 }
2214
2215 void
2216 SendToPlayer(data, length)
2217      char *data;
2218      int length;
2219 {
2220     int error, outCount;
2221     outCount = OutputToProcess(NoProc, data, length, &error);
2222     if (outCount < length) {
2223         DisplayFatalError(_("Error writing to display"), error, 1);
2224     }
2225 }
2226
2227 void
2228 PackHolding(packed, holding)
2229      char packed[];
2230      char *holding;
2231 {
2232     char *p = holding;
2233     char *q = packed;
2234     int runlength = 0;
2235     int curr = 9999;
2236     do {
2237         if (*p == curr) {
2238             runlength++;
2239         } else {
2240             switch (runlength) {
2241               case 0:
2242                 break;
2243               case 1:
2244                 *q++ = curr;
2245                 break;
2246               case 2:
2247                 *q++ = curr;
2248                 *q++ = curr;
2249                 break;
2250               default:
2251                 sprintf(q, "%d", runlength);
2252                 while (*q) q++;
2253                 *q++ = curr;
2254                 break;
2255             }
2256             runlength = 1;
2257             curr = *p;
2258         }
2259     } while (*p++);
2260     *q = NULLCHAR;
2261 }
2262
2263 /* Telnet protocol requests from the front end */
2264 void
2265 TelnetRequest(ddww, option)
2266      unsigned char ddww, option;
2267 {
2268     unsigned char msg[3];
2269     int outCount, outError;
2270
2271     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2272
2273     if (appData.debugMode) {
2274         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2275         switch (ddww) {
2276           case TN_DO:
2277             ddwwStr = "DO";
2278             break;
2279           case TN_DONT:
2280             ddwwStr = "DONT";
2281             break;
2282           case TN_WILL:
2283             ddwwStr = "WILL";
2284             break;
2285           case TN_WONT:
2286             ddwwStr = "WONT";
2287             break;
2288           default:
2289             ddwwStr = buf1;
2290             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2291             break;
2292         }
2293         switch (option) {
2294           case TN_ECHO:
2295             optionStr = "ECHO";
2296             break;
2297           default:
2298             optionStr = buf2;
2299             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2300             break;
2301         }
2302         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2303     }
2304     msg[0] = TN_IAC;
2305     msg[1] = ddww;
2306     msg[2] = option;
2307     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2308     if (outCount < 3) {
2309         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2310     }
2311 }
2312
2313 void
2314 DoEcho()
2315 {
2316     if (!appData.icsActive) return;
2317     TelnetRequest(TN_DO, TN_ECHO);
2318 }
2319
2320 void
2321 DontEcho()
2322 {
2323     if (!appData.icsActive) return;
2324     TelnetRequest(TN_DONT, TN_ECHO);
2325 }
2326
2327 void
2328 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2329 {
2330     /* put the holdings sent to us by the server on the board holdings area */
2331     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2332     char p;
2333     ChessSquare piece;
2334
2335     if(gameInfo.holdingsWidth < 2)  return;
2336     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2337         return; // prevent overwriting by pre-board holdings
2338
2339     if( (int)lowestPiece >= BlackPawn ) {
2340         holdingsColumn = 0;
2341         countsColumn = 1;
2342         holdingsStartRow = BOARD_HEIGHT-1;
2343         direction = -1;
2344     } else {
2345         holdingsColumn = BOARD_WIDTH-1;
2346         countsColumn = BOARD_WIDTH-2;
2347         holdingsStartRow = 0;
2348         direction = 1;
2349     }
2350
2351     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2352         board[i][holdingsColumn] = EmptySquare;
2353         board[i][countsColumn]   = (ChessSquare) 0;
2354     }
2355     while( (p=*holdings++) != NULLCHAR ) {
2356         piece = CharToPiece( ToUpper(p) );
2357         if(piece == EmptySquare) continue;
2358         /*j = (int) piece - (int) WhitePawn;*/
2359         j = PieceToNumber(piece);
2360         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2361         if(j < 0) continue;               /* should not happen */
2362         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2363         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2364         board[holdingsStartRow+j*direction][countsColumn]++;
2365     }
2366 }
2367
2368
2369 void
2370 VariantSwitch(Board board, VariantClass newVariant)
2371 {
2372    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2373    static Board oldBoard;
2374
2375    startedFromPositionFile = FALSE;
2376    if(gameInfo.variant == newVariant) return;
2377
2378    /* [HGM] This routine is called each time an assignment is made to
2379     * gameInfo.variant during a game, to make sure the board sizes
2380     * are set to match the new variant. If that means adding or deleting
2381     * holdings, we shift the playing board accordingly
2382     * This kludge is needed because in ICS observe mode, we get boards
2383     * of an ongoing game without knowing the variant, and learn about the
2384     * latter only later. This can be because of the move list we requested,
2385     * in which case the game history is refilled from the beginning anyway,
2386     * but also when receiving holdings of a crazyhouse game. In the latter
2387     * case we want to add those holdings to the already received position.
2388     */
2389
2390
2391    if (appData.debugMode) {
2392      fprintf(debugFP, "Switch board from %s to %s\n",
2393              VariantName(gameInfo.variant), VariantName(newVariant));
2394      setbuf(debugFP, NULL);
2395    }
2396    shuffleOpenings = 0;       /* [HGM] shuffle */
2397    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2398    switch(newVariant)
2399      {
2400      case VariantShogi:
2401        newWidth = 9;  newHeight = 9;
2402        gameInfo.holdingsSize = 7;
2403      case VariantBughouse:
2404      case VariantCrazyhouse:
2405        newHoldingsWidth = 2; break;
2406      case VariantGreat:
2407        newWidth = 10;
2408      case VariantSuper:
2409        newHoldingsWidth = 2;
2410        gameInfo.holdingsSize = 8;
2411        break;
2412      case VariantGothic:
2413      case VariantCapablanca:
2414      case VariantCapaRandom:
2415        newWidth = 10;
2416      default:
2417        newHoldingsWidth = gameInfo.holdingsSize = 0;
2418      };
2419
2420    if(newWidth  != gameInfo.boardWidth  ||
2421       newHeight != gameInfo.boardHeight ||
2422       newHoldingsWidth != gameInfo.holdingsWidth ) {
2423
2424      /* shift position to new playing area, if needed */
2425      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2426        for(i=0; i<BOARD_HEIGHT; i++)
2427          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2428            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2429              board[i][j];
2430        for(i=0; i<newHeight; i++) {
2431          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2432          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2433        }
2434      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2435        for(i=0; i<BOARD_HEIGHT; i++)
2436          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2437            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2438              board[i][j];
2439      }
2440      gameInfo.boardWidth  = newWidth;
2441      gameInfo.boardHeight = newHeight;
2442      gameInfo.holdingsWidth = newHoldingsWidth;
2443      gameInfo.variant = newVariant;
2444      InitDrawingSizes(-2, 0);
2445    } else gameInfo.variant = newVariant;
2446    CopyBoard(oldBoard, board);   // remember correctly formatted board
2447      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2448    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2449 }
2450
2451 static int loggedOn = FALSE;
2452
2453 /*-- Game start info cache: --*/
2454 int gs_gamenum;
2455 char gs_kind[MSG_SIZ];
2456 static char player1Name[128] = "";
2457 static char player2Name[128] = "";
2458 static char cont_seq[] = "\n\\   ";
2459 static int player1Rating = -1;
2460 static int player2Rating = -1;
2461 /*----------------------------*/
2462
2463 ColorClass curColor = ColorNormal;
2464 int suppressKibitz = 0;
2465
2466 // [HGM] seekgraph
2467 Boolean soughtPending = FALSE;
2468 Boolean seekGraphUp;
2469 #define MAX_SEEK_ADS 200
2470 #define SQUARE 0x80
2471 char *seekAdList[MAX_SEEK_ADS];
2472 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2473 float tcList[MAX_SEEK_ADS];
2474 char colorList[MAX_SEEK_ADS];
2475 int nrOfSeekAds = 0;
2476 int minRating = 1010, maxRating = 2800;
2477 int hMargin = 10, vMargin = 20, h, w;
2478 extern int squareSize, lineGap;
2479
2480 void
2481 PlotSeekAd(int i)
2482 {
2483         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2484         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2485         if(r < minRating+100 && r >=0 ) r = minRating+100;
2486         if(r > maxRating) r = maxRating;
2487         if(tc < 1.) tc = 1.;
2488         if(tc > 95.) tc = 95.;
2489         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2490         y = ((double)r - minRating)/(maxRating - minRating)
2491             * (h-vMargin-squareSize/8-1) + vMargin;
2492         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2493         if(strstr(seekAdList[i], " u ")) color = 1;
2494         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2495            !strstr(seekAdList[i], "bullet") &&
2496            !strstr(seekAdList[i], "blitz") &&
2497            !strstr(seekAdList[i], "standard") ) color = 2;
2498         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2499         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2500 }
2501
2502 void
2503 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2504 {
2505         char buf[MSG_SIZ], *ext = "";
2506         VariantClass v = StringToVariant(type);
2507         if(strstr(type, "wild")) {
2508             ext = type + 4; // append wild number
2509             if(v == VariantFischeRandom) type = "chess960"; else
2510             if(v == VariantLoadable) type = "setup"; else
2511             type = VariantName(v);
2512         }
2513         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2514         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2515             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2516             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2517             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2518             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2519             seekNrList[nrOfSeekAds] = nr;
2520             zList[nrOfSeekAds] = 0;
2521             seekAdList[nrOfSeekAds++] = StrSave(buf);
2522             if(plot) PlotSeekAd(nrOfSeekAds-1);
2523         }
2524 }
2525
2526 void
2527 EraseSeekDot(int i)
2528 {
2529     int x = xList[i], y = yList[i], d=squareSize/4, k;
2530     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2531     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2532     // now replot every dot that overlapped
2533     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2534         int xx = xList[k], yy = yList[k];
2535         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2536             DrawSeekDot(xx, yy, colorList[k]);
2537     }
2538 }
2539
2540 void
2541 RemoveSeekAd(int nr)
2542 {
2543         int i;
2544         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2545             EraseSeekDot(i);
2546             if(seekAdList[i]) free(seekAdList[i]);
2547             seekAdList[i] = seekAdList[--nrOfSeekAds];
2548             seekNrList[i] = seekNrList[nrOfSeekAds];
2549             ratingList[i] = ratingList[nrOfSeekAds];
2550             colorList[i]  = colorList[nrOfSeekAds];
2551             tcList[i] = tcList[nrOfSeekAds];
2552             xList[i]  = xList[nrOfSeekAds];
2553             yList[i]  = yList[nrOfSeekAds];
2554             zList[i]  = zList[nrOfSeekAds];
2555             seekAdList[nrOfSeekAds] = NULL;
2556             break;
2557         }
2558 }
2559
2560 Boolean
2561 MatchSoughtLine(char *line)
2562 {
2563     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2564     int nr, base, inc, u=0; char dummy;
2565
2566     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2567        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2568        (u=1) &&
2569        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2570         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2571         // match: compact and save the line
2572         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2573         return TRUE;
2574     }
2575     return FALSE;
2576 }
2577
2578 int
2579 DrawSeekGraph()
2580 {
2581     int i;
2582     if(!seekGraphUp) return FALSE;
2583     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2584     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2585
2586     DrawSeekBackground(0, 0, w, h);
2587     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2588     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2589     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2590         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2591         yy = h-1-yy;
2592         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2593         if(i%500 == 0) {
2594             char buf[MSG_SIZ];
2595             snprintf(buf, MSG_SIZ, "%d", i);
2596             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2597         }
2598     }
2599     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2600     for(i=1; i<100; i+=(i<10?1:5)) {
2601         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2602         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2603         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2604             char buf[MSG_SIZ];
2605             snprintf(buf, MSG_SIZ, "%d", i);
2606             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2607         }
2608     }
2609     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2610     return TRUE;
2611 }
2612
2613 int SeekGraphClick(ClickType click, int x, int y, int moving)
2614 {
2615     static int lastDown = 0, displayed = 0, lastSecond;
2616     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2617         if(click == Release || moving) return FALSE;
2618         nrOfSeekAds = 0;
2619         soughtPending = TRUE;
2620         SendToICS(ics_prefix);
2621         SendToICS("sought\n"); // should this be "sought all"?
2622     } else { // issue challenge based on clicked ad
2623         int dist = 10000; int i, closest = 0, second = 0;
2624         for(i=0; i<nrOfSeekAds; i++) {
2625             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2626             if(d < dist) { dist = d; closest = i; }
2627             second += (d - zList[i] < 120); // count in-range ads
2628             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2629         }
2630         if(dist < 120) {
2631             char buf[MSG_SIZ];
2632             second = (second > 1);
2633             if(displayed != closest || second != lastSecond) {
2634                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2635                 lastSecond = second; displayed = closest;
2636             }
2637             if(click == Press) {
2638                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2639                 lastDown = closest;
2640                 return TRUE;
2641             } // on press 'hit', only show info
2642             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2643             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2644             SendToICS(ics_prefix);
2645             SendToICS(buf);
2646             return TRUE; // let incoming board of started game pop down the graph
2647         } else if(click == Release) { // release 'miss' is ignored
2648             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2649             if(moving == 2) { // right up-click
2650                 nrOfSeekAds = 0; // refresh graph
2651                 soughtPending = TRUE;
2652                 SendToICS(ics_prefix);
2653                 SendToICS("sought\n"); // should this be "sought all"?
2654             }
2655             return TRUE;
2656         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2657         // press miss or release hit 'pop down' seek graph
2658         seekGraphUp = FALSE;
2659         DrawPosition(TRUE, NULL);
2660     }
2661     return TRUE;
2662 }
2663
2664 void
2665 read_from_ics(isr, closure, data, count, error)
2666      InputSourceRef isr;
2667      VOIDSTAR closure;
2668      char *data;
2669      int count;
2670      int error;
2671 {
2672 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2673 #define STARTED_NONE 0
2674 #define STARTED_MOVES 1
2675 #define STARTED_BOARD 2
2676 #define STARTED_OBSERVE 3
2677 #define STARTED_HOLDINGS 4
2678 #define STARTED_CHATTER 5
2679 #define STARTED_COMMENT 6
2680 #define STARTED_MOVES_NOHIDE 7
2681
2682     static int started = STARTED_NONE;
2683     static char parse[20000];
2684     static int parse_pos = 0;
2685     static char buf[BUF_SIZE + 1];
2686     static int firstTime = TRUE, intfSet = FALSE;
2687     static ColorClass prevColor = ColorNormal;
2688     static int savingComment = FALSE;
2689     static int cmatch = 0; // continuation sequence match
2690     char *bp;
2691     char str[MSG_SIZ];
2692     int i, oldi;
2693     int buf_len;
2694     int next_out;
2695     int tkind;
2696     int backup;    /* [DM] For zippy color lines */
2697     char *p;
2698     char talker[MSG_SIZ]; // [HGM] chat
2699     int channel;
2700
2701     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2702
2703     if (appData.debugMode) {
2704       if (!error) {
2705         fprintf(debugFP, "<ICS: ");
2706         show_bytes(debugFP, data, count);
2707         fprintf(debugFP, "\n");
2708       }
2709     }
2710
2711     if (appData.debugMode) { int f = forwardMostMove;
2712         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2713                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2714                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2715     }
2716     if (count > 0) {
2717         /* If last read ended with a partial line that we couldn't parse,
2718            prepend it to the new read and try again. */
2719         if (leftover_len > 0) {
2720             for (i=0; i<leftover_len; i++)
2721               buf[i] = buf[leftover_start + i];
2722         }
2723
2724     /* copy new characters into the buffer */
2725     bp = buf + leftover_len;
2726     buf_len=leftover_len;
2727     for (i=0; i<count; i++)
2728     {
2729         // ignore these
2730         if (data[i] == '\r')
2731             continue;
2732
2733         // join lines split by ICS?
2734         if (!appData.noJoin)
2735         {
2736             /*
2737                 Joining just consists of finding matches against the
2738                 continuation sequence, and discarding that sequence
2739                 if found instead of copying it.  So, until a match
2740                 fails, there's nothing to do since it might be the
2741                 complete sequence, and thus, something we don't want
2742                 copied.
2743             */
2744             if (data[i] == cont_seq[cmatch])
2745             {
2746                 cmatch++;
2747                 if (cmatch == strlen(cont_seq))
2748                 {
2749                     cmatch = 0; // complete match.  just reset the counter
2750
2751                     /*
2752                         it's possible for the ICS to not include the space
2753                         at the end of the last word, making our [correct]
2754                         join operation fuse two separate words.  the server
2755                         does this when the space occurs at the width setting.
2756                     */
2757                     if (!buf_len || buf[buf_len-1] != ' ')
2758                     {
2759                         *bp++ = ' ';
2760                         buf_len++;
2761                     }
2762                 }
2763                 continue;
2764             }
2765             else if (cmatch)
2766             {
2767                 /*
2768                     match failed, so we have to copy what matched before
2769                     falling through and copying this character.  In reality,
2770                     this will only ever be just the newline character, but
2771                     it doesn't hurt to be precise.
2772                 */
2773                 strncpy(bp, cont_seq, cmatch);
2774                 bp += cmatch;
2775                 buf_len += cmatch;
2776                 cmatch = 0;
2777             }
2778         }
2779
2780         // copy this char
2781         *bp++ = data[i];
2782         buf_len++;
2783     }
2784
2785         buf[buf_len] = NULLCHAR;
2786 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2787         next_out = 0;
2788         leftover_start = 0;
2789
2790         i = 0;
2791         while (i < buf_len) {
2792             /* Deal with part of the TELNET option negotiation
2793                protocol.  We refuse to do anything beyond the
2794                defaults, except that we allow the WILL ECHO option,
2795                which ICS uses to turn off password echoing when we are
2796                directly connected to it.  We reject this option
2797                if localLineEditing mode is on (always on in xboard)
2798                and we are talking to port 23, which might be a real
2799                telnet server that will try to keep WILL ECHO on permanently.
2800              */
2801             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2802                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2803                 unsigned char option;
2804                 oldi = i;
2805                 switch ((unsigned char) buf[++i]) {
2806                   case TN_WILL:
2807                     if (appData.debugMode)
2808                       fprintf(debugFP, "\n<WILL ");
2809                     switch (option = (unsigned char) buf[++i]) {
2810                       case TN_ECHO:
2811                         if (appData.debugMode)
2812                           fprintf(debugFP, "ECHO ");
2813                         /* Reply only if this is a change, according
2814                            to the protocol rules. */
2815                         if (remoteEchoOption) break;
2816                         if (appData.localLineEditing &&
2817                             atoi(appData.icsPort) == TN_PORT) {
2818                             TelnetRequest(TN_DONT, TN_ECHO);
2819                         } else {
2820                             EchoOff();
2821                             TelnetRequest(TN_DO, TN_ECHO);
2822                             remoteEchoOption = TRUE;
2823                         }
2824                         break;
2825                       default:
2826                         if (appData.debugMode)
2827                           fprintf(debugFP, "%d ", option);
2828                         /* Whatever this is, we don't want it. */
2829                         TelnetRequest(TN_DONT, option);
2830                         break;
2831                     }
2832                     break;
2833                   case TN_WONT:
2834                     if (appData.debugMode)
2835                       fprintf(debugFP, "\n<WONT ");
2836                     switch (option = (unsigned char) buf[++i]) {
2837                       case TN_ECHO:
2838                         if (appData.debugMode)
2839                           fprintf(debugFP, "ECHO ");
2840                         /* Reply only if this is a change, according
2841                            to the protocol rules. */
2842                         if (!remoteEchoOption) break;
2843                         EchoOn();
2844                         TelnetRequest(TN_DONT, TN_ECHO);
2845                         remoteEchoOption = FALSE;
2846                         break;
2847                       default:
2848                         if (appData.debugMode)
2849                           fprintf(debugFP, "%d ", (unsigned char) option);
2850                         /* Whatever this is, it must already be turned
2851                            off, because we never agree to turn on
2852                            anything non-default, so according to the
2853                            protocol rules, we don't reply. */
2854                         break;
2855                     }
2856                     break;
2857                   case TN_DO:
2858                     if (appData.debugMode)
2859                       fprintf(debugFP, "\n<DO ");
2860                     switch (option = (unsigned char) buf[++i]) {
2861                       default:
2862                         /* Whatever this is, we refuse to do it. */
2863                         if (appData.debugMode)
2864                           fprintf(debugFP, "%d ", option);
2865                         TelnetRequest(TN_WONT, option);
2866                         break;
2867                     }
2868                     break;
2869                   case TN_DONT:
2870                     if (appData.debugMode)
2871                       fprintf(debugFP, "\n<DONT ");
2872                     switch (option = (unsigned char) buf[++i]) {
2873                       default:
2874                         if (appData.debugMode)
2875                           fprintf(debugFP, "%d ", option);
2876                         /* Whatever this is, we are already not doing
2877                            it, because we never agree to do anything
2878                            non-default, so according to the protocol
2879                            rules, we don't reply. */
2880                         break;
2881                     }
2882                     break;
2883                   case TN_IAC:
2884                     if (appData.debugMode)
2885                       fprintf(debugFP, "\n<IAC ");
2886                     /* Doubled IAC; pass it through */
2887                     i--;
2888                     break;
2889                   default:
2890                     if (appData.debugMode)
2891                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2892                     /* Drop all other telnet commands on the floor */
2893                     break;
2894                 }
2895                 if (oldi > next_out)
2896                   SendToPlayer(&buf[next_out], oldi - next_out);
2897                 if (++i > next_out)
2898                   next_out = i;
2899                 continue;
2900             }
2901
2902             /* OK, this at least will *usually* work */
2903             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2904                 loggedOn = TRUE;
2905             }
2906
2907             if (loggedOn && !intfSet) {
2908                 if (ics_type == ICS_ICC) {
2909                   snprintf(str, MSG_SIZ,
2910                           "/set-quietly interface %s\n/set-quietly style 12\n",
2911                           programVersion);
2912                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2913                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2914                 } else if (ics_type == ICS_CHESSNET) {
2915                   snprintf(str, MSG_SIZ, "/style 12\n");
2916                 } else {
2917                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2918                   strcat(str, programVersion);
2919                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2920                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2921                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2922 #ifdef WIN32
2923                   strcat(str, "$iset nohighlight 1\n");
2924 #endif
2925                   strcat(str, "$iset lock 1\n$style 12\n");
2926                 }
2927                 SendToICS(str);
2928                 NotifyFrontendLogin();
2929                 intfSet = TRUE;
2930             }
2931
2932             if (started == STARTED_COMMENT) {
2933                 /* Accumulate characters in comment */
2934                 parse[parse_pos++] = buf[i];
2935                 if (buf[i] == '\n') {
2936                     parse[parse_pos] = NULLCHAR;
2937                     if(chattingPartner>=0) {
2938                         char mess[MSG_SIZ];
2939                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2940                         OutputChatMessage(chattingPartner, mess);
2941                         chattingPartner = -1;
2942                         next_out = i+1; // [HGM] suppress printing in ICS window
2943                     } else
2944                     if(!suppressKibitz) // [HGM] kibitz
2945                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2946                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2947                         int nrDigit = 0, nrAlph = 0, j;
2948                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2949                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2950                         parse[parse_pos] = NULLCHAR;
2951                         // try to be smart: if it does not look like search info, it should go to
2952                         // ICS interaction window after all, not to engine-output window.
2953                         for(j=0; j<parse_pos; j++) { // count letters and digits
2954                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2955                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2956                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2957                         }
2958                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2959                             int depth=0; float score;
2960                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2961                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2962                                 pvInfoList[forwardMostMove-1].depth = depth;
2963                                 pvInfoList[forwardMostMove-1].score = 100*score;
2964                             }
2965                             OutputKibitz(suppressKibitz, parse);
2966                         } else {
2967                             char tmp[MSG_SIZ];
2968                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2969                             SendToPlayer(tmp, strlen(tmp));
2970                         }
2971                         next_out = i+1; // [HGM] suppress printing in ICS window
2972                     }
2973                     started = STARTED_NONE;
2974                 } else {
2975                     /* Don't match patterns against characters in comment */
2976                     i++;
2977                     continue;
2978                 }
2979             }
2980             if (started == STARTED_CHATTER) {
2981                 if (buf[i] != '\n') {
2982                     /* Don't match patterns against characters in chatter */
2983                     i++;
2984                     continue;
2985                 }
2986                 started = STARTED_NONE;
2987                 if(suppressKibitz) next_out = i+1;
2988             }
2989
2990             /* Kludge to deal with rcmd protocol */
2991             if (firstTime && looking_at(buf, &i, "\001*")) {
2992                 DisplayFatalError(&buf[1], 0, 1);
2993                 continue;
2994             } else {
2995                 firstTime = FALSE;
2996             }
2997
2998             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2999                 ics_type = ICS_ICC;
3000                 ics_prefix = "/";
3001                 if (appData.debugMode)
3002                   fprintf(debugFP, "ics_type %d\n", ics_type);
3003                 continue;
3004             }
3005             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3006                 ics_type = ICS_FICS;
3007                 ics_prefix = "$";
3008                 if (appData.debugMode)
3009                   fprintf(debugFP, "ics_type %d\n", ics_type);
3010                 continue;
3011             }
3012             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3013                 ics_type = ICS_CHESSNET;
3014                 ics_prefix = "/";
3015                 if (appData.debugMode)
3016                   fprintf(debugFP, "ics_type %d\n", ics_type);
3017                 continue;
3018             }
3019
3020             if (!loggedOn &&
3021                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3022                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3023                  looking_at(buf, &i, "will be \"*\""))) {
3024               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3025               continue;
3026             }
3027
3028             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3029               char buf[MSG_SIZ];
3030               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3031               DisplayIcsInteractionTitle(buf);
3032               have_set_title = TRUE;
3033             }
3034
3035             /* skip finger notes */
3036             if (started == STARTED_NONE &&
3037                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3038                  (buf[i] == '1' && buf[i+1] == '0')) &&
3039                 buf[i+2] == ':' && buf[i+3] == ' ') {
3040               started = STARTED_CHATTER;
3041               i += 3;
3042               continue;
3043             }
3044
3045             oldi = i;
3046             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3047             if(appData.seekGraph) {
3048                 if(soughtPending && MatchSoughtLine(buf+i)) {
3049                     i = strstr(buf+i, "rated") - buf;
3050                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3051                     next_out = leftover_start = i;
3052                     started = STARTED_CHATTER;
3053                     suppressKibitz = TRUE;
3054                     continue;
3055                 }
3056                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3057                         && looking_at(buf, &i, "* ads displayed")) {
3058                     soughtPending = FALSE;
3059                     seekGraphUp = TRUE;
3060                     DrawSeekGraph();
3061                     continue;
3062                 }
3063                 if(appData.autoRefresh) {
3064                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3065                         int s = (ics_type == ICS_ICC); // ICC format differs
3066                         if(seekGraphUp)
3067                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3068                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3069                         looking_at(buf, &i, "*% "); // eat prompt
3070                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3071                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3072                         next_out = i; // suppress
3073                         continue;
3074                     }
3075                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3076                         char *p = star_match[0];
3077                         while(*p) {
3078                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3079                             while(*p && *p++ != ' '); // next
3080                         }
3081                         looking_at(buf, &i, "*% "); // eat prompt
3082                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3083                         next_out = i;
3084                         continue;
3085                     }
3086                 }
3087             }
3088
3089             /* skip formula vars */
3090             if (started == STARTED_NONE &&
3091                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3092               started = STARTED_CHATTER;
3093               i += 3;
3094               continue;
3095             }
3096
3097             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3098             if (appData.autoKibitz && started == STARTED_NONE &&
3099                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3100                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3101                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3102                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3103                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3104                         suppressKibitz = TRUE;
3105                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3106                         next_out = i;
3107                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3108                                 && (gameMode == IcsPlayingWhite)) ||
3109                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3110                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3111                             started = STARTED_CHATTER; // own kibitz we simply discard
3112                         else {
3113                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3114                             parse_pos = 0; parse[0] = NULLCHAR;
3115                             savingComment = TRUE;
3116                             suppressKibitz = gameMode != IcsObserving ? 2 :
3117                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3118                         }
3119                         continue;
3120                 } else
3121                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3122                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3123                          && atoi(star_match[0])) {
3124                     // suppress the acknowledgements of our own autoKibitz
3125                     char *p;
3126                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3127                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3128                     SendToPlayer(star_match[0], strlen(star_match[0]));
3129                     if(looking_at(buf, &i, "*% ")) // eat prompt
3130                         suppressKibitz = FALSE;
3131                     next_out = i;
3132                     continue;
3133                 }
3134             } // [HGM] kibitz: end of patch
3135
3136             // [HGM] chat: intercept tells by users for which we have an open chat window
3137             channel = -1;
3138             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3139                                            looking_at(buf, &i, "* whispers:") ||
3140                                            looking_at(buf, &i, "* kibitzes:") ||
3141                                            looking_at(buf, &i, "* shouts:") ||
3142                                            looking_at(buf, &i, "* c-shouts:") ||
3143                                            looking_at(buf, &i, "--> * ") ||
3144                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3145                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3146                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3148                 int p;
3149                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3150                 chattingPartner = -1;
3151
3152                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3153                 for(p=0; p<MAX_CHAT; p++) {
3154                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3155                     talker[0] = '['; strcat(talker, "] ");
3156                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3157                     chattingPartner = p; break;
3158                     }
3159                 } else
3160                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3161                 for(p=0; p<MAX_CHAT; p++) {
3162                     if(!strcmp("kibitzes", chatPartner[p])) {
3163                         talker[0] = '['; strcat(talker, "] ");
3164                         chattingPartner = p; break;
3165                     }
3166                 } else
3167                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3168                 for(p=0; p<MAX_CHAT; p++) {
3169                     if(!strcmp("whispers", chatPartner[p])) {
3170                         talker[0] = '['; strcat(talker, "] ");
3171                         chattingPartner = p; break;
3172                     }
3173                 } else
3174                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3175                   if(buf[i-8] == '-' && buf[i-3] == 't')
3176                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3177                     if(!strcmp("c-shouts", chatPartner[p])) {
3178                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3179                         chattingPartner = p; break;
3180                     }
3181                   }
3182                   if(chattingPartner < 0)
3183                   for(p=0; p<MAX_CHAT; p++) {
3184                     if(!strcmp("shouts", chatPartner[p])) {
3185                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3186                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3187                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3188                         chattingPartner = p; break;
3189                     }
3190                   }
3191                 }
3192                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3193                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3194                     talker[0] = 0; Colorize(ColorTell, FALSE);
3195                     chattingPartner = p; break;
3196                 }
3197                 if(chattingPartner<0) i = oldi; else {
3198                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3199                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3200                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3201                     started = STARTED_COMMENT;
3202                     parse_pos = 0; parse[0] = NULLCHAR;
3203                     savingComment = 3 + chattingPartner; // counts as TRUE
3204                     suppressKibitz = TRUE;
3205                     continue;
3206                 }
3207             } // [HGM] chat: end of patch
3208
3209           backup = i;
3210             if (appData.zippyTalk || appData.zippyPlay) {
3211                 /* [DM] Backup address for color zippy lines */
3212 #if ZIPPY
3213                if (loggedOn == TRUE)
3214                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3215                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3216 #endif
3217             } // [DM] 'else { ' deleted
3218                 if (
3219                     /* Regular tells and says */
3220                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3221                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3222                     looking_at(buf, &i, "* says: ") ||
3223                     /* Don't color "message" or "messages" output */
3224                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3225                     looking_at(buf, &i, "*. * at *:*: ") ||
3226                     looking_at(buf, &i, "--* (*:*): ") ||
3227                     /* Message notifications (same color as tells) */
3228                     looking_at(buf, &i, "* has left a message ") ||
3229                     looking_at(buf, &i, "* just sent you a message:\n") ||
3230                     /* Whispers and kibitzes */
3231                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3232                     looking_at(buf, &i, "* kibitzes: ") ||
3233                     /* Channel tells */
3234                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3235
3236                   if (tkind == 1 && strchr(star_match[0], ':')) {
3237                       /* Avoid "tells you:" spoofs in channels */
3238                      tkind = 3;
3239                   }
3240                   if (star_match[0][0] == NULLCHAR ||
3241                       strchr(star_match[0], ' ') ||
3242                       (tkind == 3 && strchr(star_match[1], ' '))) {
3243                     /* Reject bogus matches */
3244                     i = oldi;
3245                   } else {
3246                     if (appData.colorize) {
3247                       if (oldi > next_out) {
3248                         SendToPlayer(&buf[next_out], oldi - next_out);
3249                         next_out = oldi;
3250                       }
3251                       switch (tkind) {
3252                       case 1:
3253                         Colorize(ColorTell, FALSE);
3254                         curColor = ColorTell;
3255                         break;
3256                       case 2:
3257                         Colorize(ColorKibitz, FALSE);
3258                         curColor = ColorKibitz;
3259                         break;
3260                       case 3:
3261                         p = strrchr(star_match[1], '(');
3262                         if (p == NULL) {
3263                           p = star_match[1];
3264                         } else {
3265                           p++;
3266                         }
3267                         if (atoi(p) == 1) {
3268                           Colorize(ColorChannel1, FALSE);
3269                           curColor = ColorChannel1;
3270                         } else {
3271                           Colorize(ColorChannel, FALSE);
3272                           curColor = ColorChannel;
3273                         }
3274                         break;
3275                       case 5:
3276                         curColor = ColorNormal;
3277                         break;
3278                       }
3279                     }
3280                     if (started == STARTED_NONE && appData.autoComment &&
3281                         (gameMode == IcsObserving ||
3282                          gameMode == IcsPlayingWhite ||
3283                          gameMode == IcsPlayingBlack)) {
3284                       parse_pos = i - oldi;
3285                       memcpy(parse, &buf[oldi], parse_pos);
3286                       parse[parse_pos] = NULLCHAR;
3287                       started = STARTED_COMMENT;
3288                       savingComment = TRUE;
3289                     } else {
3290                       started = STARTED_CHATTER;
3291                       savingComment = FALSE;
3292                     }
3293                     loggedOn = TRUE;
3294                     continue;
3295                   }
3296                 }
3297
3298                 if (looking_at(buf, &i, "* s-shouts: ") ||
3299                     looking_at(buf, &i, "* c-shouts: ")) {
3300                     if (appData.colorize) {
3301                         if (oldi > next_out) {
3302                             SendToPlayer(&buf[next_out], oldi - next_out);
3303                             next_out = oldi;
3304                         }
3305                         Colorize(ColorSShout, FALSE);
3306                         curColor = ColorSShout;
3307                     }
3308                     loggedOn = TRUE;
3309                     started = STARTED_CHATTER;
3310                     continue;
3311                 }
3312
3313                 if (looking_at(buf, &i, "--->")) {
3314                     loggedOn = TRUE;
3315                     continue;
3316                 }
3317
3318                 if (looking_at(buf, &i, "* shouts: ") ||
3319                     looking_at(buf, &i, "--> ")) {
3320                     if (appData.colorize) {
3321                         if (oldi > next_out) {
3322                             SendToPlayer(&buf[next_out], oldi - next_out);
3323                             next_out = oldi;
3324                         }
3325                         Colorize(ColorShout, FALSE);
3326                         curColor = ColorShout;
3327                     }
3328                     loggedOn = TRUE;
3329                     started = STARTED_CHATTER;
3330                     continue;
3331                 }
3332
3333                 if (looking_at( buf, &i, "Challenge:")) {
3334                     if (appData.colorize) {
3335                         if (oldi > next_out) {
3336                             SendToPlayer(&buf[next_out], oldi - next_out);
3337                             next_out = oldi;
3338                         }
3339                         Colorize(ColorChallenge, FALSE);
3340                         curColor = ColorChallenge;
3341                     }
3342                     loggedOn = TRUE;
3343                     continue;
3344                 }
3345
3346                 if (looking_at(buf, &i, "* offers you") ||
3347                     looking_at(buf, &i, "* offers to be") ||
3348                     looking_at(buf, &i, "* would like to") ||
3349                     looking_at(buf, &i, "* requests to") ||
3350                     looking_at(buf, &i, "Your opponent offers") ||
3351                     looking_at(buf, &i, "Your opponent requests")) {
3352
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorRequest, FALSE);
3359                         curColor = ColorRequest;
3360                     }
3361                     continue;
3362                 }
3363
3364                 if (looking_at(buf, &i, "* (*) seeking")) {
3365                     if (appData.colorize) {
3366                         if (oldi > next_out) {
3367                             SendToPlayer(&buf[next_out], oldi - next_out);
3368                             next_out = oldi;
3369                         }
3370                         Colorize(ColorSeek, FALSE);
3371                         curColor = ColorSeek;
3372                     }
3373                     continue;
3374             }
3375
3376           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3377
3378             if (looking_at(buf, &i, "\\   ")) {
3379                 if (prevColor != ColorNormal) {
3380                     if (oldi > next_out) {
3381                         SendToPlayer(&buf[next_out], oldi - next_out);
3382                         next_out = oldi;
3383                     }
3384                     Colorize(prevColor, TRUE);
3385                     curColor = prevColor;
3386                 }
3387                 if (savingComment) {
3388                     parse_pos = i - oldi;
3389                     memcpy(parse, &buf[oldi], parse_pos);
3390                     parse[parse_pos] = NULLCHAR;
3391                     started = STARTED_COMMENT;
3392                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3393                         chattingPartner = savingComment - 3; // kludge to remember the box
3394                 } else {
3395                     started = STARTED_CHATTER;
3396                 }
3397                 continue;
3398             }
3399
3400             if (looking_at(buf, &i, "Black Strength :") ||
3401                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3402                 looking_at(buf, &i, "<10>") ||
3403                 looking_at(buf, &i, "#@#")) {
3404                 /* Wrong board style */
3405                 loggedOn = TRUE;
3406                 SendToICS(ics_prefix);
3407                 SendToICS("set style 12\n");
3408                 SendToICS(ics_prefix);
3409                 SendToICS("refresh\n");
3410                 continue;
3411             }
3412
3413             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3414                 ICSInitScript();
3415                 have_sent_ICS_logon = 1;
3416                 continue;
3417             }
3418
3419             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3420                 (looking_at(buf, &i, "\n<12> ") ||
3421                  looking_at(buf, &i, "<12> "))) {
3422                 loggedOn = TRUE;
3423                 if (oldi > next_out) {
3424                     SendToPlayer(&buf[next_out], oldi - next_out);
3425                 }
3426                 next_out = i;
3427                 started = STARTED_BOARD;
3428                 parse_pos = 0;
3429                 continue;
3430             }
3431
3432             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3433                 looking_at(buf, &i, "<b1> ")) {
3434                 if (oldi > next_out) {
3435                     SendToPlayer(&buf[next_out], oldi - next_out);
3436                 }
3437                 next_out = i;
3438                 started = STARTED_HOLDINGS;
3439                 parse_pos = 0;
3440                 continue;
3441             }
3442
3443             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3444                 loggedOn = TRUE;
3445                 /* Header for a move list -- first line */
3446
3447                 switch (ics_getting_history) {
3448                   case H_FALSE:
3449                     switch (gameMode) {
3450                       case IcsIdle:
3451                       case BeginningOfGame:
3452                         /* User typed "moves" or "oldmoves" while we
3453                            were idle.  Pretend we asked for these
3454                            moves and soak them up so user can step
3455                            through them and/or save them.
3456                            */
3457                         Reset(FALSE, TRUE);
3458                         gameMode = IcsObserving;
3459                         ModeHighlight();
3460                         ics_gamenum = -1;
3461                         ics_getting_history = H_GOT_UNREQ_HEADER;
3462                         break;
3463                       case EditGame: /*?*/
3464                       case EditPosition: /*?*/
3465                         /* Should above feature work in these modes too? */
3466                         /* For now it doesn't */
3467                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3468                         break;
3469                       default:
3470                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3471                         break;
3472                     }
3473                     break;
3474                   case H_REQUESTED:
3475                     /* Is this the right one? */
3476                     if (gameInfo.white && gameInfo.black &&
3477                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3478                         strcmp(gameInfo.black, star_match[2]) == 0) {
3479                         /* All is well */
3480                         ics_getting_history = H_GOT_REQ_HEADER;
3481                     }
3482                     break;
3483                   case H_GOT_REQ_HEADER:
3484                   case H_GOT_UNREQ_HEADER:
3485                   case H_GOT_UNWANTED_HEADER:
3486                   case H_GETTING_MOVES:
3487                     /* Should not happen */
3488                     DisplayError(_("Error gathering move list: two headers"), 0);
3489                     ics_getting_history = H_FALSE;
3490                     break;
3491                 }
3492
3493                 /* Save player ratings into gameInfo if needed */
3494                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3495                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3496                     (gameInfo.whiteRating == -1 ||
3497                      gameInfo.blackRating == -1)) {
3498
3499                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3500                     gameInfo.blackRating = string_to_rating(star_match[3]);
3501                     if (appData.debugMode)
3502                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3503                               gameInfo.whiteRating, gameInfo.blackRating);
3504                 }
3505                 continue;
3506             }
3507
3508             if (looking_at(buf, &i,
3509               "* * match, initial time: * minute*, increment: * second")) {
3510                 /* Header for a move list -- second line */
3511                 /* Initial board will follow if this is a wild game */
3512                 if (gameInfo.event != NULL) free(gameInfo.event);
3513                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3514                 gameInfo.event = StrSave(str);
3515                 /* [HGM] we switched variant. Translate boards if needed. */
3516                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3517                 continue;
3518             }
3519
3520             if (looking_at(buf, &i, "Move  ")) {
3521                 /* Beginning of a move list */
3522                 switch (ics_getting_history) {
3523                   case H_FALSE:
3524                     /* Normally should not happen */
3525                     /* Maybe user hit reset while we were parsing */
3526                     break;
3527                   case H_REQUESTED:
3528                     /* Happens if we are ignoring a move list that is not
3529                      * the one we just requested.  Common if the user
3530                      * tries to observe two games without turning off
3531                      * getMoveList */
3532                     break;
3533                   case H_GETTING_MOVES:
3534                     /* Should not happen */
3535                     DisplayError(_("Error gathering move list: nested"), 0);
3536                     ics_getting_history = H_FALSE;
3537                     break;
3538                   case H_GOT_REQ_HEADER:
3539                     ics_getting_history = H_GETTING_MOVES;
3540                     started = STARTED_MOVES;
3541                     parse_pos = 0;
3542                     if (oldi > next_out) {
3543                         SendToPlayer(&buf[next_out], oldi - next_out);
3544                     }
3545                     break;
3546                   case H_GOT_UNREQ_HEADER:
3547                     ics_getting_history = H_GETTING_MOVES;
3548                     started = STARTED_MOVES_NOHIDE;
3549                     parse_pos = 0;
3550                     break;
3551                   case H_GOT_UNWANTED_HEADER:
3552                     ics_getting_history = H_FALSE;
3553                     break;
3554                 }
3555                 continue;
3556             }
3557
3558             if (looking_at(buf, &i, "% ") ||
3559                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3560                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3561                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3562                     soughtPending = FALSE;
3563                     seekGraphUp = TRUE;
3564                     DrawSeekGraph();
3565                 }
3566                 if(suppressKibitz) next_out = i;
3567                 savingComment = FALSE;
3568                 suppressKibitz = 0;
3569                 switch (started) {
3570                   case STARTED_MOVES:
3571                   case STARTED_MOVES_NOHIDE:
3572                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3573                     parse[parse_pos + i - oldi] = NULLCHAR;
3574                     ParseGameHistory(parse);
3575 #if ZIPPY
3576                     if (appData.zippyPlay && first.initDone) {
3577                         FeedMovesToProgram(&first, forwardMostMove);
3578                         if (gameMode == IcsPlayingWhite) {
3579                             if (WhiteOnMove(forwardMostMove)) {
3580                                 if (first.sendTime) {
3581                                   if (first.useColors) {
3582                                     SendToProgram("black\n", &first);
3583                                   }
3584                                   SendTimeRemaining(&first, TRUE);
3585                                 }
3586                                 if (first.useColors) {
3587                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3588                                 }
3589                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3590                                 first.maybeThinking = TRUE;
3591                             } else {
3592                                 if (first.usePlayother) {
3593                                   if (first.sendTime) {
3594                                     SendTimeRemaining(&first, TRUE);
3595                                   }
3596                                   SendToProgram("playother\n", &first);
3597                                   firstMove = FALSE;
3598                                 } else {
3599                                   firstMove = TRUE;
3600                                 }
3601                             }
3602                         } else if (gameMode == IcsPlayingBlack) {
3603                             if (!WhiteOnMove(forwardMostMove)) {
3604                                 if (first.sendTime) {
3605                                   if (first.useColors) {
3606                                     SendToProgram("white\n", &first);
3607                                   }
3608                                   SendTimeRemaining(&first, FALSE);
3609                                 }
3610                                 if (first.useColors) {
3611                                   SendToProgram("black\n", &first);
3612                                 }
3613                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3614                                 first.maybeThinking = TRUE;
3615                             } else {
3616                                 if (first.usePlayother) {
3617                                   if (first.sendTime) {
3618                                     SendTimeRemaining(&first, FALSE);
3619                                   }
3620                                   SendToProgram("playother\n", &first);
3621                                   firstMove = FALSE;
3622                                 } else {
3623                                   firstMove = TRUE;
3624                                 }
3625                             }
3626                         }
3627                     }
3628 #endif
3629                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3630                         /* Moves came from oldmoves or moves command
3631                            while we weren't doing anything else.
3632                            */
3633                         currentMove = forwardMostMove;
3634                         ClearHighlights();/*!!could figure this out*/
3635                         flipView = appData.flipView;
3636                         DrawPosition(TRUE, boards[currentMove]);
3637                         DisplayBothClocks();
3638                         snprintf(str, MSG_SIZ, "%s vs. %s",
3639                                 gameInfo.white, gameInfo.black);
3640                         DisplayTitle(str);
3641                         gameMode = IcsIdle;
3642                     } else {
3643                         /* Moves were history of an active game */
3644                         if (gameInfo.resultDetails != NULL) {
3645                             free(gameInfo.resultDetails);
3646                             gameInfo.resultDetails = NULL;
3647                         }
3648                     }
3649                     HistorySet(parseList, backwardMostMove,
3650                                forwardMostMove, currentMove-1);
3651                     DisplayMove(currentMove - 1);
3652                     if (started == STARTED_MOVES) next_out = i;
3653                     started = STARTED_NONE;
3654                     ics_getting_history = H_FALSE;
3655                     break;
3656
3657                   case STARTED_OBSERVE:
3658                     started = STARTED_NONE;
3659                     SendToICS(ics_prefix);
3660                     SendToICS("refresh\n");
3661                     break;
3662
3663                   default:
3664                     break;
3665                 }
3666                 if(bookHit) { // [HGM] book: simulate book reply
3667                     static char bookMove[MSG_SIZ]; // a bit generous?
3668
3669                     programStats.nodes = programStats.depth = programStats.time =
3670                     programStats.score = programStats.got_only_move = 0;
3671                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3672
3673                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3674                     strcat(bookMove, bookHit);
3675                     HandleMachineMove(bookMove, &first);
3676                 }
3677                 continue;
3678             }
3679
3680             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3681                  started == STARTED_HOLDINGS ||
3682                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3683                 /* Accumulate characters in move list or board */
3684                 parse[parse_pos++] = buf[i];
3685             }
3686
3687             /* Start of game messages.  Mostly we detect start of game
3688                when the first board image arrives.  On some versions
3689                of the ICS, though, we need to do a "refresh" after starting
3690                to observe in order to get the current board right away. */
3691             if (looking_at(buf, &i, "Adding game * to observation list")) {
3692                 started = STARTED_OBSERVE;
3693                 continue;
3694             }
3695
3696             /* Handle auto-observe */
3697             if (appData.autoObserve &&
3698                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3699                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3700                 char *player;
3701                 /* Choose the player that was highlighted, if any. */
3702                 if (star_match[0][0] == '\033' ||
3703                     star_match[1][0] != '\033') {
3704                     player = star_match[0];
3705                 } else {
3706                     player = star_match[2];
3707                 }
3708                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3709                         ics_prefix, StripHighlightAndTitle(player));
3710                 SendToICS(str);
3711
3712                 /* Save ratings from notify string */
3713                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3714                 player1Rating = string_to_rating(star_match[1]);
3715                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3716                 player2Rating = string_to_rating(star_match[3]);
3717
3718                 if (appData.debugMode)
3719                   fprintf(debugFP,
3720                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3721                           player1Name, player1Rating,
3722                           player2Name, player2Rating);
3723
3724                 continue;
3725             }
3726
3727             /* Deal with automatic examine mode after a game,
3728                and with IcsObserving -> IcsExamining transition */
3729             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3730                 looking_at(buf, &i, "has made you an examiner of game *")) {
3731
3732                 int gamenum = atoi(star_match[0]);
3733                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3734                     gamenum == ics_gamenum) {
3735                     /* We were already playing or observing this game;
3736                        no need to refetch history */
3737                     gameMode = IcsExamining;
3738                     if (pausing) {
3739                         pauseExamForwardMostMove = forwardMostMove;
3740                     } else if (currentMove < forwardMostMove) {
3741                         ForwardInner(forwardMostMove);
3742                     }
3743                 } else {
3744                     /* I don't think this case really can happen */
3745                     SendToICS(ics_prefix);
3746                     SendToICS("refresh\n");
3747                 }
3748                 continue;
3749             }
3750
3751             /* Error messages */
3752 //          if (ics_user_moved) {
3753             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3754                 if (looking_at(buf, &i, "Illegal move") ||
3755                     looking_at(buf, &i, "Not a legal move") ||
3756                     looking_at(buf, &i, "Your king is in check") ||
3757                     looking_at(buf, &i, "It isn't your turn") ||
3758                     looking_at(buf, &i, "It is not your move")) {
3759                     /* Illegal move */
3760                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3761                         currentMove = forwardMostMove-1;
3762                         DisplayMove(currentMove - 1); /* before DMError */
3763                         DrawPosition(FALSE, boards[currentMove]);
3764                         SwitchClocks(forwardMostMove-1); // [HGM] race
3765                         DisplayBothClocks();
3766                     }
3767                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3768                     ics_user_moved = 0;
3769                     continue;
3770                 }
3771             }
3772
3773             if (looking_at(buf, &i, "still have time") ||
3774                 looking_at(buf, &i, "not out of time") ||
3775                 looking_at(buf, &i, "either player is out of time") ||
3776                 looking_at(buf, &i, "has timeseal; checking")) {
3777                 /* We must have called his flag a little too soon */
3778                 whiteFlag = blackFlag = FALSE;
3779                 continue;
3780             }
3781
3782             if (looking_at(buf, &i, "added * seconds to") ||
3783                 looking_at(buf, &i, "seconds were added to")) {
3784                 /* Update the clocks */
3785                 SendToICS(ics_prefix);
3786                 SendToICS("refresh\n");
3787                 continue;
3788             }
3789
3790             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3791                 ics_clock_paused = TRUE;
3792                 StopClocks();
3793                 continue;
3794             }
3795
3796             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3797                 ics_clock_paused = FALSE;
3798                 StartClocks();
3799                 continue;
3800             }
3801
3802             /* Grab player ratings from the Creating: message.
3803                Note we have to check for the special case when
3804                the ICS inserts things like [white] or [black]. */
3805             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3806                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3807                 /* star_matches:
3808                    0    player 1 name (not necessarily white)
3809                    1    player 1 rating
3810                    2    empty, white, or black (IGNORED)
3811                    3    player 2 name (not necessarily black)
3812                    4    player 2 rating
3813
3814                    The names/ratings are sorted out when the game
3815                    actually starts (below).
3816                 */
3817                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3818                 player1Rating = string_to_rating(star_match[1]);
3819                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3820                 player2Rating = string_to_rating(star_match[4]);
3821
3822                 if (appData.debugMode)
3823                   fprintf(debugFP,
3824                           "Ratings from 'Creating:' %s %d, %s %d\n",
3825                           player1Name, player1Rating,
3826                           player2Name, player2Rating);
3827
3828                 continue;
3829             }
3830
3831             /* Improved generic start/end-of-game messages */
3832             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3833                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3834                 /* If tkind == 0: */
3835                 /* star_match[0] is the game number */
3836                 /*           [1] is the white player's name */
3837                 /*           [2] is the black player's name */
3838                 /* For end-of-game: */
3839                 /*           [3] is the reason for the game end */
3840                 /*           [4] is a PGN end game-token, preceded by " " */
3841                 /* For start-of-game: */
3842                 /*           [3] begins with "Creating" or "Continuing" */
3843                 /*           [4] is " *" or empty (don't care). */
3844                 int gamenum = atoi(star_match[0]);
3845                 char *whitename, *blackname, *why, *endtoken;
3846                 ChessMove endtype = EndOfFile;
3847
3848                 if (tkind == 0) {
3849                   whitename = star_match[1];
3850                   blackname = star_match[2];
3851                   why = star_match[3];
3852                   endtoken = star_match[4];
3853                 } else {
3854                   whitename = star_match[1];
3855                   blackname = star_match[3];
3856                   why = star_match[5];
3857                   endtoken = star_match[6];
3858                 }
3859
3860                 /* Game start messages */
3861                 if (strncmp(why, "Creating ", 9) == 0 ||
3862                     strncmp(why, "Continuing ", 11) == 0) {
3863                     gs_gamenum = gamenum;
3864                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3865                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3866 #if ZIPPY
3867                     if (appData.zippyPlay) {
3868                         ZippyGameStart(whitename, blackname);
3869                     }
3870 #endif /*ZIPPY*/
3871                     partnerBoardValid = FALSE; // [HGM] bughouse
3872                     continue;
3873                 }
3874
3875                 /* Game end messages */
3876                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3877                     ics_gamenum != gamenum) {
3878                     continue;
3879                 }
3880                 while (endtoken[0] == ' ') endtoken++;
3881                 switch (endtoken[0]) {
3882                   case '*':
3883                   default:
3884                     endtype = GameUnfinished;
3885                     break;
3886                   case '0':
3887                     endtype = BlackWins;
3888                     break;
3889                   case '1':
3890                     if (endtoken[1] == '/')
3891                       endtype = GameIsDrawn;
3892                     else
3893                       endtype = WhiteWins;
3894                     break;
3895                 }
3896                 GameEnds(endtype, why, GE_ICS);
3897 #if ZIPPY
3898                 if (appData.zippyPlay && first.initDone) {
3899                     ZippyGameEnd(endtype, why);
3900                     if (first.pr == NoProc) {
3901                       /* Start the next process early so that we'll
3902                          be ready for the next challenge */
3903                       StartChessProgram(&first);
3904                     }
3905                     /* Send "new" early, in case this command takes
3906                        a long time to finish, so that we'll be ready
3907                        for the next challenge. */
3908                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3909                     Reset(TRUE, TRUE);
3910                 }
3911 #endif /*ZIPPY*/
3912                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3913                 continue;
3914             }
3915
3916             if (looking_at(buf, &i, "Removing game * from observation") ||
3917                 looking_at(buf, &i, "no longer observing game *") ||
3918                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3919                 if (gameMode == IcsObserving &&
3920                     atoi(star_match[0]) == ics_gamenum)
3921                   {
3922                       /* icsEngineAnalyze */
3923                       if (appData.icsEngineAnalyze) {
3924                             ExitAnalyzeMode();
3925                             ModeHighlight();
3926                       }
3927                       StopClocks();
3928                       gameMode = IcsIdle;
3929                       ics_gamenum = -1;
3930                       ics_user_moved = FALSE;
3931                   }
3932                 continue;
3933             }
3934
3935             if (looking_at(buf, &i, "no longer examining game *")) {
3936                 if (gameMode == IcsExamining &&
3937                     atoi(star_match[0]) == ics_gamenum)
3938                   {
3939                       gameMode = IcsIdle;
3940                       ics_gamenum = -1;
3941                       ics_user_moved = FALSE;
3942                   }
3943                 continue;
3944             }
3945
3946             /* Advance leftover_start past any newlines we find,
3947                so only partial lines can get reparsed */
3948             if (looking_at(buf, &i, "\n")) {
3949                 prevColor = curColor;
3950                 if (curColor != ColorNormal) {
3951                     if (oldi > next_out) {
3952                         SendToPlayer(&buf[next_out], oldi - next_out);
3953                         next_out = oldi;
3954                     }
3955                     Colorize(ColorNormal, FALSE);
3956                     curColor = ColorNormal;
3957                 }
3958                 if (started == STARTED_BOARD) {
3959                     started = STARTED_NONE;
3960                     parse[parse_pos] = NULLCHAR;
3961                     ParseBoard12(parse);
3962                     ics_user_moved = 0;
3963
3964                     /* Send premove here */
3965                     if (appData.premove) {
3966                       char str[MSG_SIZ];
3967                       if (currentMove == 0 &&
3968                           gameMode == IcsPlayingWhite &&
3969                           appData.premoveWhite) {
3970                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3971                         if (appData.debugMode)
3972                           fprintf(debugFP, "Sending premove:\n");
3973                         SendToICS(str);
3974                       } else if (currentMove == 1 &&
3975                                  gameMode == IcsPlayingBlack &&
3976                                  appData.premoveBlack) {
3977                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3978                         if (appData.debugMode)
3979                           fprintf(debugFP, "Sending premove:\n");
3980                         SendToICS(str);
3981                       } else if (gotPremove) {
3982                         gotPremove = 0;
3983                         ClearPremoveHighlights();
3984                         if (appData.debugMode)
3985                           fprintf(debugFP, "Sending premove:\n");
3986                           UserMoveEvent(premoveFromX, premoveFromY,
3987                                         premoveToX, premoveToY,
3988                                         premovePromoChar);
3989                       }
3990                     }
3991
3992                     /* Usually suppress following prompt */
3993                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3994                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3995                         if (looking_at(buf, &i, "*% ")) {
3996                             savingComment = FALSE;
3997                             suppressKibitz = 0;
3998                         }
3999                     }
4000                     next_out = i;
4001                 } else if (started == STARTED_HOLDINGS) {
4002                     int gamenum;
4003                     char new_piece[MSG_SIZ];
4004                     started = STARTED_NONE;
4005                     parse[parse_pos] = NULLCHAR;
4006                     if (appData.debugMode)
4007                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4008                                                         parse, currentMove);
4009                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4010                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4011                         if (gameInfo.variant == VariantNormal) {
4012                           /* [HGM] We seem to switch variant during a game!
4013                            * Presumably no holdings were displayed, so we have
4014                            * to move the position two files to the right to
4015                            * create room for them!
4016                            */
4017                           VariantClass newVariant;
4018                           switch(gameInfo.boardWidth) { // base guess on board width
4019                                 case 9:  newVariant = VariantShogi; break;
4020                                 case 10: newVariant = VariantGreat; break;
4021                                 default: newVariant = VariantCrazyhouse; break;
4022                           }
4023                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4024                           /* Get a move list just to see the header, which
4025                              will tell us whether this is really bug or zh */
4026                           if (ics_getting_history == H_FALSE) {
4027                             ics_getting_history = H_REQUESTED;
4028                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4029                             SendToICS(str);
4030                           }
4031                         }
4032                         new_piece[0] = NULLCHAR;
4033                         sscanf(parse, "game %d white [%s black [%s <- %s",
4034                                &gamenum, white_holding, black_holding,
4035                                new_piece);
4036                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4037                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4038                         /* [HGM] copy holdings to board holdings area */
4039                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4040                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4041                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4042 #if ZIPPY
4043                         if (appData.zippyPlay && first.initDone) {
4044                             ZippyHoldings(white_holding, black_holding,
4045                                           new_piece);
4046                         }
4047 #endif /*ZIPPY*/
4048                         if (tinyLayout || smallLayout) {
4049                             char wh[16], bh[16];
4050                             PackHolding(wh, white_holding);
4051                             PackHolding(bh, black_holding);
4052                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4053                                     gameInfo.white, gameInfo.black);
4054                         } else {
4055                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4056                                     gameInfo.white, white_holding,
4057                                     gameInfo.black, black_holding);
4058                         }
4059                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4060                         DrawPosition(FALSE, boards[currentMove]);
4061                         DisplayTitle(str);
4062                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4063                         sscanf(parse, "game %d white [%s black [%s <- %s",
4064                                &gamenum, white_holding, black_holding,
4065                                new_piece);
4066                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4067                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4068                         /* [HGM] copy holdings to partner-board holdings area */
4069                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4070                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4071                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4072                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4073                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4074                       }
4075                     }
4076                     /* Suppress following prompt */
4077                     if (looking_at(buf, &i, "*% ")) {
4078                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4079                         savingComment = FALSE;
4080                         suppressKibitz = 0;
4081                     }
4082                     next_out = i;
4083                 }
4084                 continue;
4085             }
4086
4087             i++;                /* skip unparsed character and loop back */
4088         }
4089
4090         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4091 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4092 //          SendToPlayer(&buf[next_out], i - next_out);
4093             started != STARTED_HOLDINGS && leftover_start > next_out) {
4094             SendToPlayer(&buf[next_out], leftover_start - next_out);
4095             next_out = i;
4096         }
4097
4098         leftover_len = buf_len - leftover_start;
4099         /* if buffer ends with something we couldn't parse,
4100            reparse it after appending the next read */
4101
4102     } else if (count == 0) {
4103         RemoveInputSource(isr);
4104         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4105     } else {
4106         DisplayFatalError(_("Error reading from ICS"), error, 1);
4107     }
4108 }
4109
4110
4111 /* Board style 12 looks like this:
4112
4113    <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
4114
4115  * The "<12> " is stripped before it gets to this routine.  The two
4116  * trailing 0's (flip state and clock ticking) are later addition, and
4117  * some chess servers may not have them, or may have only the first.
4118  * Additional trailing fields may be added in the future.
4119  */
4120
4121 #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"
4122
4123 #define RELATION_OBSERVING_PLAYED    0
4124 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4125 #define RELATION_PLAYING_MYMOVE      1
4126 #define RELATION_PLAYING_NOTMYMOVE  -1
4127 #define RELATION_EXAMINING           2
4128 #define RELATION_ISOLATED_BOARD     -3
4129 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4130
4131 void
4132 ParseBoard12(string)
4133      char *string;
4134 {
4135     GameMode newGameMode;
4136     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4137     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4138     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4139     char to_play, board_chars[200];
4140     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4141     char black[32], white[32];
4142     Board board;
4143     int prevMove = currentMove;
4144     int ticking = 2;
4145     ChessMove moveType;
4146     int fromX, fromY, toX, toY;
4147     char promoChar;
4148     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4149     char *bookHit = NULL; // [HGM] book
4150     Boolean weird = FALSE, reqFlag = FALSE;
4151
4152     fromX = fromY = toX = toY = -1;
4153
4154     newGame = FALSE;
4155
4156     if (appData.debugMode)
4157       fprintf(debugFP, _("Parsing board: %s\n"), string);
4158
4159     move_str[0] = NULLCHAR;
4160     elapsed_time[0] = NULLCHAR;
4161     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4162         int  i = 0, j;
4163         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4164             if(string[i] == ' ') { ranks++; files = 0; }
4165             else files++;
4166             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4167             i++;
4168         }
4169         for(j = 0; j <i; j++) board_chars[j] = string[j];
4170         board_chars[i] = '\0';
4171         string += i + 1;
4172     }
4173     n = sscanf(string, PATTERN, &to_play, &double_push,
4174                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4175                &gamenum, white, black, &relation, &basetime, &increment,
4176                &white_stren, &black_stren, &white_time, &black_time,
4177                &moveNum, str, elapsed_time, move_str, &ics_flip,
4178                &ticking);
4179
4180     if (n < 21) {
4181         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4182         DisplayError(str, 0);
4183         return;
4184     }
4185
4186     /* Convert the move number to internal form */
4187     moveNum = (moveNum - 1) * 2;
4188     if (to_play == 'B') moveNum++;
4189     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4190       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4191                         0, 1);
4192       return;
4193     }
4194
4195     switch (relation) {
4196       case RELATION_OBSERVING_PLAYED:
4197       case RELATION_OBSERVING_STATIC:
4198         if (gamenum == -1) {
4199             /* Old ICC buglet */
4200             relation = RELATION_OBSERVING_STATIC;
4201         }
4202         newGameMode = IcsObserving;
4203         break;
4204       case RELATION_PLAYING_MYMOVE:
4205       case RELATION_PLAYING_NOTMYMOVE:
4206         newGameMode =
4207           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4208             IcsPlayingWhite : IcsPlayingBlack;
4209         break;
4210       case RELATION_EXAMINING:
4211         newGameMode = IcsExamining;
4212         break;
4213       case RELATION_ISOLATED_BOARD:
4214       default:
4215         /* Just display this board.  If user was doing something else,
4216            we will forget about it until the next board comes. */
4217         newGameMode = IcsIdle;
4218         break;
4219       case RELATION_STARTING_POSITION:
4220         newGameMode = gameMode;
4221         break;
4222     }
4223
4224     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4225          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4226       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4227       char *toSqr;
4228       for (k = 0; k < ranks; k++) {
4229         for (j = 0; j < files; j++)
4230           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4231         if(gameInfo.holdingsWidth > 1) {
4232              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4233              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4234         }
4235       }
4236       CopyBoard(partnerBoard, board);
4237       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4238         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4239         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4240       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4241       if(toSqr = strchr(str, '-')) {
4242         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4243         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4244       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4245       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4246       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4247       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4248       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4249       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4250                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4251       DisplayMessage(partnerStatus, "");
4252         partnerBoardValid = TRUE;
4253       return;
4254     }
4255
4256     /* Modify behavior for initial board display on move listing
4257        of wild games.
4258        */
4259     switch (ics_getting_history) {
4260       case H_FALSE:
4261       case H_REQUESTED:
4262         break;
4263       case H_GOT_REQ_HEADER:
4264       case H_GOT_UNREQ_HEADER:
4265         /* This is the initial position of the current game */
4266         gamenum = ics_gamenum;
4267         moveNum = 0;            /* old ICS bug workaround */
4268         if (to_play == 'B') {
4269           startedFromSetupPosition = TRUE;
4270           blackPlaysFirst = TRUE;
4271           moveNum = 1;
4272           if (forwardMostMove == 0) forwardMostMove = 1;
4273           if (backwardMostMove == 0) backwardMostMove = 1;
4274           if (currentMove == 0) currentMove = 1;
4275         }
4276         newGameMode = gameMode;
4277         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4278         break;
4279       case H_GOT_UNWANTED_HEADER:
4280         /* This is an initial board that we don't want */
4281         return;
4282       case H_GETTING_MOVES:
4283         /* Should not happen */
4284         DisplayError(_("Error gathering move list: extra board"), 0);
4285         ics_getting_history = H_FALSE;
4286         return;
4287     }
4288
4289    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4290                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4291      /* [HGM] We seem to have switched variant unexpectedly
4292       * Try to guess new variant from board size
4293       */
4294           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4295           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4296           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4297           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4298           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4299           if(!weird) newVariant = VariantNormal;
4300           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4301           /* Get a move list just to see the header, which
4302              will tell us whether this is really bug or zh */
4303           if (ics_getting_history == H_FALSE) {
4304             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4305             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4306             SendToICS(str);
4307           }
4308     }
4309
4310     /* Take action if this is the first board of a new game, or of a
4311        different game than is currently being displayed.  */
4312     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4313         relation == RELATION_ISOLATED_BOARD) {
4314
4315         /* Forget the old game and get the history (if any) of the new one */
4316         if (gameMode != BeginningOfGame) {
4317           Reset(TRUE, TRUE);
4318         }
4319         newGame = TRUE;
4320         if (appData.autoRaiseBoard) BoardToTop();
4321         prevMove = -3;
4322         if (gamenum == -1) {
4323             newGameMode = IcsIdle;
4324         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4325                    appData.getMoveList && !reqFlag) {
4326             /* Need to get game history */
4327             ics_getting_history = H_REQUESTED;
4328             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4329             SendToICS(str);
4330         }
4331
4332         /* Initially flip the board to have black on the bottom if playing
4333            black or if the ICS flip flag is set, but let the user change
4334            it with the Flip View button. */
4335         flipView = appData.autoFlipView ?
4336           (newGameMode == IcsPlayingBlack) || ics_flip :
4337           appData.flipView;
4338
4339         /* Done with values from previous mode; copy in new ones */
4340         gameMode = newGameMode;
4341         ModeHighlight();
4342         ics_gamenum = gamenum;
4343         if (gamenum == gs_gamenum) {
4344             int klen = strlen(gs_kind);
4345             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4346             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4347             gameInfo.event = StrSave(str);
4348         } else {
4349             gameInfo.event = StrSave("ICS game");
4350         }
4351         gameInfo.site = StrSave(appData.icsHost);
4352         gameInfo.date = PGNDate();
4353         gameInfo.round = StrSave("-");
4354         gameInfo.white = StrSave(white);
4355         gameInfo.black = StrSave(black);
4356         timeControl = basetime * 60 * 1000;
4357         timeControl_2 = 0;
4358         timeIncrement = increment * 1000;
4359         movesPerSession = 0;
4360         gameInfo.timeControl = TimeControlTagValue();
4361         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4362   if (appData.debugMode) {
4363     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4364     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4365     setbuf(debugFP, NULL);
4366   }
4367
4368         gameInfo.outOfBook = NULL;
4369
4370         /* Do we have the ratings? */
4371         if (strcmp(player1Name, white) == 0 &&
4372             strcmp(player2Name, black) == 0) {
4373             if (appData.debugMode)
4374               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4375                       player1Rating, player2Rating);
4376             gameInfo.whiteRating = player1Rating;
4377             gameInfo.blackRating = player2Rating;
4378         } else if (strcmp(player2Name, white) == 0 &&
4379                    strcmp(player1Name, black) == 0) {
4380             if (appData.debugMode)
4381               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4382                       player2Rating, player1Rating);
4383             gameInfo.whiteRating = player2Rating;
4384             gameInfo.blackRating = player1Rating;
4385         }
4386         player1Name[0] = player2Name[0] = NULLCHAR;
4387
4388         /* Silence shouts if requested */
4389         if (appData.quietPlay &&
4390             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4391             SendToICS(ics_prefix);
4392             SendToICS("set shout 0\n");
4393         }
4394     }
4395
4396     /* Deal with midgame name changes */
4397     if (!newGame) {
4398         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4399             if (gameInfo.white) free(gameInfo.white);
4400             gameInfo.white = StrSave(white);
4401         }
4402         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4403             if (gameInfo.black) free(gameInfo.black);
4404             gameInfo.black = StrSave(black);
4405         }
4406     }
4407
4408     /* Throw away game result if anything actually changes in examine mode */
4409     if (gameMode == IcsExamining && !newGame) {
4410         gameInfo.result = GameUnfinished;
4411         if (gameInfo.resultDetails != NULL) {
4412             free(gameInfo.resultDetails);
4413             gameInfo.resultDetails = NULL;
4414         }
4415     }
4416
4417     /* In pausing && IcsExamining mode, we ignore boards coming
4418        in if they are in a different variation than we are. */
4419     if (pauseExamInvalid) return;
4420     if (pausing && gameMode == IcsExamining) {
4421         if (moveNum <= pauseExamForwardMostMove) {
4422             pauseExamInvalid = TRUE;
4423             forwardMostMove = pauseExamForwardMostMove;
4424             return;
4425         }
4426     }
4427
4428   if (appData.debugMode) {
4429     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4430   }
4431     /* Parse the board */
4432     for (k = 0; k < ranks; k++) {
4433       for (j = 0; j < files; j++)
4434         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4435       if(gameInfo.holdingsWidth > 1) {
4436            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4437            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4438       }
4439     }
4440     CopyBoard(boards[moveNum], board);
4441     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4442     if (moveNum == 0) {
4443         startedFromSetupPosition =
4444           !CompareBoards(board, initialPosition);
4445         if(startedFromSetupPosition)
4446             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4447     }
4448
4449     /* [HGM] Set castling rights. Take the outermost Rooks,
4450        to make it also work for FRC opening positions. Note that board12
4451        is really defective for later FRC positions, as it has no way to
4452        indicate which Rook can castle if they are on the same side of King.
4453        For the initial position we grant rights to the outermost Rooks,
4454        and remember thos rights, and we then copy them on positions
4455        later in an FRC game. This means WB might not recognize castlings with
4456        Rooks that have moved back to their original position as illegal,
4457        but in ICS mode that is not its job anyway.
4458     */
4459     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4460     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4461
4462         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4463             if(board[0][i] == WhiteRook) j = i;
4464         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4465         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4466             if(board[0][i] == WhiteRook) j = i;
4467         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4468         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4469             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4470         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4471         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4472             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4473         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4474
4475         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4476         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4477             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4478         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4479             if(board[BOARD_HEIGHT-1][k] == bKing)
4480                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4481         if(gameInfo.variant == VariantTwoKings) {
4482             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4483             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4484             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4485         }
4486     } else { int r;
4487         r = boards[moveNum][CASTLING][0] = initialRights[0];
4488         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4489         r = boards[moveNum][CASTLING][1] = initialRights[1];
4490         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4491         r = boards[moveNum][CASTLING][3] = initialRights[3];
4492         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4493         r = boards[moveNum][CASTLING][4] = initialRights[4];
4494         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4495         /* wildcastle kludge: always assume King has rights */
4496         r = boards[moveNum][CASTLING][2] = initialRights[2];
4497         r = boards[moveNum][CASTLING][5] = initialRights[5];
4498     }
4499     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4500     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4501
4502
4503     if (ics_getting_history == H_GOT_REQ_HEADER ||
4504         ics_getting_history == H_GOT_UNREQ_HEADER) {
4505         /* This was an initial position from a move list, not
4506            the current position */
4507         return;
4508     }
4509
4510     /* Update currentMove and known move number limits */
4511     newMove = newGame || moveNum > forwardMostMove;
4512
4513     if (newGame) {
4514         forwardMostMove = backwardMostMove = currentMove = moveNum;
4515         if (gameMode == IcsExamining && moveNum == 0) {
4516           /* Workaround for ICS limitation: we are not told the wild
4517              type when starting to examine a game.  But if we ask for
4518              the move list, the move list header will tell us */
4519             ics_getting_history = H_REQUESTED;
4520             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4521             SendToICS(str);
4522         }
4523     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4524                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4525 #if ZIPPY
4526         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4527         /* [HGM] applied this also to an engine that is silently watching        */
4528         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4529             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4530             gameInfo.variant == currentlyInitializedVariant) {
4531           takeback = forwardMostMove - moveNum;
4532           for (i = 0; i < takeback; i++) {
4533             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4534             SendToProgram("undo\n", &first);
4535           }
4536         }
4537 #endif
4538
4539         forwardMostMove = moveNum;
4540         if (!pausing || currentMove > forwardMostMove)
4541           currentMove = forwardMostMove;
4542     } else {
4543         /* New part of history that is not contiguous with old part */
4544         if (pausing && gameMode == IcsExamining) {
4545             pauseExamInvalid = TRUE;
4546             forwardMostMove = pauseExamForwardMostMove;
4547             return;
4548         }
4549         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4550 #if ZIPPY
4551             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4552                 // [HGM] when we will receive the move list we now request, it will be
4553                 // fed to the engine from the first move on. So if the engine is not
4554                 // in the initial position now, bring it there.
4555                 InitChessProgram(&first, 0);
4556             }
4557 #endif
4558             ics_getting_history = H_REQUESTED;
4559             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4560             SendToICS(str);
4561         }
4562         forwardMostMove = backwardMostMove = currentMove = moveNum;
4563     }
4564
4565     /* Update the clocks */
4566     if (strchr(elapsed_time, '.')) {
4567       /* Time is in ms */
4568       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4569       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4570     } else {
4571       /* Time is in seconds */
4572       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4573       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4574     }
4575
4576
4577 #if ZIPPY
4578     if (appData.zippyPlay && newGame &&
4579         gameMode != IcsObserving && gameMode != IcsIdle &&
4580         gameMode != IcsExamining)
4581       ZippyFirstBoard(moveNum, basetime, increment);
4582 #endif
4583
4584     /* Put the move on the move list, first converting
4585        to canonical algebraic form. */
4586     if (moveNum > 0) {
4587   if (appData.debugMode) {
4588     if (appData.debugMode) { int f = forwardMostMove;
4589         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4590                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4591                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4592     }
4593     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4594     fprintf(debugFP, "moveNum = %d\n", moveNum);
4595     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4596     setbuf(debugFP, NULL);
4597   }
4598         if (moveNum <= backwardMostMove) {
4599             /* We don't know what the board looked like before
4600                this move.  Punt. */
4601           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4602             strcat(parseList[moveNum - 1], " ");
4603             strcat(parseList[moveNum - 1], elapsed_time);
4604             moveList[moveNum - 1][0] = NULLCHAR;
4605         } else if (strcmp(move_str, "none") == 0) {
4606             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4607             /* Again, we don't know what the board looked like;
4608                this is really the start of the game. */
4609             parseList[moveNum - 1][0] = NULLCHAR;
4610             moveList[moveNum - 1][0] = NULLCHAR;
4611             backwardMostMove = moveNum;
4612             startedFromSetupPosition = TRUE;
4613             fromX = fromY = toX = toY = -1;
4614         } else {
4615           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4616           //                 So we parse the long-algebraic move string in stead of the SAN move
4617           int valid; char buf[MSG_SIZ], *prom;
4618
4619           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4620                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4621           // str looks something like "Q/a1-a2"; kill the slash
4622           if(str[1] == '/')
4623             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4624           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4625           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4626                 strcat(buf, prom); // long move lacks promo specification!
4627           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4628                 if(appData.debugMode)
4629                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4630                 safeStrCpy(move_str, buf, MSG_SIZ);
4631           }
4632           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4633                                 &fromX, &fromY, &toX, &toY, &promoChar)
4634                || ParseOneMove(buf, moveNum - 1, &moveType,
4635                                 &fromX, &fromY, &toX, &toY, &promoChar);
4636           // end of long SAN patch
4637           if (valid) {
4638             (void) CoordsToAlgebraic(boards[moveNum - 1],
4639                                      PosFlags(moveNum - 1),
4640                                      fromY, fromX, toY, toX, promoChar,
4641                                      parseList[moveNum-1]);
4642             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4643               case MT_NONE:
4644               case MT_STALEMATE:
4645               default:
4646                 break;
4647               case MT_CHECK:
4648                 if(gameInfo.variant != VariantShogi)
4649                     strcat(parseList[moveNum - 1], "+");
4650                 break;
4651               case MT_CHECKMATE:
4652               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4653                 strcat(parseList[moveNum - 1], "#");
4654                 break;
4655             }
4656             strcat(parseList[moveNum - 1], " ");
4657             strcat(parseList[moveNum - 1], elapsed_time);
4658             /* currentMoveString is set as a side-effect of ParseOneMove */
4659             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4660             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4661             strcat(moveList[moveNum - 1], "\n");
4662
4663             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4664                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4665               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4666                 ChessSquare old, new = boards[moveNum][k][j];
4667                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4668                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4669                   if(old == new) continue;
4670                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4671                   else if(new == WhiteWazir || new == BlackWazir) {
4672                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4673                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4674                       else boards[moveNum][k][j] = old; // preserve type of Gold
4675                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4676                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4677               }
4678           } else {
4679             /* Move from ICS was illegal!?  Punt. */
4680             if (appData.debugMode) {
4681               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4682               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4683             }
4684             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4685             strcat(parseList[moveNum - 1], " ");
4686             strcat(parseList[moveNum - 1], elapsed_time);
4687             moveList[moveNum - 1][0] = NULLCHAR;
4688             fromX = fromY = toX = toY = -1;
4689           }
4690         }
4691   if (appData.debugMode) {
4692     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4693     setbuf(debugFP, NULL);
4694   }
4695
4696 #if ZIPPY
4697         /* Send move to chess program (BEFORE animating it). */
4698         if (appData.zippyPlay && !newGame && newMove &&
4699            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4700
4701             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4702                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4703                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4704                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4705                             move_str);
4706                     DisplayError(str, 0);
4707                 } else {
4708                     if (first.sendTime) {
4709                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4710                     }
4711                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4712                     if (firstMove && !bookHit) {
4713                         firstMove = FALSE;
4714                         if (first.useColors) {
4715                           SendToProgram(gameMode == IcsPlayingWhite ?
4716                                         "white\ngo\n" :
4717                                         "black\ngo\n", &first);
4718                         } else {
4719                           SendToProgram("go\n", &first);
4720                         }
4721                         first.maybeThinking = TRUE;
4722                     }
4723                 }
4724             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4725               if (moveList[moveNum - 1][0] == NULLCHAR) {
4726                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4727                 DisplayError(str, 0);
4728               } else {
4729                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4730                 SendMoveToProgram(moveNum - 1, &first);
4731               }
4732             }
4733         }
4734 #endif
4735     }
4736
4737     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4738         /* If move comes from a remote source, animate it.  If it
4739            isn't remote, it will have already been animated. */
4740         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4741             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4742         }
4743         if (!pausing && appData.highlightLastMove) {
4744             SetHighlights(fromX, fromY, toX, toY);
4745         }
4746     }
4747
4748     /* Start the clocks */
4749     whiteFlag = blackFlag = FALSE;
4750     appData.clockMode = !(basetime == 0 && increment == 0);
4751     if (ticking == 0) {
4752       ics_clock_paused = TRUE;
4753       StopClocks();
4754     } else if (ticking == 1) {
4755       ics_clock_paused = FALSE;
4756     }
4757     if (gameMode == IcsIdle ||
4758         relation == RELATION_OBSERVING_STATIC ||
4759         relation == RELATION_EXAMINING ||
4760         ics_clock_paused)
4761       DisplayBothClocks();
4762     else
4763       StartClocks();
4764
4765     /* Display opponents and material strengths */
4766     if (gameInfo.variant != VariantBughouse &&
4767         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4768         if (tinyLayout || smallLayout) {
4769             if(gameInfo.variant == VariantNormal)
4770               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4771                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4772                     basetime, increment);
4773             else
4774               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4775                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4776                     basetime, increment, (int) gameInfo.variant);
4777         } else {
4778             if(gameInfo.variant == VariantNormal)
4779               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4780                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4781                     basetime, increment);
4782             else
4783               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4784                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4785                     basetime, increment, VariantName(gameInfo.variant));
4786         }
4787         DisplayTitle(str);
4788   if (appData.debugMode) {
4789     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4790   }
4791     }
4792
4793
4794     /* Display the board */
4795     if (!pausing && !appData.noGUI) {
4796
4797       if (appData.premove)
4798           if (!gotPremove ||
4799              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4800              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4801               ClearPremoveHighlights();
4802
4803       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4804         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4805       DrawPosition(j, boards[currentMove]);
4806
4807       DisplayMove(moveNum - 1);
4808       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4809             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4810               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4811         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4812       }
4813     }
4814
4815     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4816 #if ZIPPY
4817     if(bookHit) { // [HGM] book: simulate book reply
4818         static char bookMove[MSG_SIZ]; // a bit generous?
4819
4820         programStats.nodes = programStats.depth = programStats.time =
4821         programStats.score = programStats.got_only_move = 0;
4822         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4823
4824         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4825         strcat(bookMove, bookHit);
4826         HandleMachineMove(bookMove, &first);
4827     }
4828 #endif
4829 }
4830
4831 void
4832 GetMoveListEvent()
4833 {
4834     char buf[MSG_SIZ];
4835     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4836         ics_getting_history = H_REQUESTED;
4837         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4838         SendToICS(buf);
4839     }
4840 }
4841
4842 void
4843 AnalysisPeriodicEvent(force)
4844      int force;
4845 {
4846     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4847          && !force) || !appData.periodicUpdates)
4848       return;
4849
4850     /* Send . command to Crafty to collect stats */
4851     SendToProgram(".\n", &first);
4852
4853     /* Don't send another until we get a response (this makes
4854        us stop sending to old Crafty's which don't understand
4855        the "." command (sending illegal cmds resets node count & time,
4856        which looks bad)) */
4857     programStats.ok_to_send = 0;
4858 }
4859
4860 void ics_update_width(new_width)
4861         int new_width;
4862 {
4863         ics_printf("set width %d\n", new_width);
4864 }
4865
4866 void
4867 SendMoveToProgram(moveNum, cps)
4868      int moveNum;
4869      ChessProgramState *cps;
4870 {
4871     char buf[MSG_SIZ];
4872
4873     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4874         // null move in variant where engine does not understand it (for analysis purposes)
4875         SendBoard(cps, moveNum + 1); // send position after move in stead.
4876         return;
4877     }
4878     if (cps->useUsermove) {
4879       SendToProgram("usermove ", cps);
4880     }
4881     if (cps->useSAN) {
4882       char *space;
4883       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4884         int len = space - parseList[moveNum];
4885         memcpy(buf, parseList[moveNum], len);
4886         buf[len++] = '\n';
4887         buf[len] = NULLCHAR;
4888       } else {
4889         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4890       }
4891       SendToProgram(buf, cps);
4892     } else {
4893       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4894         AlphaRank(moveList[moveNum], 4);
4895         SendToProgram(moveList[moveNum], cps);
4896         AlphaRank(moveList[moveNum], 4); // and back
4897       } else
4898       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4899        * the engine. It would be nice to have a better way to identify castle
4900        * moves here. */
4901       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4902                                                                          && cps->useOOCastle) {
4903         int fromX = moveList[moveNum][0] - AAA;
4904         int fromY = moveList[moveNum][1] - ONE;
4905         int toX = moveList[moveNum][2] - AAA;
4906         int toY = moveList[moveNum][3] - ONE;
4907         if((boards[moveNum][fromY][fromX] == WhiteKing
4908             && boards[moveNum][toY][toX] == WhiteRook)
4909            || (boards[moveNum][fromY][fromX] == BlackKing
4910                && boards[moveNum][toY][toX] == BlackRook)) {
4911           if(toX > fromX) SendToProgram("O-O\n", cps);
4912           else SendToProgram("O-O-O\n", cps);
4913         }
4914         else SendToProgram(moveList[moveNum], cps);
4915       } else
4916       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4917         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4918           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4919           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4920                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4921         } else
4922           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4923                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4924         SendToProgram(buf, cps);
4925       }
4926       else SendToProgram(moveList[moveNum], cps);
4927       /* End of additions by Tord */
4928     }
4929
4930     /* [HGM] setting up the opening has brought engine in force mode! */
4931     /*       Send 'go' if we are in a mode where machine should play. */
4932     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4933         (gameMode == TwoMachinesPlay   ||
4934 #if ZIPPY
4935          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4936 #endif
4937          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4938         SendToProgram("go\n", cps);
4939   if (appData.debugMode) {
4940     fprintf(debugFP, "(extra)\n");
4941   }
4942     }
4943     setboardSpoiledMachineBlack = 0;
4944 }
4945
4946 void
4947 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4948      ChessMove moveType;
4949      int fromX, fromY, toX, toY;
4950      char promoChar;
4951 {
4952     char user_move[MSG_SIZ];
4953
4954     switch (moveType) {
4955       default:
4956         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4957                 (int)moveType, fromX, fromY, toX, toY);
4958         DisplayError(user_move + strlen("say "), 0);
4959         break;
4960       case WhiteKingSideCastle:
4961       case BlackKingSideCastle:
4962       case WhiteQueenSideCastleWild:
4963       case BlackQueenSideCastleWild:
4964       /* PUSH Fabien */
4965       case WhiteHSideCastleFR:
4966       case BlackHSideCastleFR:
4967       /* POP Fabien */
4968         snprintf(user_move, MSG_SIZ, "o-o\n");
4969         break;
4970       case WhiteQueenSideCastle:
4971       case BlackQueenSideCastle:
4972       case WhiteKingSideCastleWild:
4973       case BlackKingSideCastleWild:
4974       /* PUSH Fabien */
4975       case WhiteASideCastleFR:
4976       case BlackASideCastleFR:
4977       /* POP Fabien */
4978         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4979         break;
4980       case WhiteNonPromotion:
4981       case BlackNonPromotion:
4982         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4983         break;
4984       case WhitePromotion:
4985       case BlackPromotion:
4986         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4987           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4988                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4989                 PieceToChar(WhiteFerz));
4990         else if(gameInfo.variant == VariantGreat)
4991           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4992                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4993                 PieceToChar(WhiteMan));
4994         else
4995           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4996                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4997                 promoChar);
4998         break;
4999       case WhiteDrop:
5000       case BlackDrop:
5001       drop:
5002         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5003                  ToUpper(PieceToChar((ChessSquare) fromX)),
5004                  AAA + toX, ONE + toY);
5005         break;
5006       case IllegalMove:  /* could be a variant we don't quite understand */
5007         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5008       case NormalMove:
5009       case WhiteCapturesEnPassant:
5010       case BlackCapturesEnPassant:
5011         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5012                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5013         break;
5014     }
5015     SendToICS(user_move);
5016     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5017         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5018 }
5019
5020 void
5021 UploadGameEvent()
5022 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5023     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5024     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5025     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5026         DisplayError("You cannot do this while you are playing or observing", 0);
5027         return;
5028     }
5029     if(gameMode != IcsExamining) { // is this ever not the case?
5030         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5031
5032         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5033           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5034         } else { // on FICS we must first go to general examine mode
5035           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5036         }
5037         if(gameInfo.variant != VariantNormal) {
5038             // try figure out wild number, as xboard names are not always valid on ICS
5039             for(i=1; i<=36; i++) {
5040               snprintf(buf, MSG_SIZ, "wild/%d", i);
5041                 if(StringToVariant(buf) == gameInfo.variant) break;
5042             }
5043             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5044             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5045             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5046         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5047         SendToICS(ics_prefix);
5048         SendToICS(buf);
5049         if(startedFromSetupPosition || backwardMostMove != 0) {
5050           fen = PositionToFEN(backwardMostMove, NULL);
5051           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5052             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5053             SendToICS(buf);
5054           } else { // FICS: everything has to set by separate bsetup commands
5055             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5056             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5057             SendToICS(buf);
5058             if(!WhiteOnMove(backwardMostMove)) {
5059                 SendToICS("bsetup tomove black\n");
5060             }
5061             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5062             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5063             SendToICS(buf);
5064             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5065             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5066             SendToICS(buf);
5067             i = boards[backwardMostMove][EP_STATUS];
5068             if(i >= 0) { // set e.p.
5069               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5070                 SendToICS(buf);
5071             }
5072             bsetup++;
5073           }
5074         }
5075       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5076             SendToICS("bsetup done\n"); // switch to normal examining.
5077     }
5078     for(i = backwardMostMove; i<last; i++) {
5079         char buf[20];
5080         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5081         SendToICS(buf);
5082     }
5083     SendToICS(ics_prefix);
5084     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5085 }
5086
5087 void
5088 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5089      int rf, ff, rt, ft;
5090      char promoChar;
5091      char move[7];
5092 {
5093     if (rf == DROP_RANK) {
5094       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5095       sprintf(move, "%c@%c%c\n",
5096                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5097     } else {
5098         if (promoChar == 'x' || promoChar == NULLCHAR) {
5099           sprintf(move, "%c%c%c%c\n",
5100                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5101         } else {
5102             sprintf(move, "%c%c%c%c%c\n",
5103                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5104         }
5105     }
5106 }
5107
5108 void
5109 ProcessICSInitScript(f)
5110      FILE *f;
5111 {
5112     char buf[MSG_SIZ];
5113
5114     while (fgets(buf, MSG_SIZ, f)) {
5115         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5116     }
5117
5118     fclose(f);
5119 }
5120
5121
5122 static int lastX, lastY, selectFlag, dragging;
5123
5124 void
5125 Sweep(int step)
5126 {
5127     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5128     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5129     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5130     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5131     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5132     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5133     do {
5134         promoSweep -= step;
5135         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5136         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5137         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5138         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5139         if(!step) step = -1;
5140     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5141             appData.testLegality && (promoSweep == king ||
5142             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5143     ChangeDragPiece(promoSweep);
5144 }
5145
5146 int PromoScroll(int x, int y)
5147 {
5148   int step = 0;
5149
5150   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5151   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5152   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5153   if(!step) return FALSE;
5154   lastX = x; lastY = y;
5155   if((promoSweep < BlackPawn) == flipView) step = -step;
5156   if(step > 0) selectFlag = 1;
5157   if(!selectFlag) Sweep(step);
5158   return FALSE;
5159 }
5160
5161 void
5162 NextPiece(int step)
5163 {
5164     ChessSquare piece = boards[currentMove][toY][toX];
5165     do {
5166         pieceSweep -= step;
5167         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5168         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5169         if(!step) step = -1;
5170     } while(PieceToChar(pieceSweep) == '.');
5171     boards[currentMove][toY][toX] = pieceSweep;
5172     DrawPosition(FALSE, boards[currentMove]);
5173     boards[currentMove][toY][toX] = piece;
5174 }
5175 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5176 void
5177 AlphaRank(char *move, int n)
5178 {
5179 //    char *p = move, c; int x, y;
5180
5181     if (appData.debugMode) {
5182         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5183     }
5184
5185     if(move[1]=='*' &&
5186        move[2]>='0' && move[2]<='9' &&
5187        move[3]>='a' && move[3]<='x'    ) {
5188         move[1] = '@';
5189         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5190         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5191     } else
5192     if(move[0]>='0' && move[0]<='9' &&
5193        move[1]>='a' && move[1]<='x' &&
5194        move[2]>='0' && move[2]<='9' &&
5195        move[3]>='a' && move[3]<='x'    ) {
5196         /* input move, Shogi -> normal */
5197         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5198         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5199         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5200         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5201     } else
5202     if(move[1]=='@' &&
5203        move[3]>='0' && move[3]<='9' &&
5204        move[2]>='a' && move[2]<='x'    ) {
5205         move[1] = '*';
5206         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5207         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5208     } else
5209     if(
5210        move[0]>='a' && move[0]<='x' &&
5211        move[3]>='0' && move[3]<='9' &&
5212        move[2]>='a' && move[2]<='x'    ) {
5213          /* output move, normal -> Shogi */
5214         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5215         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5216         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5217         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5218         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5219     }
5220     if (appData.debugMode) {
5221         fprintf(debugFP, "   out = '%s'\n", move);
5222     }
5223 }
5224
5225 char yy_textstr[8000];
5226
5227 /* Parser for moves from gnuchess, ICS, or user typein box */
5228 Boolean
5229 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5230      char *move;
5231      int moveNum;
5232      ChessMove *moveType;
5233      int *fromX, *fromY, *toX, *toY;
5234      char *promoChar;
5235 {
5236     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5237
5238     switch (*moveType) {
5239       case WhitePromotion:
5240       case BlackPromotion:
5241       case WhiteNonPromotion:
5242       case BlackNonPromotion:
5243       case NormalMove:
5244       case WhiteCapturesEnPassant:
5245       case BlackCapturesEnPassant:
5246       case WhiteKingSideCastle:
5247       case WhiteQueenSideCastle:
5248       case BlackKingSideCastle:
5249       case BlackQueenSideCastle:
5250       case WhiteKingSideCastleWild:
5251       case WhiteQueenSideCastleWild:
5252       case BlackKingSideCastleWild:
5253       case BlackQueenSideCastleWild:
5254       /* Code added by Tord: */
5255       case WhiteHSideCastleFR:
5256       case WhiteASideCastleFR:
5257       case BlackHSideCastleFR:
5258       case BlackASideCastleFR:
5259       /* End of code added by Tord */
5260       case IllegalMove:         /* bug or odd chess variant */
5261         *fromX = currentMoveString[0] - AAA;
5262         *fromY = currentMoveString[1] - ONE;
5263         *toX = currentMoveString[2] - AAA;
5264         *toY = currentMoveString[3] - ONE;
5265         *promoChar = currentMoveString[4];
5266         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5267             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5268     if (appData.debugMode) {
5269         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5270     }
5271             *fromX = *fromY = *toX = *toY = 0;
5272             return FALSE;
5273         }
5274         if (appData.testLegality) {
5275           return (*moveType != IllegalMove);
5276         } else {
5277           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5278                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5279         }
5280
5281       case WhiteDrop:
5282       case BlackDrop:
5283         *fromX = *moveType == WhiteDrop ?
5284           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5285           (int) CharToPiece(ToLower(currentMoveString[0]));
5286         *fromY = DROP_RANK;
5287         *toX = currentMoveString[2] - AAA;
5288         *toY = currentMoveString[3] - ONE;
5289         *promoChar = NULLCHAR;
5290         return TRUE;
5291
5292       case AmbiguousMove:
5293       case ImpossibleMove:
5294       case EndOfFile:
5295       case ElapsedTime:
5296       case Comment:
5297       case PGNTag:
5298       case NAG:
5299       case WhiteWins:
5300       case BlackWins:
5301       case GameIsDrawn:
5302       default:
5303     if (appData.debugMode) {
5304         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5305     }
5306         /* bug? */
5307         *fromX = *fromY = *toX = *toY = 0;
5308         *promoChar = NULLCHAR;
5309         return FALSE;
5310     }
5311 }
5312
5313 Boolean pushed = FALSE;
5314 char *lastParseAttempt;
5315
5316 void
5317 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5318 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5319   int fromX, fromY, toX, toY; char promoChar;
5320   ChessMove moveType;
5321   Boolean valid;
5322   int nr = 0;
5323
5324   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5325     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5326     pushed = TRUE;
5327   }
5328   endPV = forwardMostMove;
5329   do {
5330     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5331     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5332     lastParseAttempt = pv;
5333     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5334 if(appData.debugMode){
5335 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);
5336 }
5337     if(!valid && nr == 0 &&
5338        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5339         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5340         // Hande case where played move is different from leading PV move
5341         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5342         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5343         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5344         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5345           endPV += 2; // if position different, keep this
5346           moveList[endPV-1][0] = fromX + AAA;
5347           moveList[endPV-1][1] = fromY + ONE;
5348           moveList[endPV-1][2] = toX + AAA;
5349           moveList[endPV-1][3] = toY + ONE;
5350           parseList[endPV-1][0] = NULLCHAR;
5351           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5352         }
5353       }
5354     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5355     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5356     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5357     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5358         valid++; // allow comments in PV
5359         continue;
5360     }
5361     nr++;
5362     if(endPV+1 > framePtr) break; // no space, truncate
5363     if(!valid) break;
5364     endPV++;
5365     CopyBoard(boards[endPV], boards[endPV-1]);
5366     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5367     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5368     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5369     CoordsToAlgebraic(boards[endPV - 1],
5370                              PosFlags(endPV - 1),
5371                              fromY, fromX, toY, toX, promoChar,
5372                              parseList[endPV - 1]);
5373   } while(valid);
5374   if(atEnd == 2) return; // used hidden, for PV conversion
5375   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5376   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5377   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5378                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5379   DrawPosition(TRUE, boards[currentMove]);
5380 }
5381
5382 int
5383 MultiPV(ChessProgramState *cps)
5384 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5385         int i;
5386         for(i=0; i<cps->nrOptions; i++)
5387             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5388                 return i;
5389         return -1;
5390 }
5391
5392 Boolean
5393 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5394 {
5395         int startPV, multi, lineStart, origIndex = index;
5396         char *p, buf2[MSG_SIZ];
5397
5398         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5399         lastX = x; lastY = y;
5400         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5401         lineStart = startPV = index;
5402         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5403         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5404         index = startPV;
5405         do{ while(buf[index] && buf[index] != '\n') index++;
5406         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5407         buf[index] = 0;
5408         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5409                 int n = first.option[multi].value;
5410                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5411                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5412                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5413                 first.option[multi].value = n;
5414                 *start = *end = 0;
5415                 return FALSE;
5416         }
5417         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5418         *start = startPV; *end = index-1;
5419         return TRUE;
5420 }
5421
5422 char *
5423 PvToSAN(char *pv)
5424 {
5425         static char buf[10*MSG_SIZ];
5426         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5427         *buf = NULLCHAR;
5428         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5429         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5430         for(i = forwardMostMove; i<endPV; i++){
5431             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5432             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5433             k += strlen(buf+k);
5434         }
5435         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5436         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5437         endPV = savedEnd;
5438         return buf;
5439 }
5440
5441 Boolean
5442 LoadPV(int x, int y)
5443 { // called on right mouse click to load PV
5444   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5445   lastX = x; lastY = y;
5446   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5447   return TRUE;
5448 }
5449
5450 void
5451 UnLoadPV()
5452 {
5453   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5454   if(endPV < 0) return;
5455   endPV = -1;
5456   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5457         Boolean saveAnimate = appData.animate;
5458         if(pushed) {
5459             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5460                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5461             } else storedGames--; // abandon shelved tail of original game
5462         }
5463         pushed = FALSE;
5464         forwardMostMove = currentMove;
5465         currentMove = oldFMM;
5466         appData.animate = FALSE;
5467         ToNrEvent(forwardMostMove);
5468         appData.animate = saveAnimate;
5469   }
5470   currentMove = forwardMostMove;
5471   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5472   ClearPremoveHighlights();
5473   DrawPosition(TRUE, boards[currentMove]);
5474 }
5475
5476 void
5477 MovePV(int x, int y, int h)
5478 { // step through PV based on mouse coordinates (called on mouse move)
5479   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5480
5481   // we must somehow check if right button is still down (might be released off board!)
5482   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5483   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5484   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5485   if(!step) return;
5486   lastX = x; lastY = y;
5487
5488   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5489   if(endPV < 0) return;
5490   if(y < margin) step = 1; else
5491   if(y > h - margin) step = -1;
5492   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5493   currentMove += step;
5494   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5495   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5496                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5497   DrawPosition(FALSE, boards[currentMove]);
5498 }
5499
5500
5501 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5502 // All positions will have equal probability, but the current method will not provide a unique
5503 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5504 #define DARK 1
5505 #define LITE 2
5506 #define ANY 3
5507
5508 int squaresLeft[4];
5509 int piecesLeft[(int)BlackPawn];
5510 int seed, nrOfShuffles;
5511
5512 void GetPositionNumber()
5513 {       // sets global variable seed
5514         int i;
5515
5516         seed = appData.defaultFrcPosition;
5517         if(seed < 0) { // randomize based on time for negative FRC position numbers
5518                 for(i=0; i<50; i++) seed += random();
5519                 seed = random() ^ random() >> 8 ^ random() << 8;
5520                 if(seed<0) seed = -seed;
5521         }
5522 }
5523
5524 int put(Board board, int pieceType, int rank, int n, int shade)
5525 // put the piece on the (n-1)-th empty squares of the given shade
5526 {
5527         int i;
5528
5529         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5530                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5531                         board[rank][i] = (ChessSquare) pieceType;
5532                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5533                         squaresLeft[ANY]--;
5534                         piecesLeft[pieceType]--;
5535                         return i;
5536                 }
5537         }
5538         return -1;
5539 }
5540
5541
5542 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5543 // calculate where the next piece goes, (any empty square), and put it there
5544 {
5545         int i;
5546
5547         i = seed % squaresLeft[shade];
5548         nrOfShuffles *= squaresLeft[shade];
5549         seed /= squaresLeft[shade];
5550         put(board, pieceType, rank, i, shade);
5551 }
5552
5553 void AddTwoPieces(Board board, int pieceType, int rank)
5554 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5555 {
5556         int i, n=squaresLeft[ANY], j=n-1, k;
5557
5558         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5559         i = seed % k;  // pick one
5560         nrOfShuffles *= k;
5561         seed /= k;
5562         while(i >= j) i -= j--;
5563         j = n - 1 - j; i += j;
5564         put(board, pieceType, rank, j, ANY);
5565         put(board, pieceType, rank, i, ANY);
5566 }
5567
5568 void SetUpShuffle(Board board, int number)
5569 {
5570         int i, p, first=1;
5571
5572         GetPositionNumber(); nrOfShuffles = 1;
5573
5574         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5575         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5576         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5577
5578         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5579
5580         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5581             p = (int) board[0][i];
5582             if(p < (int) BlackPawn) piecesLeft[p] ++;
5583             board[0][i] = EmptySquare;
5584         }
5585
5586         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5587             // shuffles restricted to allow normal castling put KRR first
5588             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5589                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5590             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5591                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5592             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5593                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5594             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5595                 put(board, WhiteRook, 0, 0, ANY);
5596             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5597         }
5598
5599         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5600             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5601             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5602                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5603                 while(piecesLeft[p] >= 2) {
5604                     AddOnePiece(board, p, 0, LITE);
5605                     AddOnePiece(board, p, 0, DARK);
5606                 }
5607                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5608             }
5609
5610         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5611             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5612             // but we leave King and Rooks for last, to possibly obey FRC restriction
5613             if(p == (int)WhiteRook) continue;
5614             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5615             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5616         }
5617
5618         // now everything is placed, except perhaps King (Unicorn) and Rooks
5619
5620         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5621             // Last King gets castling rights
5622             while(piecesLeft[(int)WhiteUnicorn]) {
5623                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5624                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5625             }
5626
5627             while(piecesLeft[(int)WhiteKing]) {
5628                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5629                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5630             }
5631
5632
5633         } else {
5634             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5635             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5636         }
5637
5638         // Only Rooks can be left; simply place them all
5639         while(piecesLeft[(int)WhiteRook]) {
5640                 i = put(board, WhiteRook, 0, 0, ANY);
5641                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5642                         if(first) {
5643                                 first=0;
5644                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5645                         }
5646                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5647                 }
5648         }
5649         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5650             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5651         }
5652
5653         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5654 }
5655
5656 int SetCharTable( char *table, const char * map )
5657 /* [HGM] moved here from winboard.c because of its general usefulness */
5658 /*       Basically a safe strcpy that uses the last character as King */
5659 {
5660     int result = FALSE; int NrPieces;
5661
5662     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5663                     && NrPieces >= 12 && !(NrPieces&1)) {
5664         int i; /* [HGM] Accept even length from 12 to 34 */
5665
5666         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5667         for( i=0; i<NrPieces/2-1; i++ ) {
5668             table[i] = map[i];
5669             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5670         }
5671         table[(int) WhiteKing]  = map[NrPieces/2-1];
5672         table[(int) BlackKing]  = map[NrPieces-1];
5673
5674         result = TRUE;
5675     }
5676
5677     return result;
5678 }
5679
5680 void Prelude(Board board)
5681 {       // [HGM] superchess: random selection of exo-pieces
5682         int i, j, k; ChessSquare p;
5683         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5684
5685         GetPositionNumber(); // use FRC position number
5686
5687         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5688             SetCharTable(pieceToChar, appData.pieceToCharTable);
5689             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5690                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5691         }
5692
5693         j = seed%4;                 seed /= 4;
5694         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5695         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5696         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5697         j = seed%3 + (seed%3 >= j); seed /= 3;
5698         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5699         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5700         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5701         j = seed%3;                 seed /= 3;
5702         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5703         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5704         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5705         j = seed%2 + (seed%2 >= j); seed /= 2;
5706         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5707         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5708         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5709         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5710         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5711         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5712         put(board, exoPieces[0],    0, 0, ANY);
5713         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5714 }
5715
5716 void
5717 InitPosition(redraw)
5718      int redraw;
5719 {
5720     ChessSquare (* pieces)[BOARD_FILES];
5721     int i, j, pawnRow, overrule,
5722     oldx = gameInfo.boardWidth,
5723     oldy = gameInfo.boardHeight,
5724     oldh = gameInfo.holdingsWidth;
5725     static int oldv;
5726
5727     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5728
5729     /* [AS] Initialize pv info list [HGM] and game status */
5730     {
5731         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5732             pvInfoList[i].depth = 0;
5733             boards[i][EP_STATUS] = EP_NONE;
5734             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5735         }
5736
5737         initialRulePlies = 0; /* 50-move counter start */
5738
5739         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5740         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5741     }
5742
5743
5744     /* [HGM] logic here is completely changed. In stead of full positions */
5745     /* the initialized data only consist of the two backranks. The switch */
5746     /* selects which one we will use, which is than copied to the Board   */
5747     /* initialPosition, which for the rest is initialized by Pawns and    */
5748     /* empty squares. This initial position is then copied to boards[0],  */
5749     /* possibly after shuffling, so that it remains available.            */
5750
5751     gameInfo.holdingsWidth = 0; /* default board sizes */
5752     gameInfo.boardWidth    = 8;
5753     gameInfo.boardHeight   = 8;
5754     gameInfo.holdingsSize  = 0;
5755     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5756     for(i=0; i<BOARD_FILES-2; i++)
5757       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5758     initialPosition[EP_STATUS] = EP_NONE;
5759     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5760     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5761          SetCharTable(pieceNickName, appData.pieceNickNames);
5762     else SetCharTable(pieceNickName, "............");
5763     pieces = FIDEArray;
5764
5765     switch (gameInfo.variant) {
5766     case VariantFischeRandom:
5767       shuffleOpenings = TRUE;
5768     default:
5769       break;
5770     case VariantShatranj:
5771       pieces = ShatranjArray;
5772       nrCastlingRights = 0;
5773       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5774       break;
5775     case VariantMakruk:
5776       pieces = makrukArray;
5777       nrCastlingRights = 0;
5778       startedFromSetupPosition = TRUE;
5779       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5780       break;
5781     case VariantTwoKings:
5782       pieces = twoKingsArray;
5783       break;
5784     case VariantGrand:
5785       pieces = GrandArray;
5786       nrCastlingRights = 0;
5787       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5788       gameInfo.boardWidth = 10;
5789       gameInfo.boardHeight = 10;
5790       gameInfo.holdingsSize = 7;
5791       break;
5792     case VariantCapaRandom:
5793       shuffleOpenings = TRUE;
5794     case VariantCapablanca:
5795       pieces = CapablancaArray;
5796       gameInfo.boardWidth = 10;
5797       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5798       break;
5799     case VariantGothic:
5800       pieces = GothicArray;
5801       gameInfo.boardWidth = 10;
5802       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5803       break;
5804     case VariantSChess:
5805       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5806       gameInfo.holdingsSize = 7;
5807       break;
5808     case VariantJanus:
5809       pieces = JanusArray;
5810       gameInfo.boardWidth = 10;
5811       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5812       nrCastlingRights = 6;
5813         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5814         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5815         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5816         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5817         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5818         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5819       break;
5820     case VariantFalcon:
5821       pieces = FalconArray;
5822       gameInfo.boardWidth = 10;
5823       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5824       break;
5825     case VariantXiangqi:
5826       pieces = XiangqiArray;
5827       gameInfo.boardWidth  = 9;
5828       gameInfo.boardHeight = 10;
5829       nrCastlingRights = 0;
5830       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5831       break;
5832     case VariantShogi:
5833       pieces = ShogiArray;
5834       gameInfo.boardWidth  = 9;
5835       gameInfo.boardHeight = 9;
5836       gameInfo.holdingsSize = 7;
5837       nrCastlingRights = 0;
5838       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5839       break;
5840     case VariantCourier:
5841       pieces = CourierArray;
5842       gameInfo.boardWidth  = 12;
5843       nrCastlingRights = 0;
5844       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5845       break;
5846     case VariantKnightmate:
5847       pieces = KnightmateArray;
5848       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5849       break;
5850     case VariantSpartan:
5851       pieces = SpartanArray;
5852       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5853       break;
5854     case VariantFairy:
5855       pieces = fairyArray;
5856       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5857       break;
5858     case VariantGreat:
5859       pieces = GreatArray;
5860       gameInfo.boardWidth = 10;
5861       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5862       gameInfo.holdingsSize = 8;
5863       break;
5864     case VariantSuper:
5865       pieces = FIDEArray;
5866       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5867       gameInfo.holdingsSize = 8;
5868       startedFromSetupPosition = TRUE;
5869       break;
5870     case VariantCrazyhouse:
5871     case VariantBughouse:
5872       pieces = FIDEArray;
5873       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5874       gameInfo.holdingsSize = 5;
5875       break;
5876     case VariantWildCastle:
5877       pieces = FIDEArray;
5878       /* !!?shuffle with kings guaranteed to be on d or e file */
5879       shuffleOpenings = 1;
5880       break;
5881     case VariantNoCastle:
5882       pieces = FIDEArray;
5883       nrCastlingRights = 0;
5884       /* !!?unconstrained back-rank shuffle */
5885       shuffleOpenings = 1;
5886       break;
5887     }
5888
5889     overrule = 0;
5890     if(appData.NrFiles >= 0) {
5891         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5892         gameInfo.boardWidth = appData.NrFiles;
5893     }
5894     if(appData.NrRanks >= 0) {
5895         gameInfo.boardHeight = appData.NrRanks;
5896     }
5897     if(appData.holdingsSize >= 0) {
5898         i = appData.holdingsSize;
5899         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5900         gameInfo.holdingsSize = i;
5901     }
5902     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5903     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5904         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5905
5906     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5907     if(pawnRow < 1) pawnRow = 1;
5908     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5909
5910     /* User pieceToChar list overrules defaults */
5911     if(appData.pieceToCharTable != NULL)
5912         SetCharTable(pieceToChar, appData.pieceToCharTable);
5913
5914     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5915
5916         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5917             s = (ChessSquare) 0; /* account holding counts in guard band */
5918         for( i=0; i<BOARD_HEIGHT; i++ )
5919             initialPosition[i][j] = s;
5920
5921         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5922         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5923         initialPosition[pawnRow][j] = WhitePawn;
5924         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5925         if(gameInfo.variant == VariantXiangqi) {
5926             if(j&1) {
5927                 initialPosition[pawnRow][j] =
5928                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5929                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5930                    initialPosition[2][j] = WhiteCannon;
5931                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5932                 }
5933             }
5934         }
5935         if(gameInfo.variant == VariantGrand) {
5936             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5937                initialPosition[0][j] = WhiteRook;
5938                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5939             }
5940         }
5941         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5942     }
5943     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5944
5945             j=BOARD_LEFT+1;
5946             initialPosition[1][j] = WhiteBishop;
5947             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5948             j=BOARD_RGHT-2;
5949             initialPosition[1][j] = WhiteRook;
5950             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5951     }
5952
5953     if( nrCastlingRights == -1) {
5954         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5955         /*       This sets default castling rights from none to normal corners   */
5956         /* Variants with other castling rights must set them themselves above    */
5957         nrCastlingRights = 6;
5958
5959         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5960         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5961         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5962         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5963         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5964         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5965      }
5966
5967      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5968      if(gameInfo.variant == VariantGreat) { // promotion commoners
5969         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5970         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5971         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5972         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5973      }
5974      if( gameInfo.variant == VariantSChess ) {
5975       initialPosition[1][0] = BlackMarshall;
5976       initialPosition[2][0] = BlackAngel;
5977       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5978       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5979       initialPosition[1][1] = initialPosition[2][1] = 
5980       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5981      }
5982   if (appData.debugMode) {
5983     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5984   }
5985     if(shuffleOpenings) {
5986         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5987         startedFromSetupPosition = TRUE;
5988     }
5989     if(startedFromPositionFile) {
5990       /* [HGM] loadPos: use PositionFile for every new game */
5991       CopyBoard(initialPosition, filePosition);
5992       for(i=0; i<nrCastlingRights; i++)
5993           initialRights[i] = filePosition[CASTLING][i];
5994       startedFromSetupPosition = TRUE;
5995     }
5996
5997     CopyBoard(boards[0], initialPosition);
5998
5999     if(oldx != gameInfo.boardWidth ||
6000        oldy != gameInfo.boardHeight ||
6001        oldv != gameInfo.variant ||
6002        oldh != gameInfo.holdingsWidth
6003                                          )
6004             InitDrawingSizes(-2 ,0);
6005
6006     oldv = gameInfo.variant;
6007     if (redraw)
6008       DrawPosition(TRUE, boards[currentMove]);
6009 }
6010
6011 void
6012 SendBoard(cps, moveNum)
6013      ChessProgramState *cps;
6014      int moveNum;
6015 {
6016     char message[MSG_SIZ];
6017
6018     if (cps->useSetboard) {
6019       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6020       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6021       SendToProgram(message, cps);
6022       free(fen);
6023
6024     } else {
6025       ChessSquare *bp;
6026       int i, j;
6027       /* Kludge to set black to move, avoiding the troublesome and now
6028        * deprecated "black" command.
6029        */
6030       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6031         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6032
6033       SendToProgram("edit\n", cps);
6034       SendToProgram("#\n", cps);
6035       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6036         bp = &boards[moveNum][i][BOARD_LEFT];
6037         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6038           if ((int) *bp < (int) BlackPawn) {
6039             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6040                     AAA + j, ONE + i);
6041             if(message[0] == '+' || message[0] == '~') {
6042               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6043                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6044                         AAA + j, ONE + i);
6045             }
6046             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6047                 message[1] = BOARD_RGHT   - 1 - j + '1';
6048                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6049             }
6050             SendToProgram(message, cps);
6051           }
6052         }
6053       }
6054
6055       SendToProgram("c\n", cps);
6056       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6057         bp = &boards[moveNum][i][BOARD_LEFT];
6058         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6059           if (((int) *bp != (int) EmptySquare)
6060               && ((int) *bp >= (int) BlackPawn)) {
6061             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6062                     AAA + j, ONE + i);
6063             if(message[0] == '+' || message[0] == '~') {
6064               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6065                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6066                         AAA + j, ONE + i);
6067             }
6068             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6069                 message[1] = BOARD_RGHT   - 1 - j + '1';
6070                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6071             }
6072             SendToProgram(message, cps);
6073           }
6074         }
6075       }
6076
6077       SendToProgram(".\n", cps);
6078     }
6079     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6080 }
6081
6082 ChessSquare
6083 DefaultPromoChoice(int white)
6084 {
6085     ChessSquare result;
6086     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6087         result = WhiteFerz; // no choice
6088     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6089         result= WhiteKing; // in Suicide Q is the last thing we want
6090     else if(gameInfo.variant == VariantSpartan)
6091         result = white ? WhiteQueen : WhiteAngel;
6092     else result = WhiteQueen;
6093     if(!white) result = WHITE_TO_BLACK result;
6094     return result;
6095 }
6096
6097 static int autoQueen; // [HGM] oneclick
6098
6099 int
6100 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6101 {
6102     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6103     /* [HGM] add Shogi promotions */
6104     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6105     ChessSquare piece;
6106     ChessMove moveType;
6107     Boolean premove;
6108
6109     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6110     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6111
6112     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6113       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6114         return FALSE;
6115
6116     piece = boards[currentMove][fromY][fromX];
6117     if(gameInfo.variant == VariantShogi) {
6118         promotionZoneSize = BOARD_HEIGHT/3;
6119         highestPromotingPiece = (int)WhiteFerz;
6120     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6121         promotionZoneSize = 3;
6122     }
6123
6124     // Treat Lance as Pawn when it is not representing Amazon
6125     if(gameInfo.variant != VariantSuper) {
6126         if(piece == WhiteLance) piece = WhitePawn; else
6127         if(piece == BlackLance) piece = BlackPawn;
6128     }
6129
6130     // next weed out all moves that do not touch the promotion zone at all
6131     if((int)piece >= BlackPawn) {
6132         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6133              return FALSE;
6134         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6135     } else {
6136         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6137            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6138     }
6139
6140     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6141
6142     // weed out mandatory Shogi promotions
6143     if(gameInfo.variant == VariantShogi) {
6144         if(piece >= BlackPawn) {
6145             if(toY == 0 && piece == BlackPawn ||
6146                toY == 0 && piece == BlackQueen ||
6147                toY <= 1 && piece == BlackKnight) {
6148                 *promoChoice = '+';
6149                 return FALSE;
6150             }
6151         } else {
6152             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6153                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6154                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6155                 *promoChoice = '+';
6156                 return FALSE;
6157             }
6158         }
6159     }
6160
6161     // weed out obviously illegal Pawn moves
6162     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6163         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6164         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6165         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6166         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6167         // note we are not allowed to test for valid (non-)capture, due to premove
6168     }
6169
6170     // we either have a choice what to promote to, or (in Shogi) whether to promote
6171     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6172         *promoChoice = PieceToChar(BlackFerz);  // no choice
6173         return FALSE;
6174     }
6175     // no sense asking what we must promote to if it is going to explode...
6176     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6177         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6178         return FALSE;
6179     }
6180     // give caller the default choice even if we will not make it
6181     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6182     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6183     if(        sweepSelect && gameInfo.variant != VariantGreat
6184                            && gameInfo.variant != VariantGrand
6185                            && gameInfo.variant != VariantSuper) return FALSE;
6186     if(autoQueen) return FALSE; // predetermined
6187
6188     // suppress promotion popup on illegal moves that are not premoves
6189     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6190               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6191     if(appData.testLegality && !premove) {
6192         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6193                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6194         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6195             return FALSE;
6196     }
6197
6198     return TRUE;
6199 }
6200
6201 int
6202 InPalace(row, column)
6203      int row, column;
6204 {   /* [HGM] for Xiangqi */
6205     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6206          column < (BOARD_WIDTH + 4)/2 &&
6207          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6208     return FALSE;
6209 }
6210
6211 int
6212 PieceForSquare (x, y)
6213      int x;
6214      int y;
6215 {
6216   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6217      return -1;
6218   else
6219      return boards[currentMove][y][x];
6220 }
6221
6222 int
6223 OKToStartUserMove(x, y)
6224      int x, y;
6225 {
6226     ChessSquare from_piece;
6227     int white_piece;
6228
6229     if (matchMode) return FALSE;
6230     if (gameMode == EditPosition) return TRUE;
6231
6232     if (x >= 0 && y >= 0)
6233       from_piece = boards[currentMove][y][x];
6234     else
6235       from_piece = EmptySquare;
6236
6237     if (from_piece == EmptySquare) return FALSE;
6238
6239     white_piece = (int)from_piece >= (int)WhitePawn &&
6240       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6241
6242     switch (gameMode) {
6243       case AnalyzeFile:
6244       case TwoMachinesPlay:
6245       case EndOfGame:
6246         return FALSE;
6247
6248       case IcsObserving:
6249       case IcsIdle:
6250         return FALSE;
6251
6252       case MachinePlaysWhite:
6253       case IcsPlayingBlack:
6254         if (appData.zippyPlay) return FALSE;
6255         if (white_piece) {
6256             DisplayMoveError(_("You are playing Black"));
6257             return FALSE;
6258         }
6259         break;
6260
6261       case MachinePlaysBlack:
6262       case IcsPlayingWhite:
6263         if (appData.zippyPlay) return FALSE;
6264         if (!white_piece) {
6265             DisplayMoveError(_("You are playing White"));
6266             return FALSE;
6267         }
6268         break;
6269
6270       case PlayFromGameFile:
6271             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6272       case EditGame:
6273         if (!white_piece && WhiteOnMove(currentMove)) {
6274             DisplayMoveError(_("It is White's turn"));
6275             return FALSE;
6276         }
6277         if (white_piece && !WhiteOnMove(currentMove)) {
6278             DisplayMoveError(_("It is Black's turn"));
6279             return FALSE;
6280         }
6281         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6282             /* Editing correspondence game history */
6283             /* Could disallow this or prompt for confirmation */
6284             cmailOldMove = -1;
6285         }
6286         break;
6287
6288       case BeginningOfGame:
6289         if (appData.icsActive) return FALSE;
6290         if (!appData.noChessProgram) {
6291             if (!white_piece) {
6292                 DisplayMoveError(_("You are playing White"));
6293                 return FALSE;
6294             }
6295         }
6296         break;
6297
6298       case Training:
6299         if (!white_piece && WhiteOnMove(currentMove)) {
6300             DisplayMoveError(_("It is White's turn"));
6301             return FALSE;
6302         }
6303         if (white_piece && !WhiteOnMove(currentMove)) {
6304             DisplayMoveError(_("It is Black's turn"));
6305             return FALSE;
6306         }
6307         break;
6308
6309       default:
6310       case IcsExamining:
6311         break;
6312     }
6313     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6314         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6315         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6316         && gameMode != AnalyzeFile && gameMode != Training) {
6317         DisplayMoveError(_("Displayed position is not current"));
6318         return FALSE;
6319     }
6320     return TRUE;
6321 }
6322
6323 Boolean
6324 OnlyMove(int *x, int *y, Boolean captures) {
6325     DisambiguateClosure cl;
6326     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6327     switch(gameMode) {
6328       case MachinePlaysBlack:
6329       case IcsPlayingWhite:
6330       case BeginningOfGame:
6331         if(!WhiteOnMove(currentMove)) return FALSE;
6332         break;
6333       case MachinePlaysWhite:
6334       case IcsPlayingBlack:
6335         if(WhiteOnMove(currentMove)) return FALSE;
6336         break;
6337       case EditGame:
6338         break;
6339       default:
6340         return FALSE;
6341     }
6342     cl.pieceIn = EmptySquare;
6343     cl.rfIn = *y;
6344     cl.ffIn = *x;
6345     cl.rtIn = -1;
6346     cl.ftIn = -1;
6347     cl.promoCharIn = NULLCHAR;
6348     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6349     if( cl.kind == NormalMove ||
6350         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6351         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6352         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6353       fromX = cl.ff;
6354       fromY = cl.rf;
6355       *x = cl.ft;
6356       *y = cl.rt;
6357       return TRUE;
6358     }
6359     if(cl.kind != ImpossibleMove) return FALSE;
6360     cl.pieceIn = EmptySquare;
6361     cl.rfIn = -1;
6362     cl.ffIn = -1;
6363     cl.rtIn = *y;
6364     cl.ftIn = *x;
6365     cl.promoCharIn = NULLCHAR;
6366     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6367     if( cl.kind == NormalMove ||
6368         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6369         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6370         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6371       fromX = cl.ff;
6372       fromY = cl.rf;
6373       *x = cl.ft;
6374       *y = cl.rt;
6375       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6376       return TRUE;
6377     }
6378     return FALSE;
6379 }
6380
6381 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6382 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6383 int lastLoadGameUseList = FALSE;
6384 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6385 ChessMove lastLoadGameStart = EndOfFile;
6386
6387 void
6388 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6389      int fromX, fromY, toX, toY;
6390      int promoChar;
6391 {
6392     ChessMove moveType;
6393     ChessSquare pdown, pup;
6394
6395     /* Check if the user is playing in turn.  This is complicated because we
6396        let the user "pick up" a piece before it is his turn.  So the piece he
6397        tried to pick up may have been captured by the time he puts it down!
6398        Therefore we use the color the user is supposed to be playing in this
6399        test, not the color of the piece that is currently on the starting
6400        square---except in EditGame mode, where the user is playing both
6401        sides; fortunately there the capture race can't happen.  (It can
6402        now happen in IcsExamining mode, but that's just too bad.  The user
6403        will get a somewhat confusing message in that case.)
6404        */
6405
6406     switch (gameMode) {
6407       case AnalyzeFile:
6408       case TwoMachinesPlay:
6409       case EndOfGame:
6410       case IcsObserving:
6411       case IcsIdle:
6412         /* We switched into a game mode where moves are not accepted,
6413            perhaps while the mouse button was down. */
6414         return;
6415
6416       case MachinePlaysWhite:
6417         /* User is moving for Black */
6418         if (WhiteOnMove(currentMove)) {
6419             DisplayMoveError(_("It is White's turn"));
6420             return;
6421         }
6422         break;
6423
6424       case MachinePlaysBlack:
6425         /* User is moving for White */
6426         if (!WhiteOnMove(currentMove)) {
6427             DisplayMoveError(_("It is Black's turn"));
6428             return;
6429         }
6430         break;
6431
6432       case PlayFromGameFile:
6433             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6434       case EditGame:
6435       case IcsExamining:
6436       case BeginningOfGame:
6437       case AnalyzeMode:
6438       case Training:
6439         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6440         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6441             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6442             /* User is moving for Black */
6443             if (WhiteOnMove(currentMove)) {
6444                 DisplayMoveError(_("It is White's turn"));
6445                 return;
6446             }
6447         } else {
6448             /* User is moving for White */
6449             if (!WhiteOnMove(currentMove)) {
6450                 DisplayMoveError(_("It is Black's turn"));
6451                 return;
6452             }
6453         }
6454         break;
6455
6456       case IcsPlayingBlack:
6457         /* User is moving for Black */
6458         if (WhiteOnMove(currentMove)) {
6459             if (!appData.premove) {
6460                 DisplayMoveError(_("It is White's turn"));
6461             } else if (toX >= 0 && toY >= 0) {
6462                 premoveToX = toX;
6463                 premoveToY = toY;
6464                 premoveFromX = fromX;
6465                 premoveFromY = fromY;
6466                 premovePromoChar = promoChar;
6467                 gotPremove = 1;
6468                 if (appData.debugMode)
6469                     fprintf(debugFP, "Got premove: fromX %d,"
6470                             "fromY %d, toX %d, toY %d\n",
6471                             fromX, fromY, toX, toY);
6472             }
6473             return;
6474         }
6475         break;
6476
6477       case IcsPlayingWhite:
6478         /* User is moving for White */
6479         if (!WhiteOnMove(currentMove)) {
6480             if (!appData.premove) {
6481                 DisplayMoveError(_("It is Black's turn"));
6482             } else if (toX >= 0 && toY >= 0) {
6483                 premoveToX = toX;
6484                 premoveToY = toY;
6485                 premoveFromX = fromX;
6486                 premoveFromY = fromY;
6487                 premovePromoChar = promoChar;
6488                 gotPremove = 1;
6489                 if (appData.debugMode)
6490                     fprintf(debugFP, "Got premove: fromX %d,"
6491                             "fromY %d, toX %d, toY %d\n",
6492                             fromX, fromY, toX, toY);
6493             }
6494             return;
6495         }
6496         break;
6497
6498       default:
6499         break;
6500
6501       case EditPosition:
6502         /* EditPosition, empty square, or different color piece;
6503            click-click move is possible */
6504         if (toX == -2 || toY == -2) {
6505             boards[0][fromY][fromX] = EmptySquare;
6506             DrawPosition(FALSE, boards[currentMove]);
6507             return;
6508         } else if (toX >= 0 && toY >= 0) {
6509             boards[0][toY][toX] = boards[0][fromY][fromX];
6510             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6511                 if(boards[0][fromY][0] != EmptySquare) {
6512                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6513                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6514                 }
6515             } else
6516             if(fromX == BOARD_RGHT+1) {
6517                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6518                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6519                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6520                 }
6521             } else
6522             boards[0][fromY][fromX] = EmptySquare;
6523             DrawPosition(FALSE, boards[currentMove]);
6524             return;
6525         }
6526         return;
6527     }
6528
6529     if(toX < 0 || toY < 0) return;
6530     pdown = boards[currentMove][fromY][fromX];
6531     pup = boards[currentMove][toY][toX];
6532
6533     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6534     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6535          if( pup != EmptySquare ) return;
6536          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6537            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6538                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6539            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6540            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6541            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6542            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6543          fromY = DROP_RANK;
6544     }
6545
6546     /* [HGM] always test for legality, to get promotion info */
6547     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6548                                          fromY, fromX, toY, toX, promoChar);
6549
6550     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6551
6552     /* [HGM] but possibly ignore an IllegalMove result */
6553     if (appData.testLegality) {
6554         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6555             DisplayMoveError(_("Illegal move"));
6556             return;
6557         }
6558     }
6559
6560     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6561 }
6562
6563 /* Common tail of UserMoveEvent and DropMenuEvent */
6564 int
6565 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6566      ChessMove moveType;
6567      int fromX, fromY, toX, toY;
6568      /*char*/int promoChar;
6569 {
6570     char *bookHit = 0;
6571
6572     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6573         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6574         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6575         if(WhiteOnMove(currentMove)) {
6576             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6577         } else {
6578             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6579         }
6580     }
6581
6582     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6583        move type in caller when we know the move is a legal promotion */
6584     if(moveType == NormalMove && promoChar)
6585         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6586
6587     /* [HGM] <popupFix> The following if has been moved here from
6588        UserMoveEvent(). Because it seemed to belong here (why not allow
6589        piece drops in training games?), and because it can only be
6590        performed after it is known to what we promote. */
6591     if (gameMode == Training) {
6592       /* compare the move played on the board to the next move in the
6593        * game. If they match, display the move and the opponent's response.
6594        * If they don't match, display an error message.
6595        */
6596       int saveAnimate;
6597       Board testBoard;
6598       CopyBoard(testBoard, boards[currentMove]);
6599       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6600
6601       if (CompareBoards(testBoard, boards[currentMove+1])) {
6602         ForwardInner(currentMove+1);
6603
6604         /* Autoplay the opponent's response.
6605          * if appData.animate was TRUE when Training mode was entered,
6606          * the response will be animated.
6607          */
6608         saveAnimate = appData.animate;
6609         appData.animate = animateTraining;
6610         ForwardInner(currentMove+1);
6611         appData.animate = saveAnimate;
6612
6613         /* check for the end of the game */
6614         if (currentMove >= forwardMostMove) {
6615           gameMode = PlayFromGameFile;
6616           ModeHighlight();
6617           SetTrainingModeOff();
6618           DisplayInformation(_("End of game"));
6619         }
6620       } else {
6621         DisplayError(_("Incorrect move"), 0);
6622       }
6623       return 1;
6624     }
6625
6626   /* Ok, now we know that the move is good, so we can kill
6627      the previous line in Analysis Mode */
6628   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6629                                 && currentMove < forwardMostMove) {
6630     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6631     else forwardMostMove = currentMove;
6632   }
6633
6634   /* If we need the chess program but it's dead, restart it */
6635   ResurrectChessProgram();
6636
6637   /* A user move restarts a paused game*/
6638   if (pausing)
6639     PauseEvent();
6640
6641   thinkOutput[0] = NULLCHAR;
6642
6643   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6644
6645   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6646     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6647     return 1;
6648   }
6649
6650   if (gameMode == BeginningOfGame) {
6651     if (appData.noChessProgram) {
6652       gameMode = EditGame;
6653       SetGameInfo();
6654     } else {
6655       char buf[MSG_SIZ];
6656       gameMode = MachinePlaysBlack;
6657       StartClocks();
6658       SetGameInfo();
6659       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6660       DisplayTitle(buf);
6661       if (first.sendName) {
6662         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6663         SendToProgram(buf, &first);
6664       }
6665       StartClocks();
6666     }
6667     ModeHighlight();
6668   }
6669
6670   /* Relay move to ICS or chess engine */
6671   if (appData.icsActive) {
6672     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6673         gameMode == IcsExamining) {
6674       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6675         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6676         SendToICS("draw ");
6677         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6678       }
6679       // also send plain move, in case ICS does not understand atomic claims
6680       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6681       ics_user_moved = 1;
6682     }
6683   } else {
6684     if (first.sendTime && (gameMode == BeginningOfGame ||
6685                            gameMode == MachinePlaysWhite ||
6686                            gameMode == MachinePlaysBlack)) {
6687       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6688     }
6689     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6690          // [HGM] book: if program might be playing, let it use book
6691         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6692         first.maybeThinking = TRUE;
6693     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6694         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6695         SendBoard(&first, currentMove+1);
6696     } else SendMoveToProgram(forwardMostMove-1, &first);
6697     if (currentMove == cmailOldMove + 1) {
6698       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6699     }
6700   }
6701
6702   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6703
6704   switch (gameMode) {
6705   case EditGame:
6706     if(appData.testLegality)
6707     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6708     case MT_NONE:
6709     case MT_CHECK:
6710       break;
6711     case MT_CHECKMATE:
6712     case MT_STAINMATE:
6713       if (WhiteOnMove(currentMove)) {
6714         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6715       } else {
6716         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6717       }
6718       break;
6719     case MT_STALEMATE:
6720       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6721       break;
6722     }
6723     break;
6724
6725   case MachinePlaysBlack:
6726   case MachinePlaysWhite:
6727     /* disable certain menu options while machine is thinking */
6728     SetMachineThinkingEnables();
6729     break;
6730
6731   default:
6732     break;
6733   }
6734
6735   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6736   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6737
6738   if(bookHit) { // [HGM] book: simulate book reply
6739         static char bookMove[MSG_SIZ]; // a bit generous?
6740
6741         programStats.nodes = programStats.depth = programStats.time =
6742         programStats.score = programStats.got_only_move = 0;
6743         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6744
6745         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6746         strcat(bookMove, bookHit);
6747         HandleMachineMove(bookMove, &first);
6748   }
6749   return 1;
6750 }
6751
6752 void
6753 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6754      Board board;
6755      int flags;
6756      ChessMove kind;
6757      int rf, ff, rt, ft;
6758      VOIDSTAR closure;
6759 {
6760     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6761     Markers *m = (Markers *) closure;
6762     if(rf == fromY && ff == fromX)
6763         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6764                          || kind == WhiteCapturesEnPassant
6765                          || kind == BlackCapturesEnPassant);
6766     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6767 }
6768
6769 void
6770 MarkTargetSquares(int clear)
6771 {
6772   int x, y;
6773   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6774      !appData.testLegality || gameMode == EditPosition) return;
6775   if(clear) {
6776     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6777   } else {
6778     int capt = 0;
6779     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6780     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6781       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6782       if(capt)
6783       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6784     }
6785   }
6786   DrawPosition(TRUE, NULL);
6787 }
6788
6789 int
6790 Explode(Board board, int fromX, int fromY, int toX, int toY)
6791 {
6792     if(gameInfo.variant == VariantAtomic &&
6793        (board[toY][toX] != EmptySquare ||                     // capture?
6794         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6795                          board[fromY][fromX] == BlackPawn   )
6796       )) {
6797         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6798         return TRUE;
6799     }
6800     return FALSE;
6801 }
6802
6803 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6804
6805 int CanPromote(ChessSquare piece, int y)
6806 {
6807         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6808         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6809         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6810            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6811            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6812                                                   gameInfo.variant == VariantMakruk) return FALSE;
6813         return (piece == BlackPawn && y == 1 ||
6814                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6815                 piece == BlackLance && y == 1 ||
6816                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6817 }
6818
6819 void LeftClick(ClickType clickType, int xPix, int yPix)
6820 {
6821     int x, y;
6822     Boolean saveAnimate;
6823     static int second = 0, promotionChoice = 0, clearFlag = 0;
6824     char promoChoice = NULLCHAR;
6825     ChessSquare piece;
6826
6827     if(appData.seekGraph && appData.icsActive && loggedOn &&
6828         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6829         SeekGraphClick(clickType, xPix, yPix, 0);
6830         return;
6831     }
6832
6833     if (clickType == Press) ErrorPopDown();
6834
6835     x = EventToSquare(xPix, BOARD_WIDTH);
6836     y = EventToSquare(yPix, BOARD_HEIGHT);
6837     if (!flipView && y >= 0) {
6838         y = BOARD_HEIGHT - 1 - y;
6839     }
6840     if (flipView && x >= 0) {
6841         x = BOARD_WIDTH - 1 - x;
6842     }
6843
6844     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6845         defaultPromoChoice = promoSweep;
6846         promoSweep = EmptySquare;   // terminate sweep
6847         promoDefaultAltered = TRUE;
6848         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6849     }
6850
6851     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6852         if(clickType == Release) return; // ignore upclick of click-click destination
6853         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6854         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6855         if(gameInfo.holdingsWidth &&
6856                 (WhiteOnMove(currentMove)
6857                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6858                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6859             // click in right holdings, for determining promotion piece
6860             ChessSquare p = boards[currentMove][y][x];
6861             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6862             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6863             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6864                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6865                 fromX = fromY = -1;
6866                 return;
6867             }
6868         }
6869         DrawPosition(FALSE, boards[currentMove]);
6870         return;
6871     }
6872
6873     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6874     if(clickType == Press
6875             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6876               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6877               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6878         return;
6879
6880     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6881         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6882
6883     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6884         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6885                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6886         defaultPromoChoice = DefaultPromoChoice(side);
6887     }
6888
6889     autoQueen = appData.alwaysPromoteToQueen;
6890
6891     if (fromX == -1) {
6892       int originalY = y;
6893       gatingPiece = EmptySquare;
6894       if (clickType != Press) {
6895         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6896             DragPieceEnd(xPix, yPix); dragging = 0;
6897             DrawPosition(FALSE, NULL);
6898         }
6899         return;
6900       }
6901       fromX = x; fromY = y; toX = toY = -1;
6902       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6903          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6904          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6905             /* First square */
6906             if (OKToStartUserMove(fromX, fromY)) {
6907                 second = 0;
6908                 MarkTargetSquares(0);
6909                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6910                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6911                     promoSweep = defaultPromoChoice;
6912                     selectFlag = 0; lastX = xPix; lastY = yPix;
6913                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6914                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6915                 }
6916                 if (appData.highlightDragging) {
6917                     SetHighlights(fromX, fromY, -1, -1);
6918                 }
6919             } else fromX = fromY = -1;
6920             return;
6921         }
6922     }
6923
6924     /* fromX != -1 */
6925     if (clickType == Press && gameMode != EditPosition) {
6926         ChessSquare fromP;
6927         ChessSquare toP;
6928         int frc;
6929
6930         // ignore off-board to clicks
6931         if(y < 0 || x < 0) return;
6932
6933         /* Check if clicking again on the same color piece */
6934         fromP = boards[currentMove][fromY][fromX];
6935         toP = boards[currentMove][y][x];
6936         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6937         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6938              WhitePawn <= toP && toP <= WhiteKing &&
6939              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6940              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6941             (BlackPawn <= fromP && fromP <= BlackKing &&
6942              BlackPawn <= toP && toP <= BlackKing &&
6943              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6944              !(fromP == BlackKing && toP == BlackRook && frc))) {
6945             /* Clicked again on same color piece -- changed his mind */
6946             second = (x == fromX && y == fromY);
6947             promoDefaultAltered = FALSE;
6948             MarkTargetSquares(1);
6949            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6950             if (appData.highlightDragging) {
6951                 SetHighlights(x, y, -1, -1);
6952             } else {
6953                 ClearHighlights();
6954             }
6955             if (OKToStartUserMove(x, y)) {
6956                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6957                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6958                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6959                  gatingPiece = boards[currentMove][fromY][fromX];
6960                 else gatingPiece = EmptySquare;
6961                 fromX = x;
6962                 fromY = y; dragging = 1;
6963                 MarkTargetSquares(0);
6964                 DragPieceBegin(xPix, yPix, FALSE);
6965                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6966                     promoSweep = defaultPromoChoice;
6967                     selectFlag = 0; lastX = xPix; lastY = yPix;
6968                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6969                 }
6970             }
6971            }
6972            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6973            second = FALSE; 
6974         }
6975         // ignore clicks on holdings
6976         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6977     }
6978
6979     if (clickType == Release && x == fromX && y == fromY) {
6980         DragPieceEnd(xPix, yPix); dragging = 0;
6981         if(clearFlag) {
6982             // a deferred attempt to click-click move an empty square on top of a piece
6983             boards[currentMove][y][x] = EmptySquare;
6984             ClearHighlights();
6985             DrawPosition(FALSE, boards[currentMove]);
6986             fromX = fromY = -1; clearFlag = 0;
6987             return;
6988         }
6989         if (appData.animateDragging) {
6990             /* Undo animation damage if any */
6991             DrawPosition(FALSE, NULL);
6992         }
6993         if (second) {
6994             /* Second up/down in same square; just abort move */
6995             second = 0;
6996             fromX = fromY = -1;
6997             gatingPiece = EmptySquare;
6998             ClearHighlights();
6999             gotPremove = 0;
7000             ClearPremoveHighlights();
7001         } else {
7002             /* First upclick in same square; start click-click mode */
7003             SetHighlights(x, y, -1, -1);
7004         }
7005         return;
7006     }
7007
7008     clearFlag = 0;
7009
7010     /* we now have a different from- and (possibly off-board) to-square */
7011     /* Completed move */
7012     toX = x;
7013     toY = y;
7014     saveAnimate = appData.animate;
7015     MarkTargetSquares(1);
7016     if (clickType == Press) {
7017         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7018             // must be Edit Position mode with empty-square selected
7019             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7020             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7021             return;
7022         }
7023         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7024             ChessSquare piece = boards[currentMove][fromY][fromX];
7025             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7026             promoSweep = defaultPromoChoice;
7027             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7028             selectFlag = 0; lastX = xPix; lastY = yPix;
7029             Sweep(0); // Pawn that is going to promote: preview promotion piece
7030             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7031             DrawPosition(FALSE, boards[currentMove]);
7032             return;
7033         }
7034         /* Finish clickclick move */
7035         if (appData.animate || appData.highlightLastMove) {
7036             SetHighlights(fromX, fromY, toX, toY);
7037         } else {
7038             ClearHighlights();
7039         }
7040     } else {
7041         /* Finish drag move */
7042         if (appData.highlightLastMove) {
7043             SetHighlights(fromX, fromY, toX, toY);
7044         } else {
7045             ClearHighlights();
7046         }
7047         DragPieceEnd(xPix, yPix); dragging = 0;
7048         /* Don't animate move and drag both */
7049         appData.animate = FALSE;
7050     }
7051
7052     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7053     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7054         ChessSquare piece = boards[currentMove][fromY][fromX];
7055         if(gameMode == EditPosition && piece != EmptySquare &&
7056            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7057             int n;
7058
7059             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7060                 n = PieceToNumber(piece - (int)BlackPawn);
7061                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7062                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7063                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7064             } else
7065             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7066                 n = PieceToNumber(piece);
7067                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7068                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7069                 boards[currentMove][n][BOARD_WIDTH-2]++;
7070             }
7071             boards[currentMove][fromY][fromX] = EmptySquare;
7072         }
7073         ClearHighlights();
7074         fromX = fromY = -1;
7075         DrawPosition(TRUE, boards[currentMove]);
7076         return;
7077     }
7078
7079     // off-board moves should not be highlighted
7080     if(x < 0 || y < 0) ClearHighlights();
7081
7082     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7083
7084     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7085         SetHighlights(fromX, fromY, toX, toY);
7086         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7087             // [HGM] super: promotion to captured piece selected from holdings
7088             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7089             promotionChoice = TRUE;
7090             // kludge follows to temporarily execute move on display, without promoting yet
7091             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7092             boards[currentMove][toY][toX] = p;
7093             DrawPosition(FALSE, boards[currentMove]);
7094             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7095             boards[currentMove][toY][toX] = q;
7096             DisplayMessage("Click in holdings to choose piece", "");
7097             return;
7098         }
7099         PromotionPopUp();
7100     } else {
7101         int oldMove = currentMove;
7102         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7103         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7104         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7105         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7106            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7107             DrawPosition(TRUE, boards[currentMove]);
7108         fromX = fromY = -1;
7109     }
7110     appData.animate = saveAnimate;
7111     if (appData.animate || appData.animateDragging) {
7112         /* Undo animation damage if needed */
7113         DrawPosition(FALSE, NULL);
7114     }
7115 }
7116
7117 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7118 {   // front-end-free part taken out of PieceMenuPopup
7119     int whichMenu; int xSqr, ySqr;
7120
7121     if(seekGraphUp) { // [HGM] seekgraph
7122         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7123         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7124         return -2;
7125     }
7126
7127     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7128          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7129         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7130         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7131         if(action == Press)   {
7132             originalFlip = flipView;
7133             flipView = !flipView; // temporarily flip board to see game from partners perspective
7134             DrawPosition(TRUE, partnerBoard);
7135             DisplayMessage(partnerStatus, "");
7136             partnerUp = TRUE;
7137         } else if(action == Release) {
7138             flipView = originalFlip;
7139             DrawPosition(TRUE, boards[currentMove]);
7140             partnerUp = FALSE;
7141         }
7142         return -2;
7143     }
7144
7145     xSqr = EventToSquare(x, BOARD_WIDTH);
7146     ySqr = EventToSquare(y, BOARD_HEIGHT);
7147     if (action == Release) {
7148         if(pieceSweep != EmptySquare) {
7149             EditPositionMenuEvent(pieceSweep, toX, toY);
7150             pieceSweep = EmptySquare;
7151         } else UnLoadPV(); // [HGM] pv
7152     }
7153     if (action != Press) return -2; // return code to be ignored
7154     switch (gameMode) {
7155       case IcsExamining:
7156         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7157       case EditPosition:
7158         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7159         if (xSqr < 0 || ySqr < 0) return -1;
7160         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7161         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7162         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7163         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7164         NextPiece(0);
7165         return 2; // grab
7166       case IcsObserving:
7167         if(!appData.icsEngineAnalyze) return -1;
7168       case IcsPlayingWhite:
7169       case IcsPlayingBlack:
7170         if(!appData.zippyPlay) goto noZip;
7171       case AnalyzeMode:
7172       case AnalyzeFile:
7173       case MachinePlaysWhite:
7174       case MachinePlaysBlack:
7175       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7176         if (!appData.dropMenu) {
7177           LoadPV(x, y);
7178           return 2; // flag front-end to grab mouse events
7179         }
7180         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7181            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7182       case EditGame:
7183       noZip:
7184         if (xSqr < 0 || ySqr < 0) return -1;
7185         if (!appData.dropMenu || appData.testLegality &&
7186             gameInfo.variant != VariantBughouse &&
7187             gameInfo.variant != VariantCrazyhouse) return -1;
7188         whichMenu = 1; // drop menu
7189         break;
7190       default:
7191         return -1;
7192     }
7193
7194     if (((*fromX = xSqr) < 0) ||
7195         ((*fromY = ySqr) < 0)) {
7196         *fromX = *fromY = -1;
7197         return -1;
7198     }
7199     if (flipView)
7200       *fromX = BOARD_WIDTH - 1 - *fromX;
7201     else
7202       *fromY = BOARD_HEIGHT - 1 - *fromY;
7203
7204     return whichMenu;
7205 }
7206
7207 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7208 {
7209 //    char * hint = lastHint;
7210     FrontEndProgramStats stats;
7211
7212     stats.which = cps == &first ? 0 : 1;
7213     stats.depth = cpstats->depth;
7214     stats.nodes = cpstats->nodes;
7215     stats.score = cpstats->score;
7216     stats.time = cpstats->time;
7217     stats.pv = cpstats->movelist;
7218     stats.hint = lastHint;
7219     stats.an_move_index = 0;
7220     stats.an_move_count = 0;
7221
7222     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7223         stats.hint = cpstats->move_name;
7224         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7225         stats.an_move_count = cpstats->nr_moves;
7226     }
7227
7228     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
7229
7230     SetProgramStats( &stats );
7231 }
7232
7233 void
7234 ClearEngineOutputPane(int which)
7235 {
7236     static FrontEndProgramStats dummyStats;
7237     dummyStats.which = which;
7238     dummyStats.pv = "#";
7239     SetProgramStats( &dummyStats );
7240 }
7241
7242 #define MAXPLAYERS 500
7243
7244 char *
7245 TourneyStandings(int display)
7246 {
7247     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7248     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7249     char result, *p, *names[MAXPLAYERS];
7250
7251     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7252         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7253     names[0] = p = strdup(appData.participants);
7254     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7255
7256     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7257
7258     while(result = appData.results[nr]) {
7259         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7260         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7261         wScore = bScore = 0;
7262         switch(result) {
7263           case '+': wScore = 2; break;
7264           case '-': bScore = 2; break;
7265           case '=': wScore = bScore = 1; break;
7266           case ' ':
7267           case '*': return strdup("busy"); // tourney not finished
7268         }
7269         score[w] += wScore;
7270         score[b] += bScore;
7271         games[w]++;
7272         games[b]++;
7273         nr++;
7274     }
7275     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7276     for(w=0; w<nPlayers; w++) {
7277         bScore = -1;
7278         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7279         ranking[w] = b; points[w] = bScore; score[b] = -2;
7280     }
7281     p = malloc(nPlayers*34+1);
7282     for(w=0; w<nPlayers && w<display; w++)
7283         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7284     free(names[0]);
7285     return p;
7286 }
7287
7288 void
7289 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7290 {       // count all piece types
7291         int p, f, r;
7292         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7293         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7294         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7295                 p = board[r][f];
7296                 pCnt[p]++;
7297                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7298                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7299                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7300                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7301                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7302                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7303         }
7304 }
7305
7306 int
7307 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7308 {
7309         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7310         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7311
7312         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7313         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7314         if(myPawns == 2 && nMine == 3) // KPP
7315             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7316         if(myPawns == 1 && nMine == 2) // KP
7317             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7318         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7319             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7320         if(myPawns) return FALSE;
7321         if(pCnt[WhiteRook+side])
7322             return pCnt[BlackRook-side] ||
7323                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7324                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7325                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7326         if(pCnt[WhiteCannon+side]) {
7327             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7328             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7329         }
7330         if(pCnt[WhiteKnight+side])
7331             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7332         return FALSE;
7333 }
7334
7335 int
7336 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7337 {
7338         VariantClass v = gameInfo.variant;
7339
7340         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7341         if(v == VariantShatranj) return TRUE; // always winnable through baring
7342         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7343         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7344
7345         if(v == VariantXiangqi) {
7346                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7347
7348                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7349                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7350                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7351                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7352                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7353                 if(stale) // we have at least one last-rank P plus perhaps C
7354                     return majors // KPKX
7355                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7356                 else // KCA*E*
7357                     return pCnt[WhiteFerz+side] // KCAK
7358                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7359                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7360                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7361
7362         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7363                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7364
7365                 if(nMine == 1) return FALSE; // bare King
7366                 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
7367                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7368                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7369                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7370                 if(pCnt[WhiteKnight+side])
7371                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7372                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7373                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7374                 if(nBishops)
7375                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7376                 if(pCnt[WhiteAlfil+side])
7377                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7378                 if(pCnt[WhiteWazir+side])
7379                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7380         }
7381
7382         return TRUE;
7383 }
7384
7385 int
7386 CompareWithRights(Board b1, Board b2)
7387 {
7388     int rights = 0;
7389     if(!CompareBoards(b1, b2)) return FALSE;
7390     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7391     /* compare castling rights */
7392     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7393            rights++; /* King lost rights, while rook still had them */
7394     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7395         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7396            rights++; /* but at least one rook lost them */
7397     }
7398     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7399            rights++;
7400     if( b1[CASTLING][5] != NoRights ) {
7401         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7402            rights++;
7403     }
7404     return rights == 0;
7405 }
7406
7407 int
7408 Adjudicate(ChessProgramState *cps)
7409 {       // [HGM] some adjudications useful with buggy engines
7410         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7411         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7412         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7413         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7414         int k, count = 0; static int bare = 1;
7415         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7416         Boolean canAdjudicate = !appData.icsActive;
7417
7418         // most tests only when we understand the game, i.e. legality-checking on
7419             if( appData.testLegality )
7420             {   /* [HGM] Some more adjudications for obstinate engines */
7421                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7422                 static int moveCount = 6;
7423                 ChessMove result;
7424                 char *reason = NULL;
7425
7426                 /* Count what is on board. */
7427                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7428
7429                 /* Some material-based adjudications that have to be made before stalemate test */
7430                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7431                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7432                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7433                      if(canAdjudicate && appData.checkMates) {
7434                          if(engineOpponent)
7435                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7436                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7437                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7438                          return 1;
7439                      }
7440                 }
7441
7442                 /* Bare King in Shatranj (loses) or Losers (wins) */
7443                 if( nrW == 1 || nrB == 1) {
7444                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7445                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7446                      if(canAdjudicate && appData.checkMates) {
7447                          if(engineOpponent)
7448                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7449                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7450                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7451                          return 1;
7452                      }
7453                   } else
7454                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7455                   {    /* bare King */
7456                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7457                         if(canAdjudicate && appData.checkMates) {
7458                             /* but only adjudicate if adjudication enabled */
7459                             if(engineOpponent)
7460                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7461                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7462                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7463                             return 1;
7464                         }
7465                   }
7466                 } else bare = 1;
7467
7468
7469             // don't wait for engine to announce game end if we can judge ourselves
7470             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7471               case MT_CHECK:
7472                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7473                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7474                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7475                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7476                             checkCnt++;
7477                         if(checkCnt >= 2) {
7478                             reason = "Xboard adjudication: 3rd check";
7479                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7480                             break;
7481                         }
7482                     }
7483                 }
7484               case MT_NONE:
7485               default:
7486                 break;
7487               case MT_STALEMATE:
7488               case MT_STAINMATE:
7489                 reason = "Xboard adjudication: Stalemate";
7490                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7491                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7492                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7493                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7494                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7495                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7496                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7497                                                                         EP_CHECKMATE : EP_WINS);
7498                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7499                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7500                 }
7501                 break;
7502               case MT_CHECKMATE:
7503                 reason = "Xboard adjudication: Checkmate";
7504                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7505                 break;
7506             }
7507
7508                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7509                     case EP_STALEMATE:
7510                         result = GameIsDrawn; break;
7511                     case EP_CHECKMATE:
7512                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7513                     case EP_WINS:
7514                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7515                     default:
7516                         result = EndOfFile;
7517                 }
7518                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7519                     if(engineOpponent)
7520                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7521                     GameEnds( result, reason, GE_XBOARD );
7522                     return 1;
7523                 }
7524
7525                 /* Next absolutely insufficient mating material. */
7526                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7527                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7528                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7529
7530                      /* always flag draws, for judging claims */
7531                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7532
7533                      if(canAdjudicate && appData.materialDraws) {
7534                          /* but only adjudicate them if adjudication enabled */
7535                          if(engineOpponent) {
7536                            SendToProgram("force\n", engineOpponent); // suppress reply
7537                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7538                          }
7539                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7540                          return 1;
7541                      }
7542                 }
7543
7544                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7545                 if(gameInfo.variant == VariantXiangqi ?
7546                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7547                  : nrW + nrB == 4 &&
7548                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7549                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7550                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7551                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7552                    ) ) {
7553                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7554                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7555                           if(engineOpponent) {
7556                             SendToProgram("force\n", engineOpponent); // suppress reply
7557                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7558                           }
7559                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7560                           return 1;
7561                      }
7562                 } else moveCount = 6;
7563             }
7564         if (appData.debugMode) { int i;
7565             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7566                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7567                     appData.drawRepeats);
7568             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7569               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7570
7571         }
7572
7573         // Repetition draws and 50-move rule can be applied independently of legality testing
7574
7575                 /* Check for rep-draws */
7576                 count = 0;
7577                 for(k = forwardMostMove-2;
7578                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7579                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7580                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7581                     k-=2)
7582                 {   int rights=0;
7583                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7584                         /* compare castling rights */
7585                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7586                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7587                                 rights++; /* King lost rights, while rook still had them */
7588                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7589                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7590                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7591                                    rights++; /* but at least one rook lost them */
7592                         }
7593                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7594                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7595                                 rights++;
7596                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7597                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7598                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7599                                    rights++;
7600                         }
7601                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7602                             && appData.drawRepeats > 1) {
7603                              /* adjudicate after user-specified nr of repeats */
7604                              int result = GameIsDrawn;
7605                              char *details = "XBoard adjudication: repetition draw";
7606                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7607                                 // [HGM] xiangqi: check for forbidden perpetuals
7608                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7609                                 for(m=forwardMostMove; m>k; m-=2) {
7610                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7611                                         ourPerpetual = 0; // the current mover did not always check
7612                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7613                                         hisPerpetual = 0; // the opponent did not always check
7614                                 }
7615                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7616                                                                         ourPerpetual, hisPerpetual);
7617                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7618                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7619                                     details = "Xboard adjudication: perpetual checking";
7620                                 } else
7621                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7622                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7623                                 } else
7624                                 // Now check for perpetual chases
7625                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7626                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7627                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7628                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7629                                         static char resdet[MSG_SIZ];
7630                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7631                                         details = resdet;
7632                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7633                                     } else
7634                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7635                                         break; // Abort repetition-checking loop.
7636                                 }
7637                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7638                              }
7639                              if(engineOpponent) {
7640                                SendToProgram("force\n", engineOpponent); // suppress reply
7641                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7642                              }
7643                              GameEnds( result, details, GE_XBOARD );
7644                              return 1;
7645                         }
7646                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7647                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7648                     }
7649                 }
7650
7651                 /* Now we test for 50-move draws. Determine ply count */
7652                 count = forwardMostMove;
7653                 /* look for last irreversble move */
7654                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7655                     count--;
7656                 /* if we hit starting position, add initial plies */
7657                 if( count == backwardMostMove )
7658                     count -= initialRulePlies;
7659                 count = forwardMostMove - count;
7660                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7661                         // adjust reversible move counter for checks in Xiangqi
7662                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7663                         if(i < backwardMostMove) i = backwardMostMove;
7664                         while(i <= forwardMostMove) {
7665                                 lastCheck = inCheck; // check evasion does not count
7666                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7667                                 if(inCheck || lastCheck) count--; // check does not count
7668                                 i++;
7669                         }
7670                 }
7671                 if( count >= 100)
7672                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7673                          /* this is used to judge if draw claims are legal */
7674                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7675                          if(engineOpponent) {
7676                            SendToProgram("force\n", engineOpponent); // suppress reply
7677                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7678                          }
7679                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7680                          return 1;
7681                 }
7682
7683                 /* if draw offer is pending, treat it as a draw claim
7684                  * when draw condition present, to allow engines a way to
7685                  * claim draws before making their move to avoid a race
7686                  * condition occurring after their move
7687                  */
7688                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7689                          char *p = NULL;
7690                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7691                              p = "Draw claim: 50-move rule";
7692                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7693                              p = "Draw claim: 3-fold repetition";
7694                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7695                              p = "Draw claim: insufficient mating material";
7696                          if( p != NULL && canAdjudicate) {
7697                              if(engineOpponent) {
7698                                SendToProgram("force\n", engineOpponent); // suppress reply
7699                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7700                              }
7701                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7702                              return 1;
7703                          }
7704                 }
7705
7706                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7707                     if(engineOpponent) {
7708                       SendToProgram("force\n", engineOpponent); // suppress reply
7709                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7710                     }
7711                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7712                     return 1;
7713                 }
7714         return 0;
7715 }
7716
7717 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7718 {   // [HGM] book: this routine intercepts moves to simulate book replies
7719     char *bookHit = NULL;
7720
7721     //first determine if the incoming move brings opponent into his book
7722     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7723         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7724     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7725     if(bookHit != NULL && !cps->bookSuspend) {
7726         // make sure opponent is not going to reply after receiving move to book position
7727         SendToProgram("force\n", cps);
7728         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7729     }
7730     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7731     // now arrange restart after book miss
7732     if(bookHit) {
7733         // after a book hit we never send 'go', and the code after the call to this routine
7734         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7735         char buf[MSG_SIZ], *move = bookHit;
7736         if(cps->useSAN) {
7737             int fromX, fromY, toX, toY;
7738             char promoChar;
7739             ChessMove moveType;
7740             move = buf + 30;
7741             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7742                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7743                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7744                                     PosFlags(forwardMostMove),
7745                                     fromY, fromX, toY, toX, promoChar, move);
7746             } else {
7747                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7748                 bookHit = NULL;
7749             }
7750         }
7751         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7752         SendToProgram(buf, cps);
7753         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7754     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7755         SendToProgram("go\n", cps);
7756         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7757     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7758         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7759             SendToProgram("go\n", cps);
7760         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7761     }
7762     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7763 }
7764
7765 char *savedMessage;
7766 ChessProgramState *savedState;
7767 void DeferredBookMove(void)
7768 {
7769         if(savedState->lastPing != savedState->lastPong)
7770                     ScheduleDelayedEvent(DeferredBookMove, 10);
7771         else
7772         HandleMachineMove(savedMessage, savedState);
7773 }
7774
7775 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7776
7777 void
7778 HandleMachineMove(message, cps)
7779      char *message;
7780      ChessProgramState *cps;
7781 {
7782     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7783     char realname[MSG_SIZ];
7784     int fromX, fromY, toX, toY;
7785     ChessMove moveType;
7786     char promoChar;
7787     char *p, *pv=buf1;
7788     int machineWhite;
7789     char *bookHit;
7790
7791     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7792         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7793         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7794             DisplayError(_("Invalid pairing from pairing engine"), 0);
7795             return;
7796         }
7797         pairingReceived = 1;
7798         NextMatchGame();
7799         return; // Skim the pairing messages here.
7800     }
7801
7802     cps->userError = 0;
7803
7804 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7805     /*
7806      * Kludge to ignore BEL characters
7807      */
7808     while (*message == '\007') message++;
7809
7810     /*
7811      * [HGM] engine debug message: ignore lines starting with '#' character
7812      */
7813     if(cps->debug && *message == '#') return;
7814
7815     /*
7816      * Look for book output
7817      */
7818     if (cps == &first && bookRequested) {
7819         if (message[0] == '\t' || message[0] == ' ') {
7820             /* Part of the book output is here; append it */
7821             strcat(bookOutput, message);
7822             strcat(bookOutput, "  \n");
7823             return;
7824         } else if (bookOutput[0] != NULLCHAR) {
7825             /* All of book output has arrived; display it */
7826             char *p = bookOutput;
7827             while (*p != NULLCHAR) {
7828                 if (*p == '\t') *p = ' ';
7829                 p++;
7830             }
7831             DisplayInformation(bookOutput);
7832             bookRequested = FALSE;
7833             /* Fall through to parse the current output */
7834         }
7835     }
7836
7837     /*
7838      * Look for machine move.
7839      */
7840     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7841         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7842     {
7843         /* This method is only useful on engines that support ping */
7844         if (cps->lastPing != cps->lastPong) {
7845           if (gameMode == BeginningOfGame) {
7846             /* Extra move from before last new; ignore */
7847             if (appData.debugMode) {
7848                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7849             }
7850           } else {
7851             if (appData.debugMode) {
7852                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7853                         cps->which, gameMode);
7854             }
7855
7856             SendToProgram("undo\n", cps);
7857           }
7858           return;
7859         }
7860
7861         switch (gameMode) {
7862           case BeginningOfGame:
7863             /* Extra move from before last reset; ignore */
7864             if (appData.debugMode) {
7865                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7866             }
7867             return;
7868
7869           case EndOfGame:
7870           case IcsIdle:
7871           default:
7872             /* Extra move after we tried to stop.  The mode test is
7873                not a reliable way of detecting this problem, but it's
7874                the best we can do on engines that don't support ping.
7875             */
7876             if (appData.debugMode) {
7877                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7878                         cps->which, gameMode);
7879             }
7880             SendToProgram("undo\n", cps);
7881             return;
7882
7883           case MachinePlaysWhite:
7884           case IcsPlayingWhite:
7885             machineWhite = TRUE;
7886             break;
7887
7888           case MachinePlaysBlack:
7889           case IcsPlayingBlack:
7890             machineWhite = FALSE;
7891             break;
7892
7893           case TwoMachinesPlay:
7894             machineWhite = (cps->twoMachinesColor[0] == 'w');
7895             break;
7896         }
7897         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7898             if (appData.debugMode) {
7899                 fprintf(debugFP,
7900                         "Ignoring move out of turn by %s, gameMode %d"
7901                         ", forwardMost %d\n",
7902                         cps->which, gameMode, forwardMostMove);
7903             }
7904             return;
7905         }
7906
7907     if (appData.debugMode) { int f = forwardMostMove;
7908         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7909                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7910                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7911     }
7912         if(cps->alphaRank) AlphaRank(machineMove, 4);
7913         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7914                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7915             /* Machine move could not be parsed; ignore it. */
7916           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7917                     machineMove, _(cps->which));
7918             DisplayError(buf1, 0);
7919             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7920                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7921             if (gameMode == TwoMachinesPlay) {
7922               GameEnds(machineWhite ? BlackWins : WhiteWins,
7923                        buf1, GE_XBOARD);
7924             }
7925             return;
7926         }
7927
7928         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7929         /* So we have to redo legality test with true e.p. status here,  */
7930         /* to make sure an illegal e.p. capture does not slip through,   */
7931         /* to cause a forfeit on a justified illegal-move complaint      */
7932         /* of the opponent.                                              */
7933         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7934            ChessMove moveType;
7935            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7936                              fromY, fromX, toY, toX, promoChar);
7937             if (appData.debugMode) {
7938                 int i;
7939                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7940                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7941                 fprintf(debugFP, "castling rights\n");
7942             }
7943             if(moveType == IllegalMove) {
7944               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7945                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7946                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7947                            buf1, GE_XBOARD);
7948                 return;
7949            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7950            /* [HGM] Kludge to handle engines that send FRC-style castling
7951               when they shouldn't (like TSCP-Gothic) */
7952            switch(moveType) {
7953              case WhiteASideCastleFR:
7954              case BlackASideCastleFR:
7955                toX+=2;
7956                currentMoveString[2]++;
7957                break;
7958              case WhiteHSideCastleFR:
7959              case BlackHSideCastleFR:
7960                toX--;
7961                currentMoveString[2]--;
7962                break;
7963              default: ; // nothing to do, but suppresses warning of pedantic compilers
7964            }
7965         }
7966         hintRequested = FALSE;
7967         lastHint[0] = NULLCHAR;
7968         bookRequested = FALSE;
7969         /* Program may be pondering now */
7970         cps->maybeThinking = TRUE;
7971         if (cps->sendTime == 2) cps->sendTime = 1;
7972         if (cps->offeredDraw) cps->offeredDraw--;
7973
7974         /* [AS] Save move info*/
7975         pvInfoList[ forwardMostMove ].score = programStats.score;
7976         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7977         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7978
7979         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7980
7981         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7982         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7983             int count = 0;
7984
7985             while( count < adjudicateLossPlies ) {
7986                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7987
7988                 if( count & 1 ) {
7989                     score = -score; /* Flip score for winning side */
7990                 }
7991
7992                 if( score > adjudicateLossThreshold ) {
7993                     break;
7994                 }
7995
7996                 count++;
7997             }
7998
7999             if( count >= adjudicateLossPlies ) {
8000                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8001
8002                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8003                     "Xboard adjudication",
8004                     GE_XBOARD );
8005
8006                 return;
8007             }
8008         }
8009
8010         if(Adjudicate(cps)) {
8011             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8012             return; // [HGM] adjudicate: for all automatic game ends
8013         }
8014
8015 #if ZIPPY
8016         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8017             first.initDone) {
8018           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8019                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8020                 SendToICS("draw ");
8021                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8022           }
8023           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8024           ics_user_moved = 1;
8025           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8026                 char buf[3*MSG_SIZ];
8027
8028                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8029                         programStats.score / 100.,
8030                         programStats.depth,
8031                         programStats.time / 100.,
8032                         (unsigned int)programStats.nodes,
8033                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8034                         programStats.movelist);
8035                 SendToICS(buf);
8036 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8037           }
8038         }
8039 #endif
8040
8041         /* [AS] Clear stats for next move */
8042         ClearProgramStats();
8043         thinkOutput[0] = NULLCHAR;
8044         hiddenThinkOutputState = 0;
8045
8046         bookHit = NULL;
8047         if (gameMode == TwoMachinesPlay) {
8048             /* [HGM] relaying draw offers moved to after reception of move */
8049             /* and interpreting offer as claim if it brings draw condition */
8050             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8051                 SendToProgram("draw\n", cps->other);
8052             }
8053             if (cps->other->sendTime) {
8054                 SendTimeRemaining(cps->other,
8055                                   cps->other->twoMachinesColor[0] == 'w');
8056             }
8057             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8058             if (firstMove && !bookHit) {
8059                 firstMove = FALSE;
8060                 if (cps->other->useColors) {
8061                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8062                 }
8063                 SendToProgram("go\n", cps->other);
8064             }
8065             cps->other->maybeThinking = TRUE;
8066         }
8067
8068         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8069
8070         if (!pausing && appData.ringBellAfterMoves) {
8071             RingBell();
8072         }
8073
8074         /*
8075          * Reenable menu items that were disabled while
8076          * machine was thinking
8077          */
8078         if (gameMode != TwoMachinesPlay)
8079             SetUserThinkingEnables();
8080
8081         // [HGM] book: after book hit opponent has received move and is now in force mode
8082         // force the book reply into it, and then fake that it outputted this move by jumping
8083         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8084         if(bookHit) {
8085                 static char bookMove[MSG_SIZ]; // a bit generous?
8086
8087                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8088                 strcat(bookMove, bookHit);
8089                 message = bookMove;
8090                 cps = cps->other;
8091                 programStats.nodes = programStats.depth = programStats.time =
8092                 programStats.score = programStats.got_only_move = 0;
8093                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8094
8095                 if(cps->lastPing != cps->lastPong) {
8096                     savedMessage = message; // args for deferred call
8097                     savedState = cps;
8098                     ScheduleDelayedEvent(DeferredBookMove, 10);
8099                     return;
8100                 }
8101                 goto FakeBookMove;
8102         }
8103
8104         return;
8105     }
8106
8107     /* Set special modes for chess engines.  Later something general
8108      *  could be added here; for now there is just one kludge feature,
8109      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8110      *  when "xboard" is given as an interactive command.
8111      */
8112     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8113         cps->useSigint = FALSE;
8114         cps->useSigterm = FALSE;
8115     }
8116     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8117       ParseFeatures(message+8, cps);
8118       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8119     }
8120
8121     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8122                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8123       int dummy, s=6; char buf[MSG_SIZ];
8124       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8125       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8126       if(startedFromSetupPosition) return;
8127       ParseFEN(boards[0], &dummy, message+s);
8128       DrawPosition(TRUE, boards[0]);
8129       startedFromSetupPosition = TRUE;
8130       return;
8131     }
8132     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8133      * want this, I was asked to put it in, and obliged.
8134      */
8135     if (!strncmp(message, "setboard ", 9)) {
8136         Board initial_position;
8137
8138         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8139
8140         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8141             DisplayError(_("Bad FEN received from engine"), 0);
8142             return ;
8143         } else {
8144            Reset(TRUE, FALSE);
8145            CopyBoard(boards[0], initial_position);
8146            initialRulePlies = FENrulePlies;
8147            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8148            else gameMode = MachinePlaysBlack;
8149            DrawPosition(FALSE, boards[currentMove]);
8150         }
8151         return;
8152     }
8153
8154     /*
8155      * Look for communication commands
8156      */
8157     if (!strncmp(message, "telluser ", 9)) {
8158         if(message[9] == '\\' && message[10] == '\\')
8159             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8160         PlayTellSound();
8161         DisplayNote(message + 9);
8162         return;
8163     }
8164     if (!strncmp(message, "tellusererror ", 14)) {
8165         cps->userError = 1;
8166         if(message[14] == '\\' && message[15] == '\\')
8167             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8168         PlayTellSound();
8169         DisplayError(message + 14, 0);
8170         return;
8171     }
8172     if (!strncmp(message, "tellopponent ", 13)) {
8173       if (appData.icsActive) {
8174         if (loggedOn) {
8175           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8176           SendToICS(buf1);
8177         }
8178       } else {
8179         DisplayNote(message + 13);
8180       }
8181       return;
8182     }
8183     if (!strncmp(message, "tellothers ", 11)) {
8184       if (appData.icsActive) {
8185         if (loggedOn) {
8186           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8187           SendToICS(buf1);
8188         }
8189       }
8190       return;
8191     }
8192     if (!strncmp(message, "tellall ", 8)) {
8193       if (appData.icsActive) {
8194         if (loggedOn) {
8195           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8196           SendToICS(buf1);
8197         }
8198       } else {
8199         DisplayNote(message + 8);
8200       }
8201       return;
8202     }
8203     if (strncmp(message, "warning", 7) == 0) {
8204         /* Undocumented feature, use tellusererror in new code */
8205         DisplayError(message, 0);
8206         return;
8207     }
8208     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8209         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8210         strcat(realname, " query");
8211         AskQuestion(realname, buf2, buf1, cps->pr);
8212         return;
8213     }
8214     /* Commands from the engine directly to ICS.  We don't allow these to be
8215      *  sent until we are logged on. Crafty kibitzes have been known to
8216      *  interfere with the login process.
8217      */
8218     if (loggedOn) {
8219         if (!strncmp(message, "tellics ", 8)) {
8220             SendToICS(message + 8);
8221             SendToICS("\n");
8222             return;
8223         }
8224         if (!strncmp(message, "tellicsnoalias ", 15)) {
8225             SendToICS(ics_prefix);
8226             SendToICS(message + 15);
8227             SendToICS("\n");
8228             return;
8229         }
8230         /* The following are for backward compatibility only */
8231         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8232             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8233             SendToICS(ics_prefix);
8234             SendToICS(message);
8235             SendToICS("\n");
8236             return;
8237         }
8238     }
8239     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8240         return;
8241     }
8242     /*
8243      * If the move is illegal, cancel it and redraw the board.
8244      * Also deal with other error cases.  Matching is rather loose
8245      * here to accommodate engines written before the spec.
8246      */
8247     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8248         strncmp(message, "Error", 5) == 0) {
8249         if (StrStr(message, "name") ||
8250             StrStr(message, "rating") || StrStr(message, "?") ||
8251             StrStr(message, "result") || StrStr(message, "board") ||
8252             StrStr(message, "bk") || StrStr(message, "computer") ||
8253             StrStr(message, "variant") || StrStr(message, "hint") ||
8254             StrStr(message, "random") || StrStr(message, "depth") ||
8255             StrStr(message, "accepted")) {
8256             return;
8257         }
8258         if (StrStr(message, "protover")) {
8259           /* Program is responding to input, so it's apparently done
8260              initializing, and this error message indicates it is
8261              protocol version 1.  So we don't need to wait any longer
8262              for it to initialize and send feature commands. */
8263           FeatureDone(cps, 1);
8264           cps->protocolVersion = 1;
8265           return;
8266         }
8267         cps->maybeThinking = FALSE;
8268
8269         if (StrStr(message, "draw")) {
8270             /* Program doesn't have "draw" command */
8271             cps->sendDrawOffers = 0;
8272             return;
8273         }
8274         if (cps->sendTime != 1 &&
8275             (StrStr(message, "time") || StrStr(message, "otim"))) {
8276           /* Program apparently doesn't have "time" or "otim" command */
8277           cps->sendTime = 0;
8278           return;
8279         }
8280         if (StrStr(message, "analyze")) {
8281             cps->analysisSupport = FALSE;
8282             cps->analyzing = FALSE;
8283 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8284             EditGameEvent(); // [HGM] try to preserve loaded game
8285             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8286             DisplayError(buf2, 0);
8287             return;
8288         }
8289         if (StrStr(message, "(no matching move)st")) {
8290           /* Special kludge for GNU Chess 4 only */
8291           cps->stKludge = TRUE;
8292           SendTimeControl(cps, movesPerSession, timeControl,
8293                           timeIncrement, appData.searchDepth,
8294                           searchTime);
8295           return;
8296         }
8297         if (StrStr(message, "(no matching move)sd")) {
8298           /* Special kludge for GNU Chess 4 only */
8299           cps->sdKludge = TRUE;
8300           SendTimeControl(cps, movesPerSession, timeControl,
8301                           timeIncrement, appData.searchDepth,
8302                           searchTime);
8303           return;
8304         }
8305         if (!StrStr(message, "llegal")) {
8306             return;
8307         }
8308         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8309             gameMode == IcsIdle) return;
8310         if (forwardMostMove <= backwardMostMove) return;
8311         if (pausing) PauseEvent();
8312       if(appData.forceIllegal) {
8313             // [HGM] illegal: machine refused move; force position after move into it
8314           SendToProgram("force\n", cps);
8315           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8316                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8317                 // when black is to move, while there might be nothing on a2 or black
8318                 // might already have the move. So send the board as if white has the move.
8319                 // But first we must change the stm of the engine, as it refused the last move
8320                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8321                 if(WhiteOnMove(forwardMostMove)) {
8322                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8323                     SendBoard(cps, forwardMostMove); // kludgeless board
8324                 } else {
8325                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8326                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8327                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8328                 }
8329           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8330             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8331                  gameMode == TwoMachinesPlay)
8332               SendToProgram("go\n", cps);
8333             return;
8334       } else
8335         if (gameMode == PlayFromGameFile) {
8336             /* Stop reading this game file */
8337             gameMode = EditGame;
8338             ModeHighlight();
8339         }
8340         /* [HGM] illegal-move claim should forfeit game when Xboard */
8341         /* only passes fully legal moves                            */
8342         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8343             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8344                                 "False illegal-move claim", GE_XBOARD );
8345             return; // do not take back move we tested as valid
8346         }
8347         currentMove = forwardMostMove-1;
8348         DisplayMove(currentMove-1); /* before DisplayMoveError */
8349         SwitchClocks(forwardMostMove-1); // [HGM] race
8350         DisplayBothClocks();
8351         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8352                 parseList[currentMove], _(cps->which));
8353         DisplayMoveError(buf1);
8354         DrawPosition(FALSE, boards[currentMove]);
8355         return;
8356     }
8357     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8358         /* Program has a broken "time" command that
8359            outputs a string not ending in newline.
8360            Don't use it. */
8361         cps->sendTime = 0;
8362     }
8363
8364     /*
8365      * If chess program startup fails, exit with an error message.
8366      * Attempts to recover here are futile.
8367      */
8368     if ((StrStr(message, "unknown host") != NULL)
8369         || (StrStr(message, "No remote directory") != NULL)
8370         || (StrStr(message, "not found") != NULL)
8371         || (StrStr(message, "No such file") != NULL)
8372         || (StrStr(message, "can't alloc") != NULL)
8373         || (StrStr(message, "Permission denied") != NULL)) {
8374
8375         cps->maybeThinking = FALSE;
8376         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8377                 _(cps->which), cps->program, cps->host, message);
8378         RemoveInputSource(cps->isr);
8379         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8380             if(cps == &first) appData.noChessProgram = TRUE;
8381             DisplayError(buf1, 0);
8382         }
8383         return;
8384     }
8385
8386     /*
8387      * Look for hint output
8388      */
8389     if (sscanf(message, "Hint: %s", buf1) == 1) {
8390         if (cps == &first && hintRequested) {
8391             hintRequested = FALSE;
8392             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8393                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8394                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8395                                     PosFlags(forwardMostMove),
8396                                     fromY, fromX, toY, toX, promoChar, buf1);
8397                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8398                 DisplayInformation(buf2);
8399             } else {
8400                 /* Hint move could not be parsed!? */
8401               snprintf(buf2, sizeof(buf2),
8402                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8403                         buf1, _(cps->which));
8404                 DisplayError(buf2, 0);
8405             }
8406         } else {
8407           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8408         }
8409         return;
8410     }
8411
8412     /*
8413      * Ignore other messages if game is not in progress
8414      */
8415     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8416         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8417
8418     /*
8419      * look for win, lose, draw, or draw offer
8420      */
8421     if (strncmp(message, "1-0", 3) == 0) {
8422         char *p, *q, *r = "";
8423         p = strchr(message, '{');
8424         if (p) {
8425             q = strchr(p, '}');
8426             if (q) {
8427                 *q = NULLCHAR;
8428                 r = p + 1;
8429             }
8430         }
8431         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8432         return;
8433     } else if (strncmp(message, "0-1", 3) == 0) {
8434         char *p, *q, *r = "";
8435         p = strchr(message, '{');
8436         if (p) {
8437             q = strchr(p, '}');
8438             if (q) {
8439                 *q = NULLCHAR;
8440                 r = p + 1;
8441             }
8442         }
8443         /* Kludge for Arasan 4.1 bug */
8444         if (strcmp(r, "Black resigns") == 0) {
8445             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8446             return;
8447         }
8448         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8449         return;
8450     } else if (strncmp(message, "1/2", 3) == 0) {
8451         char *p, *q, *r = "";
8452         p = strchr(message, '{');
8453         if (p) {
8454             q = strchr(p, '}');
8455             if (q) {
8456                 *q = NULLCHAR;
8457                 r = p + 1;
8458             }
8459         }
8460
8461         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8462         return;
8463
8464     } else if (strncmp(message, "White resign", 12) == 0) {
8465         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8466         return;
8467     } else if (strncmp(message, "Black resign", 12) == 0) {
8468         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8469         return;
8470     } else if (strncmp(message, "White matches", 13) == 0 ||
8471                strncmp(message, "Black matches", 13) == 0   ) {
8472         /* [HGM] ignore GNUShogi noises */
8473         return;
8474     } else if (strncmp(message, "White", 5) == 0 &&
8475                message[5] != '(' &&
8476                StrStr(message, "Black") == NULL) {
8477         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8478         return;
8479     } else if (strncmp(message, "Black", 5) == 0 &&
8480                message[5] != '(') {
8481         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8482         return;
8483     } else if (strcmp(message, "resign") == 0 ||
8484                strcmp(message, "computer resigns") == 0) {
8485         switch (gameMode) {
8486           case MachinePlaysBlack:
8487           case IcsPlayingBlack:
8488             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8489             break;
8490           case MachinePlaysWhite:
8491           case IcsPlayingWhite:
8492             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8493             break;
8494           case TwoMachinesPlay:
8495             if (cps->twoMachinesColor[0] == 'w')
8496               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8497             else
8498               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8499             break;
8500           default:
8501             /* can't happen */
8502             break;
8503         }
8504         return;
8505     } else if (strncmp(message, "opponent mates", 14) == 0) {
8506         switch (gameMode) {
8507           case MachinePlaysBlack:
8508           case IcsPlayingBlack:
8509             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8510             break;
8511           case MachinePlaysWhite:
8512           case IcsPlayingWhite:
8513             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8514             break;
8515           case TwoMachinesPlay:
8516             if (cps->twoMachinesColor[0] == 'w')
8517               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8518             else
8519               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8520             break;
8521           default:
8522             /* can't happen */
8523             break;
8524         }
8525         return;
8526     } else if (strncmp(message, "computer mates", 14) == 0) {
8527         switch (gameMode) {
8528           case MachinePlaysBlack:
8529           case IcsPlayingBlack:
8530             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8531             break;
8532           case MachinePlaysWhite:
8533           case IcsPlayingWhite:
8534             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8535             break;
8536           case TwoMachinesPlay:
8537             if (cps->twoMachinesColor[0] == 'w')
8538               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8539             else
8540               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8541             break;
8542           default:
8543             /* can't happen */
8544             break;
8545         }
8546         return;
8547     } else if (strncmp(message, "checkmate", 9) == 0) {
8548         if (WhiteOnMove(forwardMostMove)) {
8549             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8550         } else {
8551             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8552         }
8553         return;
8554     } else if (strstr(message, "Draw") != NULL ||
8555                strstr(message, "game is a draw") != NULL) {
8556         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8557         return;
8558     } else if (strstr(message, "offer") != NULL &&
8559                strstr(message, "draw") != NULL) {
8560 #if ZIPPY
8561         if (appData.zippyPlay && first.initDone) {
8562             /* Relay offer to ICS */
8563             SendToICS(ics_prefix);
8564             SendToICS("draw\n");
8565         }
8566 #endif
8567         cps->offeredDraw = 2; /* valid until this engine moves twice */
8568         if (gameMode == TwoMachinesPlay) {
8569             if (cps->other->offeredDraw) {
8570                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8571             /* [HGM] in two-machine mode we delay relaying draw offer      */
8572             /* until after we also have move, to see if it is really claim */
8573             }
8574         } else if (gameMode == MachinePlaysWhite ||
8575                    gameMode == MachinePlaysBlack) {
8576           if (userOfferedDraw) {
8577             DisplayInformation(_("Machine accepts your draw offer"));
8578             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8579           } else {
8580             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8581           }
8582         }
8583     }
8584
8585
8586     /*
8587      * Look for thinking output
8588      */
8589     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8590           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8591                                 ) {
8592         int plylev, mvleft, mvtot, curscore, time;
8593         char mvname[MOVE_LEN];
8594         u64 nodes; // [DM]
8595         char plyext;
8596         int ignore = FALSE;
8597         int prefixHint = FALSE;
8598         mvname[0] = NULLCHAR;
8599
8600         switch (gameMode) {
8601           case MachinePlaysBlack:
8602           case IcsPlayingBlack:
8603             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8604             break;
8605           case MachinePlaysWhite:
8606           case IcsPlayingWhite:
8607             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8608             break;
8609           case AnalyzeMode:
8610           case AnalyzeFile:
8611             break;
8612           case IcsObserving: /* [DM] icsEngineAnalyze */
8613             if (!appData.icsEngineAnalyze) ignore = TRUE;
8614             break;
8615           case TwoMachinesPlay:
8616             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8617                 ignore = TRUE;
8618             }
8619             break;
8620           default:
8621             ignore = TRUE;
8622             break;
8623         }
8624
8625         if (!ignore) {
8626             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8627             buf1[0] = NULLCHAR;
8628             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8629                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8630
8631                 if (plyext != ' ' && plyext != '\t') {
8632                     time *= 100;
8633                 }
8634
8635                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8636                 if( cps->scoreIsAbsolute &&
8637                     ( gameMode == MachinePlaysBlack ||
8638                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8639                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8640                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8641                      !WhiteOnMove(currentMove)
8642                     ) )
8643                 {
8644                     curscore = -curscore;
8645                 }
8646
8647                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8648
8649                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8650                         char buf[MSG_SIZ];
8651                         FILE *f;
8652                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8653                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8654                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8655                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8656                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8657                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8658                                 fclose(f);
8659                         } else DisplayError("failed writing PV", 0);
8660                 }
8661
8662                 tempStats.depth = plylev;
8663                 tempStats.nodes = nodes;
8664                 tempStats.time = time;
8665                 tempStats.score = curscore;
8666                 tempStats.got_only_move = 0;
8667
8668                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8669                         int ticklen;
8670
8671                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8672                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8673                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8674                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8675                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8676                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8677                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8678                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8679                 }
8680
8681                 /* Buffer overflow protection */
8682                 if (pv[0] != NULLCHAR) {
8683                     if (strlen(pv) >= sizeof(tempStats.movelist)
8684                         && appData.debugMode) {
8685                         fprintf(debugFP,
8686                                 "PV is too long; using the first %u bytes.\n",
8687                                 (unsigned) sizeof(tempStats.movelist) - 1);
8688                     }
8689
8690                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8691                 } else {
8692                     sprintf(tempStats.movelist, " no PV\n");
8693                 }
8694
8695                 if (tempStats.seen_stat) {
8696                     tempStats.ok_to_send = 1;
8697                 }
8698
8699                 if (strchr(tempStats.movelist, '(') != NULL) {
8700                     tempStats.line_is_book = 1;
8701                     tempStats.nr_moves = 0;
8702                     tempStats.moves_left = 0;
8703                 } else {
8704                     tempStats.line_is_book = 0;
8705                 }
8706
8707                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8708                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8709
8710                 SendProgramStatsToFrontend( cps, &tempStats );
8711
8712                 /*
8713                     [AS] Protect the thinkOutput buffer from overflow... this
8714                     is only useful if buf1 hasn't overflowed first!
8715                 */
8716                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8717                          plylev,
8718                          (gameMode == TwoMachinesPlay ?
8719                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8720                          ((double) curscore) / 100.0,
8721                          prefixHint ? lastHint : "",
8722                          prefixHint ? " " : "" );
8723
8724                 if( buf1[0] != NULLCHAR ) {
8725                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8726
8727                     if( strlen(pv) > max_len ) {
8728                         if( appData.debugMode) {
8729                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8730                         }
8731                         pv[max_len+1] = '\0';
8732                     }
8733
8734                     strcat( thinkOutput, pv);
8735                 }
8736
8737                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8738                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8739                     DisplayMove(currentMove - 1);
8740                 }
8741                 return;
8742
8743             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8744                 /* crafty (9.25+) says "(only move) <move>"
8745                  * if there is only 1 legal move
8746                  */
8747                 sscanf(p, "(only move) %s", buf1);
8748                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8749                 sprintf(programStats.movelist, "%s (only move)", buf1);
8750                 programStats.depth = 1;
8751                 programStats.nr_moves = 1;
8752                 programStats.moves_left = 1;
8753                 programStats.nodes = 1;
8754                 programStats.time = 1;
8755                 programStats.got_only_move = 1;
8756
8757                 /* Not really, but we also use this member to
8758                    mean "line isn't going to change" (Crafty
8759                    isn't searching, so stats won't change) */
8760                 programStats.line_is_book = 1;
8761
8762                 SendProgramStatsToFrontend( cps, &programStats );
8763
8764                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8765                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8766                     DisplayMove(currentMove - 1);
8767                 }
8768                 return;
8769             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8770                               &time, &nodes, &plylev, &mvleft,
8771                               &mvtot, mvname) >= 5) {
8772                 /* The stat01: line is from Crafty (9.29+) in response
8773                    to the "." command */
8774                 programStats.seen_stat = 1;
8775                 cps->maybeThinking = TRUE;
8776
8777                 if (programStats.got_only_move || !appData.periodicUpdates)
8778                   return;
8779
8780                 programStats.depth = plylev;
8781                 programStats.time = time;
8782                 programStats.nodes = nodes;
8783                 programStats.moves_left = mvleft;
8784                 programStats.nr_moves = mvtot;
8785                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8786                 programStats.ok_to_send = 1;
8787                 programStats.movelist[0] = '\0';
8788
8789                 SendProgramStatsToFrontend( cps, &programStats );
8790
8791                 return;
8792
8793             } else if (strncmp(message,"++",2) == 0) {
8794                 /* Crafty 9.29+ outputs this */
8795                 programStats.got_fail = 2;
8796                 return;
8797
8798             } else if (strncmp(message,"--",2) == 0) {
8799                 /* Crafty 9.29+ outputs this */
8800                 programStats.got_fail = 1;
8801                 return;
8802
8803             } else if (thinkOutput[0] != NULLCHAR &&
8804                        strncmp(message, "    ", 4) == 0) {
8805                 unsigned message_len;
8806
8807                 p = message;
8808                 while (*p && *p == ' ') p++;
8809
8810                 message_len = strlen( p );
8811
8812                 /* [AS] Avoid buffer overflow */
8813                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8814                     strcat(thinkOutput, " ");
8815                     strcat(thinkOutput, p);
8816                 }
8817
8818                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8819                     strcat(programStats.movelist, " ");
8820                     strcat(programStats.movelist, p);
8821                 }
8822
8823                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8824                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8825                     DisplayMove(currentMove - 1);
8826                 }
8827                 return;
8828             }
8829         }
8830         else {
8831             buf1[0] = NULLCHAR;
8832
8833             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8834                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8835             {
8836                 ChessProgramStats cpstats;
8837
8838                 if (plyext != ' ' && plyext != '\t') {
8839                     time *= 100;
8840                 }
8841
8842                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8843                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8844                     curscore = -curscore;
8845                 }
8846
8847                 cpstats.depth = plylev;
8848                 cpstats.nodes = nodes;
8849                 cpstats.time = time;
8850                 cpstats.score = curscore;
8851                 cpstats.got_only_move = 0;
8852                 cpstats.movelist[0] = '\0';
8853
8854                 if (buf1[0] != NULLCHAR) {
8855                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8856                 }
8857
8858                 cpstats.ok_to_send = 0;
8859                 cpstats.line_is_book = 0;
8860                 cpstats.nr_moves = 0;
8861                 cpstats.moves_left = 0;
8862
8863                 SendProgramStatsToFrontend( cps, &cpstats );
8864             }
8865         }
8866     }
8867 }
8868
8869
8870 /* Parse a game score from the character string "game", and
8871    record it as the history of the current game.  The game
8872    score is NOT assumed to start from the standard position.
8873    The display is not updated in any way.
8874    */
8875 void
8876 ParseGameHistory(game)
8877      char *game;
8878 {
8879     ChessMove moveType;
8880     int fromX, fromY, toX, toY, boardIndex;
8881     char promoChar;
8882     char *p, *q;
8883     char buf[MSG_SIZ];
8884
8885     if (appData.debugMode)
8886       fprintf(debugFP, "Parsing game history: %s\n", game);
8887
8888     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8889     gameInfo.site = StrSave(appData.icsHost);
8890     gameInfo.date = PGNDate();
8891     gameInfo.round = StrSave("-");
8892
8893     /* Parse out names of players */
8894     while (*game == ' ') game++;
8895     p = buf;
8896     while (*game != ' ') *p++ = *game++;
8897     *p = NULLCHAR;
8898     gameInfo.white = StrSave(buf);
8899     while (*game == ' ') game++;
8900     p = buf;
8901     while (*game != ' ' && *game != '\n') *p++ = *game++;
8902     *p = NULLCHAR;
8903     gameInfo.black = StrSave(buf);
8904
8905     /* Parse moves */
8906     boardIndex = blackPlaysFirst ? 1 : 0;
8907     yynewstr(game);
8908     for (;;) {
8909         yyboardindex = boardIndex;
8910         moveType = (ChessMove) Myylex();
8911         switch (moveType) {
8912           case IllegalMove:             /* maybe suicide chess, etc. */
8913   if (appData.debugMode) {
8914     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8915     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8916     setbuf(debugFP, NULL);
8917   }
8918           case WhitePromotion:
8919           case BlackPromotion:
8920           case WhiteNonPromotion:
8921           case BlackNonPromotion:
8922           case NormalMove:
8923           case WhiteCapturesEnPassant:
8924           case BlackCapturesEnPassant:
8925           case WhiteKingSideCastle:
8926           case WhiteQueenSideCastle:
8927           case BlackKingSideCastle:
8928           case BlackQueenSideCastle:
8929           case WhiteKingSideCastleWild:
8930           case WhiteQueenSideCastleWild:
8931           case BlackKingSideCastleWild:
8932           case BlackQueenSideCastleWild:
8933           /* PUSH Fabien */
8934           case WhiteHSideCastleFR:
8935           case WhiteASideCastleFR:
8936           case BlackHSideCastleFR:
8937           case BlackASideCastleFR:
8938           /* POP Fabien */
8939             fromX = currentMoveString[0] - AAA;
8940             fromY = currentMoveString[1] - ONE;
8941             toX = currentMoveString[2] - AAA;
8942             toY = currentMoveString[3] - ONE;
8943             promoChar = currentMoveString[4];
8944             break;
8945           case WhiteDrop:
8946           case BlackDrop:
8947             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8948             fromX = moveType == WhiteDrop ?
8949               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8950             (int) CharToPiece(ToLower(currentMoveString[0]));
8951             fromY = DROP_RANK;
8952             toX = currentMoveString[2] - AAA;
8953             toY = currentMoveString[3] - ONE;
8954             promoChar = NULLCHAR;
8955             break;
8956           case AmbiguousMove:
8957             /* bug? */
8958             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8959   if (appData.debugMode) {
8960     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8961     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8962     setbuf(debugFP, NULL);
8963   }
8964             DisplayError(buf, 0);
8965             return;
8966           case ImpossibleMove:
8967             /* bug? */
8968             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8969   if (appData.debugMode) {
8970     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8971     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8972     setbuf(debugFP, NULL);
8973   }
8974             DisplayError(buf, 0);
8975             return;
8976           case EndOfFile:
8977             if (boardIndex < backwardMostMove) {
8978                 /* Oops, gap.  How did that happen? */
8979                 DisplayError(_("Gap in move list"), 0);
8980                 return;
8981             }
8982             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8983             if (boardIndex > forwardMostMove) {
8984                 forwardMostMove = boardIndex;
8985             }
8986             return;
8987           case ElapsedTime:
8988             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8989                 strcat(parseList[boardIndex-1], " ");
8990                 strcat(parseList[boardIndex-1], yy_text);
8991             }
8992             continue;
8993           case Comment:
8994           case PGNTag:
8995           case NAG:
8996           default:
8997             /* ignore */
8998             continue;
8999           case WhiteWins:
9000           case BlackWins:
9001           case GameIsDrawn:
9002           case GameUnfinished:
9003             if (gameMode == IcsExamining) {
9004                 if (boardIndex < backwardMostMove) {
9005                     /* Oops, gap.  How did that happen? */
9006                     return;
9007                 }
9008                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9009                 return;
9010             }
9011             gameInfo.result = moveType;
9012             p = strchr(yy_text, '{');
9013             if (p == NULL) p = strchr(yy_text, '(');
9014             if (p == NULL) {
9015                 p = yy_text;
9016                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9017             } else {
9018                 q = strchr(p, *p == '{' ? '}' : ')');
9019                 if (q != NULL) *q = NULLCHAR;
9020                 p++;
9021             }
9022             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9023             gameInfo.resultDetails = StrSave(p);
9024             continue;
9025         }
9026         if (boardIndex >= forwardMostMove &&
9027             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9028             backwardMostMove = blackPlaysFirst ? 1 : 0;
9029             return;
9030         }
9031         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9032                                  fromY, fromX, toY, toX, promoChar,
9033                                  parseList[boardIndex]);
9034         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9035         /* currentMoveString is set as a side-effect of yylex */
9036         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9037         strcat(moveList[boardIndex], "\n");
9038         boardIndex++;
9039         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9040         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9041           case MT_NONE:
9042           case MT_STALEMATE:
9043           default:
9044             break;
9045           case MT_CHECK:
9046             if(gameInfo.variant != VariantShogi)
9047                 strcat(parseList[boardIndex - 1], "+");
9048             break;
9049           case MT_CHECKMATE:
9050           case MT_STAINMATE:
9051             strcat(parseList[boardIndex - 1], "#");
9052             break;
9053         }
9054     }
9055 }
9056
9057
9058 /* Apply a move to the given board  */
9059 void
9060 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9061      int fromX, fromY, toX, toY;
9062      int promoChar;
9063      Board board;
9064 {
9065   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9066   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9067
9068     /* [HGM] compute & store e.p. status and castling rights for new position */
9069     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9070
9071       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9072       oldEP = (signed char)board[EP_STATUS];
9073       board[EP_STATUS] = EP_NONE;
9074
9075   if (fromY == DROP_RANK) {
9076         /* must be first */
9077         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9078             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9079             return;
9080         }
9081         piece = board[toY][toX] = (ChessSquare) fromX;
9082   } else {
9083       int i;
9084
9085       if( board[toY][toX] != EmptySquare )
9086            board[EP_STATUS] = EP_CAPTURE;
9087
9088       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9089            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9090                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9091       } else
9092       if( board[fromY][fromX] == WhitePawn ) {
9093            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9094                board[EP_STATUS] = EP_PAWN_MOVE;
9095            if( toY-fromY==2) {
9096                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9097                         gameInfo.variant != VariantBerolina || toX < fromX)
9098                       board[EP_STATUS] = toX | berolina;
9099                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9100                         gameInfo.variant != VariantBerolina || toX > fromX)
9101                       board[EP_STATUS] = toX;
9102            }
9103       } else
9104       if( board[fromY][fromX] == BlackPawn ) {
9105            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9106                board[EP_STATUS] = EP_PAWN_MOVE;
9107            if( toY-fromY== -2) {
9108                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9109                         gameInfo.variant != VariantBerolina || toX < fromX)
9110                       board[EP_STATUS] = toX | berolina;
9111                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9112                         gameInfo.variant != VariantBerolina || toX > fromX)
9113                       board[EP_STATUS] = toX;
9114            }
9115        }
9116
9117        for(i=0; i<nrCastlingRights; i++) {
9118            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9119               board[CASTLING][i] == toX   && castlingRank[i] == toY
9120              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9121        }
9122
9123      if (fromX == toX && fromY == toY) return;
9124
9125      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9126      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9127      if(gameInfo.variant == VariantKnightmate)
9128          king += (int) WhiteUnicorn - (int) WhiteKing;
9129
9130     /* Code added by Tord: */
9131     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9132     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9133         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9134       board[fromY][fromX] = EmptySquare;
9135       board[toY][toX] = EmptySquare;
9136       if((toX > fromX) != (piece == WhiteRook)) {
9137         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9138       } else {
9139         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9140       }
9141     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9142                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9143       board[fromY][fromX] = EmptySquare;
9144       board[toY][toX] = EmptySquare;
9145       if((toX > fromX) != (piece == BlackRook)) {
9146         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9147       } else {
9148         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9149       }
9150     /* End of code added by Tord */
9151
9152     } else if (board[fromY][fromX] == king
9153         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9154         && toY == fromY && toX > fromX+1) {
9155         board[fromY][fromX] = EmptySquare;
9156         board[toY][toX] = king;
9157         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9158         board[fromY][BOARD_RGHT-1] = EmptySquare;
9159     } else if (board[fromY][fromX] == king
9160         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9161                && toY == fromY && toX < fromX-1) {
9162         board[fromY][fromX] = EmptySquare;
9163         board[toY][toX] = king;
9164         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9165         board[fromY][BOARD_LEFT] = EmptySquare;
9166     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9167                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9168                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9169                ) {
9170         /* white pawn promotion */
9171         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9172         if(gameInfo.variant==VariantBughouse ||
9173            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9174             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9175         board[fromY][fromX] = EmptySquare;
9176     } else if ((fromY >= BOARD_HEIGHT>>1)
9177                && (toX != fromX)
9178                && gameInfo.variant != VariantXiangqi
9179                && gameInfo.variant != VariantBerolina
9180                && (board[fromY][fromX] == WhitePawn)
9181                && (board[toY][toX] == EmptySquare)) {
9182         board[fromY][fromX] = EmptySquare;
9183         board[toY][toX] = WhitePawn;
9184         captured = board[toY - 1][toX];
9185         board[toY - 1][toX] = EmptySquare;
9186     } else if ((fromY == BOARD_HEIGHT-4)
9187                && (toX == fromX)
9188                && gameInfo.variant == VariantBerolina
9189                && (board[fromY][fromX] == WhitePawn)
9190                && (board[toY][toX] == EmptySquare)) {
9191         board[fromY][fromX] = EmptySquare;
9192         board[toY][toX] = WhitePawn;
9193         if(oldEP & EP_BEROLIN_A) {
9194                 captured = board[fromY][fromX-1];
9195                 board[fromY][fromX-1] = EmptySquare;
9196         }else{  captured = board[fromY][fromX+1];
9197                 board[fromY][fromX+1] = EmptySquare;
9198         }
9199     } else if (board[fromY][fromX] == king
9200         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9201                && toY == fromY && toX > fromX+1) {
9202         board[fromY][fromX] = EmptySquare;
9203         board[toY][toX] = king;
9204         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9205         board[fromY][BOARD_RGHT-1] = EmptySquare;
9206     } else if (board[fromY][fromX] == king
9207         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9208                && toY == fromY && toX < fromX-1) {
9209         board[fromY][fromX] = EmptySquare;
9210         board[toY][toX] = king;
9211         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9212         board[fromY][BOARD_LEFT] = EmptySquare;
9213     } else if (fromY == 7 && fromX == 3
9214                && board[fromY][fromX] == BlackKing
9215                && toY == 7 && toX == 5) {
9216         board[fromY][fromX] = EmptySquare;
9217         board[toY][toX] = BlackKing;
9218         board[fromY][7] = EmptySquare;
9219         board[toY][4] = BlackRook;
9220     } else if (fromY == 7 && fromX == 3
9221                && board[fromY][fromX] == BlackKing
9222                && toY == 7 && toX == 1) {
9223         board[fromY][fromX] = EmptySquare;
9224         board[toY][toX] = BlackKing;
9225         board[fromY][0] = EmptySquare;
9226         board[toY][2] = BlackRook;
9227     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9228                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9229                && toY < promoRank && promoChar
9230                ) {
9231         /* black pawn promotion */
9232         board[toY][toX] = CharToPiece(ToLower(promoChar));
9233         if(gameInfo.variant==VariantBughouse ||
9234            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9235             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9236         board[fromY][fromX] = EmptySquare;
9237     } else if ((fromY < BOARD_HEIGHT>>1)
9238                && (toX != fromX)
9239                && gameInfo.variant != VariantXiangqi
9240                && gameInfo.variant != VariantBerolina
9241                && (board[fromY][fromX] == BlackPawn)
9242                && (board[toY][toX] == EmptySquare)) {
9243         board[fromY][fromX] = EmptySquare;
9244         board[toY][toX] = BlackPawn;
9245         captured = board[toY + 1][toX];
9246         board[toY + 1][toX] = EmptySquare;
9247     } else if ((fromY == 3)
9248                && (toX == fromX)
9249                && gameInfo.variant == VariantBerolina
9250                && (board[fromY][fromX] == BlackPawn)
9251                && (board[toY][toX] == EmptySquare)) {
9252         board[fromY][fromX] = EmptySquare;
9253         board[toY][toX] = BlackPawn;
9254         if(oldEP & EP_BEROLIN_A) {
9255                 captured = board[fromY][fromX-1];
9256                 board[fromY][fromX-1] = EmptySquare;
9257         }else{  captured = board[fromY][fromX+1];
9258                 board[fromY][fromX+1] = EmptySquare;
9259         }
9260     } else {
9261         board[toY][toX] = board[fromY][fromX];
9262         board[fromY][fromX] = EmptySquare;
9263     }
9264   }
9265
9266     if (gameInfo.holdingsWidth != 0) {
9267
9268       /* !!A lot more code needs to be written to support holdings  */
9269       /* [HGM] OK, so I have written it. Holdings are stored in the */
9270       /* penultimate board files, so they are automaticlly stored   */
9271       /* in the game history.                                       */
9272       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9273                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9274         /* Delete from holdings, by decreasing count */
9275         /* and erasing image if necessary            */
9276         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9277         if(p < (int) BlackPawn) { /* white drop */
9278              p -= (int)WhitePawn;
9279                  p = PieceToNumber((ChessSquare)p);
9280              if(p >= gameInfo.holdingsSize) p = 0;
9281              if(--board[p][BOARD_WIDTH-2] <= 0)
9282                   board[p][BOARD_WIDTH-1] = EmptySquare;
9283              if((int)board[p][BOARD_WIDTH-2] < 0)
9284                         board[p][BOARD_WIDTH-2] = 0;
9285         } else {                  /* black drop */
9286              p -= (int)BlackPawn;
9287                  p = PieceToNumber((ChessSquare)p);
9288              if(p >= gameInfo.holdingsSize) p = 0;
9289              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9290                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9291              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9292                         board[BOARD_HEIGHT-1-p][1] = 0;
9293         }
9294       }
9295       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9296           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9297         /* [HGM] holdings: Add to holdings, if holdings exist */
9298         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9299                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9300                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9301         }
9302         p = (int) captured;
9303         if (p >= (int) BlackPawn) {
9304           p -= (int)BlackPawn;
9305           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9306                   /* in Shogi restore piece to its original  first */
9307                   captured = (ChessSquare) (DEMOTED captured);
9308                   p = DEMOTED p;
9309           }
9310           p = PieceToNumber((ChessSquare)p);
9311           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9312           board[p][BOARD_WIDTH-2]++;
9313           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9314         } else {
9315           p -= (int)WhitePawn;
9316           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9317                   captured = (ChessSquare) (DEMOTED captured);
9318                   p = DEMOTED p;
9319           }
9320           p = PieceToNumber((ChessSquare)p);
9321           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9322           board[BOARD_HEIGHT-1-p][1]++;
9323           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9324         }
9325       }
9326     } else if (gameInfo.variant == VariantAtomic) {
9327       if (captured != EmptySquare) {
9328         int y, x;
9329         for (y = toY-1; y <= toY+1; y++) {
9330           for (x = toX-1; x <= toX+1; x++) {
9331             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9332                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9333               board[y][x] = EmptySquare;
9334             }
9335           }
9336         }
9337         board[toY][toX] = EmptySquare;
9338       }
9339     }
9340     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9341         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9342     } else
9343     if(promoChar == '+') {
9344         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9345         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9346     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9347         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9348     }
9349     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9350                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9351         // [HGM] superchess: take promotion piece out of holdings
9352         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9353         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9354             if(!--board[k][BOARD_WIDTH-2])
9355                 board[k][BOARD_WIDTH-1] = EmptySquare;
9356         } else {
9357             if(!--board[BOARD_HEIGHT-1-k][1])
9358                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9359         }
9360     }
9361
9362 }
9363
9364 /* Updates forwardMostMove */
9365 void
9366 MakeMove(fromX, fromY, toX, toY, promoChar)
9367      int fromX, fromY, toX, toY;
9368      int promoChar;
9369 {
9370 //    forwardMostMove++; // [HGM] bare: moved downstream
9371
9372     (void) CoordsToAlgebraic(boards[forwardMostMove],
9373                              PosFlags(forwardMostMove),
9374                              fromY, fromX, toY, toX, promoChar,
9375                              parseList[forwardMostMove]);
9376
9377     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9378         int timeLeft; static int lastLoadFlag=0; int king, piece;
9379         piece = boards[forwardMostMove][fromY][fromX];
9380         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9381         if(gameInfo.variant == VariantKnightmate)
9382             king += (int) WhiteUnicorn - (int) WhiteKing;
9383         if(forwardMostMove == 0) {
9384             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9385                 fprintf(serverMoves, "%s;", UserName());
9386             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9387                 fprintf(serverMoves, "%s;", second.tidy);
9388             fprintf(serverMoves, "%s;", first.tidy);
9389             if(gameMode == MachinePlaysWhite)
9390                 fprintf(serverMoves, "%s;", UserName());
9391             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9392                 fprintf(serverMoves, "%s;", second.tidy);
9393         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9394         lastLoadFlag = loadFlag;
9395         // print base move
9396         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9397         // print castling suffix
9398         if( toY == fromY && piece == king ) {
9399             if(toX-fromX > 1)
9400                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9401             if(fromX-toX >1)
9402                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9403         }
9404         // e.p. suffix
9405         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9406              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9407              boards[forwardMostMove][toY][toX] == EmptySquare
9408              && fromX != toX && fromY != toY)
9409                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9410         // promotion suffix
9411         if(promoChar != NULLCHAR)
9412                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9413         if(!loadFlag) {
9414                 char buf[MOVE_LEN*2], *p; int len;
9415             fprintf(serverMoves, "/%d/%d",
9416                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9417             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9418             else                      timeLeft = blackTimeRemaining/1000;
9419             fprintf(serverMoves, "/%d", timeLeft);
9420                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9421                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9422                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9423             fprintf(serverMoves, "/%s", buf);
9424         }
9425         fflush(serverMoves);
9426     }
9427
9428     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9429         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9430       return;
9431     }
9432     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9433     if (commentList[forwardMostMove+1] != NULL) {
9434         free(commentList[forwardMostMove+1]);
9435         commentList[forwardMostMove+1] = NULL;
9436     }
9437     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9438     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9439     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9440     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9441     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9442     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9443     adjustedClock = FALSE;
9444     gameInfo.result = GameUnfinished;
9445     if (gameInfo.resultDetails != NULL) {
9446         free(gameInfo.resultDetails);
9447         gameInfo.resultDetails = NULL;
9448     }
9449     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9450                               moveList[forwardMostMove - 1]);
9451     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9452       case MT_NONE:
9453       case MT_STALEMATE:
9454       default:
9455         break;
9456       case MT_CHECK:
9457         if(gameInfo.variant != VariantShogi)
9458             strcat(parseList[forwardMostMove - 1], "+");
9459         break;
9460       case MT_CHECKMATE:
9461       case MT_STAINMATE:
9462         strcat(parseList[forwardMostMove - 1], "#");
9463         break;
9464     }
9465     if (appData.debugMode) {
9466         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9467     }
9468
9469 }
9470
9471 /* Updates currentMove if not pausing */
9472 void
9473 ShowMove(fromX, fromY, toX, toY)
9474 {
9475     int instant = (gameMode == PlayFromGameFile) ?
9476         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9477     if(appData.noGUI) return;
9478     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9479         if (!instant) {
9480             if (forwardMostMove == currentMove + 1) {
9481                 AnimateMove(boards[forwardMostMove - 1],
9482                             fromX, fromY, toX, toY);
9483             }
9484             if (appData.highlightLastMove) {
9485                 SetHighlights(fromX, fromY, toX, toY);
9486             }
9487         }
9488         currentMove = forwardMostMove;
9489     }
9490
9491     if (instant) return;
9492
9493     DisplayMove(currentMove - 1);
9494     DrawPosition(FALSE, boards[currentMove]);
9495     DisplayBothClocks();
9496     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9497 }
9498
9499 void SendEgtPath(ChessProgramState *cps)
9500 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9501         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9502
9503         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9504
9505         while(*p) {
9506             char c, *q = name+1, *r, *s;
9507
9508             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9509             while(*p && *p != ',') *q++ = *p++;
9510             *q++ = ':'; *q = 0;
9511             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9512                 strcmp(name, ",nalimov:") == 0 ) {
9513                 // take nalimov path from the menu-changeable option first, if it is defined
9514               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9515                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9516             } else
9517             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9518                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9519                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9520                 s = r = StrStr(s, ":") + 1; // beginning of path info
9521                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9522                 c = *r; *r = 0;             // temporarily null-terminate path info
9523                     *--q = 0;               // strip of trailig ':' from name
9524                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9525                 *r = c;
9526                 SendToProgram(buf,cps);     // send egtbpath command for this format
9527             }
9528             if(*p == ',') p++; // read away comma to position for next format name
9529         }
9530 }
9531
9532 void
9533 InitChessProgram(cps, setup)
9534      ChessProgramState *cps;
9535      int setup; /* [HGM] needed to setup FRC opening position */
9536 {
9537     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9538     if (appData.noChessProgram) return;
9539     hintRequested = FALSE;
9540     bookRequested = FALSE;
9541
9542     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9543     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9544     if(cps->memSize) { /* [HGM] memory */
9545       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9546         SendToProgram(buf, cps);
9547     }
9548     SendEgtPath(cps); /* [HGM] EGT */
9549     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9550       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9551         SendToProgram(buf, cps);
9552     }
9553
9554     SendToProgram(cps->initString, cps);
9555     if (gameInfo.variant != VariantNormal &&
9556         gameInfo.variant != VariantLoadable
9557         /* [HGM] also send variant if board size non-standard */
9558         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9559                                             ) {
9560       char *v = VariantName(gameInfo.variant);
9561       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9562         /* [HGM] in protocol 1 we have to assume all variants valid */
9563         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9564         DisplayFatalError(buf, 0, 1);
9565         return;
9566       }
9567
9568       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9569       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9570       if( gameInfo.variant == VariantXiangqi )
9571            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9572       if( gameInfo.variant == VariantShogi )
9573            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9574       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9575            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9576       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9577           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9578            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9579       if( gameInfo.variant == VariantCourier )
9580            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9581       if( gameInfo.variant == VariantSuper )
9582            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9583       if( gameInfo.variant == VariantGreat )
9584            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9585       if( gameInfo.variant == VariantSChess )
9586            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9587       if( gameInfo.variant == VariantGrand )
9588            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9589
9590       if(overruled) {
9591         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9592                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9593            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9594            if(StrStr(cps->variants, b) == NULL) {
9595                // specific sized variant not known, check if general sizing allowed
9596                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9597                    if(StrStr(cps->variants, "boardsize") == NULL) {
9598                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9599                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9600                        DisplayFatalError(buf, 0, 1);
9601                        return;
9602                    }
9603                    /* [HGM] here we really should compare with the maximum supported board size */
9604                }
9605            }
9606       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9607       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9608       SendToProgram(buf, cps);
9609     }
9610     currentlyInitializedVariant = gameInfo.variant;
9611
9612     /* [HGM] send opening position in FRC to first engine */
9613     if(setup) {
9614           SendToProgram("force\n", cps);
9615           SendBoard(cps, 0);
9616           /* engine is now in force mode! Set flag to wake it up after first move. */
9617           setboardSpoiledMachineBlack = 1;
9618     }
9619
9620     if (cps->sendICS) {
9621       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9622       SendToProgram(buf, cps);
9623     }
9624     cps->maybeThinking = FALSE;
9625     cps->offeredDraw = 0;
9626     if (!appData.icsActive) {
9627         SendTimeControl(cps, movesPerSession, timeControl,
9628                         timeIncrement, appData.searchDepth,
9629                         searchTime);
9630     }
9631     if (appData.showThinking
9632         // [HGM] thinking: four options require thinking output to be sent
9633         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9634                                 ) {
9635         SendToProgram("post\n", cps);
9636     }
9637     SendToProgram("hard\n", cps);
9638     if (!appData.ponderNextMove) {
9639         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9640            it without being sure what state we are in first.  "hard"
9641            is not a toggle, so that one is OK.
9642          */
9643         SendToProgram("easy\n", cps);
9644     }
9645     if (cps->usePing) {
9646       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9647       SendToProgram(buf, cps);
9648     }
9649     cps->initDone = TRUE;
9650     ClearEngineOutputPane(cps == &second);
9651 }
9652
9653
9654 void
9655 StartChessProgram(cps)
9656      ChessProgramState *cps;
9657 {
9658     char buf[MSG_SIZ];
9659     int err;
9660
9661     if (appData.noChessProgram) return;
9662     cps->initDone = FALSE;
9663
9664     if (strcmp(cps->host, "localhost") == 0) {
9665         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9666     } else if (*appData.remoteShell == NULLCHAR) {
9667         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9668     } else {
9669         if (*appData.remoteUser == NULLCHAR) {
9670           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9671                     cps->program);
9672         } else {
9673           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9674                     cps->host, appData.remoteUser, cps->program);
9675         }
9676         err = StartChildProcess(buf, "", &cps->pr);
9677     }
9678
9679     if (err != 0) {
9680       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9681         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9682         if(cps != &first) return;
9683         appData.noChessProgram = TRUE;
9684         ThawUI();
9685         SetNCPMode();
9686 //      DisplayFatalError(buf, err, 1);
9687 //      cps->pr = NoProc;
9688 //      cps->isr = NULL;
9689         return;
9690     }
9691
9692     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9693     if (cps->protocolVersion > 1) {
9694       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9695       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9696       cps->comboCnt = 0;  //                and values of combo boxes
9697       SendToProgram(buf, cps);
9698     } else {
9699       SendToProgram("xboard\n", cps);
9700     }
9701 }
9702
9703 void
9704 TwoMachinesEventIfReady P((void))
9705 {
9706   static int curMess = 0;
9707   if (first.lastPing != first.lastPong) {
9708     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9709     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9710     return;
9711   }
9712   if (second.lastPing != second.lastPong) {
9713     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9714     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9715     return;
9716   }
9717   DisplayMessage("", ""); curMess = 0;
9718   ThawUI();
9719   TwoMachinesEvent();
9720 }
9721
9722 char *
9723 MakeName(char *template)
9724 {
9725     time_t clock;
9726     struct tm *tm;
9727     static char buf[MSG_SIZ];
9728     char *p = buf;
9729     int i;
9730
9731     clock = time((time_t *)NULL);
9732     tm = localtime(&clock);
9733
9734     while(*p++ = *template++) if(p[-1] == '%') {
9735         switch(*template++) {
9736           case 0:   *p = 0; return buf;
9737           case 'Y': i = tm->tm_year+1900; break;
9738           case 'y': i = tm->tm_year-100; break;
9739           case 'M': i = tm->tm_mon+1; break;
9740           case 'd': i = tm->tm_mday; break;
9741           case 'h': i = tm->tm_hour; break;
9742           case 'm': i = tm->tm_min; break;
9743           case 's': i = tm->tm_sec; break;
9744           default:  i = 0;
9745         }
9746         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9747     }
9748     return buf;
9749 }
9750
9751 int
9752 CountPlayers(char *p)
9753 {
9754     int n = 0;
9755     while(p = strchr(p, '\n')) p++, n++; // count participants
9756     return n;
9757 }
9758
9759 FILE *
9760 WriteTourneyFile(char *results, FILE *f)
9761 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9762     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9763     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9764         // create a file with tournament description
9765         fprintf(f, "-participants {%s}\n", appData.participants);
9766         fprintf(f, "-seedBase %d\n", appData.seedBase);
9767         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9768         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9769         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9770         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9771         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9772         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9773         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9774         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9775         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9776         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9777         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9778         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9779         if(searchTime > 0)
9780                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9781         else {
9782                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9783                 fprintf(f, "-tc %s\n", appData.timeControl);
9784                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9785         }
9786         fprintf(f, "-results \"%s\"\n", results);
9787     }
9788     return f;
9789 }
9790
9791 #define MAXENGINES 1000
9792 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9793
9794 void Substitute(char *participants, int expunge)
9795 {
9796     int i, changed, changes=0, nPlayers=0;
9797     char *p, *q, *r, buf[MSG_SIZ];
9798     if(participants == NULL) return;
9799     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9800     r = p = participants; q = appData.participants;
9801     while(*p && *p == *q) {
9802         if(*p == '\n') r = p+1, nPlayers++;
9803         p++; q++;
9804     }
9805     if(*p) { // difference
9806         while(*p && *p++ != '\n');
9807         while(*q && *q++ != '\n');
9808       changed = nPlayers;
9809         changes = 1 + (strcmp(p, q) != 0);
9810     }
9811     if(changes == 1) { // a single engine mnemonic was changed
9812         q = r; while(*q) nPlayers += (*q++ == '\n');
9813         p = buf; while(*r && (*p = *r++) != '\n') p++;
9814         *p = NULLCHAR;
9815         NamesToList(firstChessProgramNames, command, mnemonic);
9816         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9817         if(mnemonic[i]) { // The substitute is valid
9818             FILE *f;
9819             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9820                 flock(fileno(f), LOCK_EX);
9821                 ParseArgsFromFile(f);
9822                 fseek(f, 0, SEEK_SET);
9823                 FREE(appData.participants); appData.participants = participants;
9824                 if(expunge) { // erase results of replaced engine
9825                     int len = strlen(appData.results), w, b, dummy;
9826                     for(i=0; i<len; i++) {
9827                         Pairing(i, nPlayers, &w, &b, &dummy);
9828                         if((w == changed || b == changed) && appData.results[i] == '*') {
9829                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9830                             fclose(f);
9831                             return;
9832                         }
9833                     }
9834                     for(i=0; i<len; i++) {
9835                         Pairing(i, nPlayers, &w, &b, &dummy);
9836                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9837                     }
9838                 }
9839                 WriteTourneyFile(appData.results, f);
9840                 fclose(f); // release lock
9841                 return;
9842             }
9843         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9844     }
9845     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9846     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9847     free(participants);
9848     return;
9849 }
9850
9851 int
9852 CreateTourney(char *name)
9853 {
9854         FILE *f;
9855         if(matchMode && strcmp(name, appData.tourneyFile)) {
9856              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9857         }
9858         if(name[0] == NULLCHAR) {
9859             if(appData.participants[0])
9860                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9861             return 0;
9862         }
9863         f = fopen(name, "r");
9864         if(f) { // file exists
9865             ASSIGN(appData.tourneyFile, name);
9866             ParseArgsFromFile(f); // parse it
9867         } else {
9868             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9869             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9870                 DisplayError(_("Not enough participants"), 0);
9871                 return 0;
9872             }
9873             ASSIGN(appData.tourneyFile, name);
9874             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9875             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9876         }
9877         fclose(f);
9878         appData.noChessProgram = FALSE;
9879         appData.clockMode = TRUE;
9880         SetGNUMode();
9881         return 1;
9882 }
9883
9884 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9885 {
9886     char buf[MSG_SIZ], *p, *q;
9887     int i=1;
9888     while(*names) {
9889         p = names; q = buf;
9890         while(*p && *p != '\n') *q++ = *p++;
9891         *q = 0;
9892         if(engineList[i]) free(engineList[i]);
9893         engineList[i] = strdup(buf);
9894         if(*p == '\n') p++;
9895         TidyProgramName(engineList[i], "localhost", buf);
9896         if(engineMnemonic[i]) free(engineMnemonic[i]);
9897         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9898             strcat(buf, " (");
9899             sscanf(q + 8, "%s", buf + strlen(buf));
9900             strcat(buf, ")");
9901         }
9902         engineMnemonic[i] = strdup(buf);
9903         names = p; i++;
9904       if(i > MAXENGINES - 2) break;
9905     }
9906     engineList[i] = engineMnemonic[i] = NULL;
9907 }
9908
9909 // following implemented as macro to avoid type limitations
9910 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9911
9912 void SwapEngines(int n)
9913 {   // swap settings for first engine and other engine (so far only some selected options)
9914     int h;
9915     char *p;
9916     if(n == 0) return;
9917     SWAP(directory, p)
9918     SWAP(chessProgram, p)
9919     SWAP(isUCI, h)
9920     SWAP(hasOwnBookUCI, h)
9921     SWAP(protocolVersion, h)
9922     SWAP(reuse, h)
9923     SWAP(scoreIsAbsolute, h)
9924     SWAP(timeOdds, h)
9925     SWAP(logo, p)
9926     SWAP(pgnName, p)
9927     SWAP(pvSAN, h)
9928     SWAP(engOptions, p)
9929 }
9930
9931 void
9932 SetPlayer(int player)
9933 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9934     int i;
9935     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9936     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9937     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9938     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9939     if(mnemonic[i]) {
9940         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9941         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9942         appData.firstHasOwnBookUCI = !appData.defNoBook;
9943         ParseArgsFromString(buf);
9944     }
9945     free(engineName);
9946 }
9947
9948 int
9949 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9950 {   // determine players from game number
9951     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9952
9953     if(appData.tourneyType == 0) {
9954         roundsPerCycle = (nPlayers - 1) | 1;
9955         pairingsPerRound = nPlayers / 2;
9956     } else if(appData.tourneyType > 0) {
9957         roundsPerCycle = nPlayers - appData.tourneyType;
9958         pairingsPerRound = appData.tourneyType;
9959     }
9960     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9961     gamesPerCycle = gamesPerRound * roundsPerCycle;
9962     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9963     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9964     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9965     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9966     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9967     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9968
9969     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9970     if(appData.roundSync) *syncInterval = gamesPerRound;
9971
9972     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9973
9974     if(appData.tourneyType == 0) {
9975         if(curPairing == (nPlayers-1)/2 ) {
9976             *whitePlayer = curRound;
9977             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9978         } else {
9979             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9980             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9981             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9982             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9983         }
9984     } else if(appData.tourneyType > 0) {
9985         *whitePlayer = curPairing;
9986         *blackPlayer = curRound + appData.tourneyType;
9987     }
9988
9989     // take care of white/black alternation per round. 
9990     // For cycles and games this is already taken care of by default, derived from matchGame!
9991     return curRound & 1;
9992 }
9993
9994 int
9995 NextTourneyGame(int nr, int *swapColors)
9996 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9997     char *p, *q;
9998     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9999     FILE *tf;
10000     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10001     tf = fopen(appData.tourneyFile, "r");
10002     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10003     ParseArgsFromFile(tf); fclose(tf);
10004     InitTimeControls(); // TC might be altered from tourney file
10005
10006     nPlayers = CountPlayers(appData.participants); // count participants
10007     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10008     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10009
10010     if(syncInterval) {
10011         p = q = appData.results;
10012         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10013         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10014             DisplayMessage(_("Waiting for other game(s)"),"");
10015             waitingForGame = TRUE;
10016             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10017             return 0;
10018         }
10019         waitingForGame = FALSE;
10020     }
10021
10022     if(appData.tourneyType < 0) {
10023         if(nr>=0 && !pairingReceived) {
10024             char buf[1<<16];
10025             if(pairing.pr == NoProc) {
10026                 if(!appData.pairingEngine[0]) {
10027                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10028                     return 0;
10029                 }
10030                 StartChessProgram(&pairing); // starts the pairing engine
10031             }
10032             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10033             SendToProgram(buf, &pairing);
10034             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10035             SendToProgram(buf, &pairing);
10036             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10037         }
10038         pairingReceived = 0;                              // ... so we continue here 
10039         *swapColors = 0;
10040         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10041         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10042         matchGame = 1; roundNr = nr / syncInterval + 1;
10043     }
10044
10045     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10046
10047     // redefine engines, engine dir, etc.
10048     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10049     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10050     SwapEngines(1);
10051     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10052     SwapEngines(1);         // and make that valid for second engine by swapping
10053     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10054     InitEngine(&second, 1);
10055     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10056     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10057     return 1;
10058 }
10059
10060 void
10061 NextMatchGame()
10062 {   // performs game initialization that does not invoke engines, and then tries to start the game
10063     int res, firstWhite, swapColors = 0;
10064     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10065     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10066     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10067     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10068     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10069     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10070     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10071     Reset(FALSE, first.pr != NoProc);
10072     res = LoadGameOrPosition(matchGame); // setup game
10073     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10074     if(!res) return; // abort when bad game/pos file
10075     TwoMachinesEvent();
10076 }
10077
10078 void UserAdjudicationEvent( int result )
10079 {
10080     ChessMove gameResult = GameIsDrawn;
10081
10082     if( result > 0 ) {
10083         gameResult = WhiteWins;
10084     }
10085     else if( result < 0 ) {
10086         gameResult = BlackWins;
10087     }
10088
10089     if( gameMode == TwoMachinesPlay ) {
10090         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10091     }
10092 }
10093
10094
10095 // [HGM] save: calculate checksum of game to make games easily identifiable
10096 int StringCheckSum(char *s)
10097 {
10098         int i = 0;
10099         if(s==NULL) return 0;
10100         while(*s) i = i*259 + *s++;
10101         return i;
10102 }
10103
10104 int GameCheckSum()
10105 {
10106         int i, sum=0;
10107         for(i=backwardMostMove; i<forwardMostMove; i++) {
10108                 sum += pvInfoList[i].depth;
10109                 sum += StringCheckSum(parseList[i]);
10110                 sum += StringCheckSum(commentList[i]);
10111                 sum *= 261;
10112         }
10113         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10114         return sum + StringCheckSum(commentList[i]);
10115 } // end of save patch
10116
10117 void
10118 GameEnds(result, resultDetails, whosays)
10119      ChessMove result;
10120      char *resultDetails;
10121      int whosays;
10122 {
10123     GameMode nextGameMode;
10124     int isIcsGame;
10125     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10126
10127     if(endingGame) return; /* [HGM] crash: forbid recursion */
10128     endingGame = 1;
10129     if(twoBoards) { // [HGM] dual: switch back to one board
10130         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10131         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10132     }
10133     if (appData.debugMode) {
10134       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10135               result, resultDetails ? resultDetails : "(null)", whosays);
10136     }
10137
10138     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10139
10140     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10141         /* If we are playing on ICS, the server decides when the
10142            game is over, but the engine can offer to draw, claim
10143            a draw, or resign.
10144          */
10145 #if ZIPPY
10146         if (appData.zippyPlay && first.initDone) {
10147             if (result == GameIsDrawn) {
10148                 /* In case draw still needs to be claimed */
10149                 SendToICS(ics_prefix);
10150                 SendToICS("draw\n");
10151             } else if (StrCaseStr(resultDetails, "resign")) {
10152                 SendToICS(ics_prefix);
10153                 SendToICS("resign\n");
10154             }
10155         }
10156 #endif
10157         endingGame = 0; /* [HGM] crash */
10158         return;
10159     }
10160
10161     /* If we're loading the game from a file, stop */
10162     if (whosays == GE_FILE) {
10163       (void) StopLoadGameTimer();
10164       gameFileFP = NULL;
10165     }
10166
10167     /* Cancel draw offers */
10168     first.offeredDraw = second.offeredDraw = 0;
10169
10170     /* If this is an ICS game, only ICS can really say it's done;
10171        if not, anyone can. */
10172     isIcsGame = (gameMode == IcsPlayingWhite ||
10173                  gameMode == IcsPlayingBlack ||
10174                  gameMode == IcsObserving    ||
10175                  gameMode == IcsExamining);
10176
10177     if (!isIcsGame || whosays == GE_ICS) {
10178         /* OK -- not an ICS game, or ICS said it was done */
10179         StopClocks();
10180         if (!isIcsGame && !appData.noChessProgram)
10181           SetUserThinkingEnables();
10182
10183         /* [HGM] if a machine claims the game end we verify this claim */
10184         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10185             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10186                 char claimer;
10187                 ChessMove trueResult = (ChessMove) -1;
10188
10189                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10190                                             first.twoMachinesColor[0] :
10191                                             second.twoMachinesColor[0] ;
10192
10193                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10194                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10195                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10196                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10197                 } else
10198                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10199                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10200                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10201                 } else
10202                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10203                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10204                 }
10205
10206                 // now verify win claims, but not in drop games, as we don't understand those yet
10207                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10208                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10209                     (result == WhiteWins && claimer == 'w' ||
10210                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10211                       if (appData.debugMode) {
10212                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10213                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10214                       }
10215                       if(result != trueResult) {
10216                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10217                               result = claimer == 'w' ? BlackWins : WhiteWins;
10218                               resultDetails = buf;
10219                       }
10220                 } else
10221                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10222                     && (forwardMostMove <= backwardMostMove ||
10223                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10224                         (claimer=='b')==(forwardMostMove&1))
10225                                                                                   ) {
10226                       /* [HGM] verify: draws that were not flagged are false claims */
10227                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10228                       result = claimer == 'w' ? BlackWins : WhiteWins;
10229                       resultDetails = buf;
10230                 }
10231                 /* (Claiming a loss is accepted no questions asked!) */
10232             }
10233             /* [HGM] bare: don't allow bare King to win */
10234             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10235                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10236                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10237                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10238                && result != GameIsDrawn)
10239             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10240                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10241                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10242                         if(p >= 0 && p <= (int)WhiteKing) k++;
10243                 }
10244                 if (appData.debugMode) {
10245                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10246                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10247                 }
10248                 if(k <= 1) {
10249                         result = GameIsDrawn;
10250                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10251                         resultDetails = buf;
10252                 }
10253             }
10254         }
10255
10256
10257         if(serverMoves != NULL && !loadFlag) { char c = '=';
10258             if(result==WhiteWins) c = '+';
10259             if(result==BlackWins) c = '-';
10260             if(resultDetails != NULL)
10261                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10262         }
10263         if (resultDetails != NULL) {
10264             gameInfo.result = result;
10265             gameInfo.resultDetails = StrSave(resultDetails);
10266
10267             /* display last move only if game was not loaded from file */
10268             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10269                 DisplayMove(currentMove - 1);
10270
10271             if (forwardMostMove != 0) {
10272                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10273                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10274                                                                 ) {
10275                     if (*appData.saveGameFile != NULLCHAR) {
10276                         SaveGameToFile(appData.saveGameFile, TRUE);
10277                     } else if (appData.autoSaveGames) {
10278                         AutoSaveGame();
10279                     }
10280                     if (*appData.savePositionFile != NULLCHAR) {
10281                         SavePositionToFile(appData.savePositionFile);
10282                     }
10283                 }
10284             }
10285
10286             /* Tell program how game ended in case it is learning */
10287             /* [HGM] Moved this to after saving the PGN, just in case */
10288             /* engine died and we got here through time loss. In that */
10289             /* case we will get a fatal error writing the pipe, which */
10290             /* would otherwise lose us the PGN.                       */
10291             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10292             /* output during GameEnds should never be fatal anymore   */
10293             if (gameMode == MachinePlaysWhite ||
10294                 gameMode == MachinePlaysBlack ||
10295                 gameMode == TwoMachinesPlay ||
10296                 gameMode == IcsPlayingWhite ||
10297                 gameMode == IcsPlayingBlack ||
10298                 gameMode == BeginningOfGame) {
10299                 char buf[MSG_SIZ];
10300                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10301                         resultDetails);
10302                 if (first.pr != NoProc) {
10303                     SendToProgram(buf, &first);
10304                 }
10305                 if (second.pr != NoProc &&
10306                     gameMode == TwoMachinesPlay) {
10307                     SendToProgram(buf, &second);
10308                 }
10309             }
10310         }
10311
10312         if (appData.icsActive) {
10313             if (appData.quietPlay &&
10314                 (gameMode == IcsPlayingWhite ||
10315                  gameMode == IcsPlayingBlack)) {
10316                 SendToICS(ics_prefix);
10317                 SendToICS("set shout 1\n");
10318             }
10319             nextGameMode = IcsIdle;
10320             ics_user_moved = FALSE;
10321             /* clean up premove.  It's ugly when the game has ended and the
10322              * premove highlights are still on the board.
10323              */
10324             if (gotPremove) {
10325               gotPremove = FALSE;
10326               ClearPremoveHighlights();
10327               DrawPosition(FALSE, boards[currentMove]);
10328             }
10329             if (whosays == GE_ICS) {
10330                 switch (result) {
10331                 case WhiteWins:
10332                     if (gameMode == IcsPlayingWhite)
10333                         PlayIcsWinSound();
10334                     else if(gameMode == IcsPlayingBlack)
10335                         PlayIcsLossSound();
10336                     break;
10337                 case BlackWins:
10338                     if (gameMode == IcsPlayingBlack)
10339                         PlayIcsWinSound();
10340                     else if(gameMode == IcsPlayingWhite)
10341                         PlayIcsLossSound();
10342                     break;
10343                 case GameIsDrawn:
10344                     PlayIcsDrawSound();
10345                     break;
10346                 default:
10347                     PlayIcsUnfinishedSound();
10348                 }
10349             }
10350         } else if (gameMode == EditGame ||
10351                    gameMode == PlayFromGameFile ||
10352                    gameMode == AnalyzeMode ||
10353                    gameMode == AnalyzeFile) {
10354             nextGameMode = gameMode;
10355         } else {
10356             nextGameMode = EndOfGame;
10357         }
10358         pausing = FALSE;
10359         ModeHighlight();
10360     } else {
10361         nextGameMode = gameMode;
10362     }
10363
10364     if (appData.noChessProgram) {
10365         gameMode = nextGameMode;
10366         ModeHighlight();
10367         endingGame = 0; /* [HGM] crash */
10368         return;
10369     }
10370
10371     if (first.reuse) {
10372         /* Put first chess program into idle state */
10373         if (first.pr != NoProc &&
10374             (gameMode == MachinePlaysWhite ||
10375              gameMode == MachinePlaysBlack ||
10376              gameMode == TwoMachinesPlay ||
10377              gameMode == IcsPlayingWhite ||
10378              gameMode == IcsPlayingBlack ||
10379              gameMode == BeginningOfGame)) {
10380             SendToProgram("force\n", &first);
10381             if (first.usePing) {
10382               char buf[MSG_SIZ];
10383               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10384               SendToProgram(buf, &first);
10385             }
10386         }
10387     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10388         /* Kill off first chess program */
10389         if (first.isr != NULL)
10390           RemoveInputSource(first.isr);
10391         first.isr = NULL;
10392
10393         if (first.pr != NoProc) {
10394             ExitAnalyzeMode();
10395             DoSleep( appData.delayBeforeQuit );
10396             SendToProgram("quit\n", &first);
10397             DoSleep( appData.delayAfterQuit );
10398             DestroyChildProcess(first.pr, first.useSigterm);
10399         }
10400         first.pr = NoProc;
10401     }
10402     if (second.reuse) {
10403         /* Put second chess program into idle state */
10404         if (second.pr != NoProc &&
10405             gameMode == TwoMachinesPlay) {
10406             SendToProgram("force\n", &second);
10407             if (second.usePing) {
10408               char buf[MSG_SIZ];
10409               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10410               SendToProgram(buf, &second);
10411             }
10412         }
10413     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10414         /* Kill off second chess program */
10415         if (second.isr != NULL)
10416           RemoveInputSource(second.isr);
10417         second.isr = NULL;
10418
10419         if (second.pr != NoProc) {
10420             DoSleep( appData.delayBeforeQuit );
10421             SendToProgram("quit\n", &second);
10422             DoSleep( appData.delayAfterQuit );
10423             DestroyChildProcess(second.pr, second.useSigterm);
10424         }
10425         second.pr = NoProc;
10426     }
10427
10428     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10429         char resChar = '=';
10430         switch (result) {
10431         case WhiteWins:
10432           resChar = '+';
10433           if (first.twoMachinesColor[0] == 'w') {
10434             first.matchWins++;
10435           } else {
10436             second.matchWins++;
10437           }
10438           break;
10439         case BlackWins:
10440           resChar = '-';
10441           if (first.twoMachinesColor[0] == 'b') {
10442             first.matchWins++;
10443           } else {
10444             second.matchWins++;
10445           }
10446           break;
10447         case GameUnfinished:
10448           resChar = ' ';
10449         default:
10450           break;
10451         }
10452
10453         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10454         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10455             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10456             ReserveGame(nextGame, resChar); // sets nextGame
10457             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10458             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10459         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10460
10461         if (nextGame <= appData.matchGames && !abortMatch) {
10462             gameMode = nextGameMode;
10463             matchGame = nextGame; // this will be overruled in tourney mode!
10464             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10465             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10466             endingGame = 0; /* [HGM] crash */
10467             return;
10468         } else {
10469             gameMode = nextGameMode;
10470             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10471                      first.tidy, second.tidy,
10472                      first.matchWins, second.matchWins,
10473                      appData.matchGames - (first.matchWins + second.matchWins));
10474             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10475             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10476             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10477                 first.twoMachinesColor = "black\n";
10478                 second.twoMachinesColor = "white\n";
10479             } else {
10480                 first.twoMachinesColor = "white\n";
10481                 second.twoMachinesColor = "black\n";
10482             }
10483         }
10484     }
10485     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10486         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10487       ExitAnalyzeMode();
10488     gameMode = nextGameMode;
10489     ModeHighlight();
10490     endingGame = 0;  /* [HGM] crash */
10491     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10492         if(matchMode == TRUE) { // match through command line: exit with or without popup
10493             if(ranking) {
10494                 ToNrEvent(forwardMostMove);
10495                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10496                 else ExitEvent(0);
10497             } else DisplayFatalError(buf, 0, 0);
10498         } else { // match through menu; just stop, with or without popup
10499             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10500             ModeHighlight();
10501             if(ranking){
10502                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10503             } else DisplayNote(buf);
10504       }
10505       if(ranking) free(ranking);
10506     }
10507 }
10508
10509 /* Assumes program was just initialized (initString sent).
10510    Leaves program in force mode. */
10511 void
10512 FeedMovesToProgram(cps, upto)
10513      ChessProgramState *cps;
10514      int upto;
10515 {
10516     int i;
10517
10518     if (appData.debugMode)
10519       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10520               startedFromSetupPosition ? "position and " : "",
10521               backwardMostMove, upto, cps->which);
10522     if(currentlyInitializedVariant != gameInfo.variant) {
10523       char buf[MSG_SIZ];
10524         // [HGM] variantswitch: make engine aware of new variant
10525         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10526                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10527         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10528         SendToProgram(buf, cps);
10529         currentlyInitializedVariant = gameInfo.variant;
10530     }
10531     SendToProgram("force\n", cps);
10532     if (startedFromSetupPosition) {
10533         SendBoard(cps, backwardMostMove);
10534     if (appData.debugMode) {
10535         fprintf(debugFP, "feedMoves\n");
10536     }
10537     }
10538     for (i = backwardMostMove; i < upto; i++) {
10539         SendMoveToProgram(i, cps);
10540     }
10541 }
10542
10543
10544 int
10545 ResurrectChessProgram()
10546 {
10547      /* The chess program may have exited.
10548         If so, restart it and feed it all the moves made so far. */
10549     static int doInit = 0;
10550
10551     if (appData.noChessProgram) return 1;
10552
10553     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10554         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10555         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10556         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10557     } else {
10558         if (first.pr != NoProc) return 1;
10559         StartChessProgram(&first);
10560     }
10561     InitChessProgram(&first, FALSE);
10562     FeedMovesToProgram(&first, currentMove);
10563
10564     if (!first.sendTime) {
10565         /* can't tell gnuchess what its clock should read,
10566            so we bow to its notion. */
10567         ResetClocks();
10568         timeRemaining[0][currentMove] = whiteTimeRemaining;
10569         timeRemaining[1][currentMove] = blackTimeRemaining;
10570     }
10571
10572     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10573                 appData.icsEngineAnalyze) && first.analysisSupport) {
10574       SendToProgram("analyze\n", &first);
10575       first.analyzing = TRUE;
10576     }
10577     return 1;
10578 }
10579
10580 /*
10581  * Button procedures
10582  */
10583 void
10584 Reset(redraw, init)
10585      int redraw, init;
10586 {
10587     int i;
10588
10589     if (appData.debugMode) {
10590         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10591                 redraw, init, gameMode);
10592     }
10593     CleanupTail(); // [HGM] vari: delete any stored variations
10594     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10595     pausing = pauseExamInvalid = FALSE;
10596     startedFromSetupPosition = blackPlaysFirst = FALSE;
10597     firstMove = TRUE;
10598     whiteFlag = blackFlag = FALSE;
10599     userOfferedDraw = FALSE;
10600     hintRequested = bookRequested = FALSE;
10601     first.maybeThinking = FALSE;
10602     second.maybeThinking = FALSE;
10603     first.bookSuspend = FALSE; // [HGM] book
10604     second.bookSuspend = FALSE;
10605     thinkOutput[0] = NULLCHAR;
10606     lastHint[0] = NULLCHAR;
10607     ClearGameInfo(&gameInfo);
10608     gameInfo.variant = StringToVariant(appData.variant);
10609     ics_user_moved = ics_clock_paused = FALSE;
10610     ics_getting_history = H_FALSE;
10611     ics_gamenum = -1;
10612     white_holding[0] = black_holding[0] = NULLCHAR;
10613     ClearProgramStats();
10614     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10615
10616     ResetFrontEnd();
10617     ClearHighlights();
10618     flipView = appData.flipView;
10619     ClearPremoveHighlights();
10620     gotPremove = FALSE;
10621     alarmSounded = FALSE;
10622
10623     GameEnds(EndOfFile, NULL, GE_PLAYER);
10624     if(appData.serverMovesName != NULL) {
10625         /* [HGM] prepare to make moves file for broadcasting */
10626         clock_t t = clock();
10627         if(serverMoves != NULL) fclose(serverMoves);
10628         serverMoves = fopen(appData.serverMovesName, "r");
10629         if(serverMoves != NULL) {
10630             fclose(serverMoves);
10631             /* delay 15 sec before overwriting, so all clients can see end */
10632             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10633         }
10634         serverMoves = fopen(appData.serverMovesName, "w");
10635     }
10636
10637     ExitAnalyzeMode();
10638     gameMode = BeginningOfGame;
10639     ModeHighlight();
10640     if(appData.icsActive) gameInfo.variant = VariantNormal;
10641     currentMove = forwardMostMove = backwardMostMove = 0;
10642     InitPosition(redraw);
10643     for (i = 0; i < MAX_MOVES; i++) {
10644         if (commentList[i] != NULL) {
10645             free(commentList[i]);
10646             commentList[i] = NULL;
10647         }
10648     }
10649     ResetClocks();
10650     timeRemaining[0][0] = whiteTimeRemaining;
10651     timeRemaining[1][0] = blackTimeRemaining;
10652
10653     if (first.pr == NoProc) {
10654         StartChessProgram(&first);
10655     }
10656     if (init) {
10657             InitChessProgram(&first, startedFromSetupPosition);
10658     }
10659     DisplayTitle("");
10660     DisplayMessage("", "");
10661     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10662     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10663 }
10664
10665 void
10666 AutoPlayGameLoop()
10667 {
10668     for (;;) {
10669         if (!AutoPlayOneMove())
10670           return;
10671         if (matchMode || appData.timeDelay == 0)
10672           continue;
10673         if (appData.timeDelay < 0)
10674           return;
10675         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10676         break;
10677     }
10678 }
10679
10680
10681 int
10682 AutoPlayOneMove()
10683 {
10684     int fromX, fromY, toX, toY;
10685
10686     if (appData.debugMode) {
10687       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10688     }
10689
10690     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10691       return FALSE;
10692
10693     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10694       pvInfoList[currentMove].depth = programStats.depth;
10695       pvInfoList[currentMove].score = programStats.score;
10696       pvInfoList[currentMove].time  = 0;
10697       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10698     }
10699
10700     if (currentMove >= forwardMostMove) {
10701       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10702 //      gameMode = EndOfGame;
10703 //      ModeHighlight();
10704
10705       /* [AS] Clear current move marker at the end of a game */
10706       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10707
10708       return FALSE;
10709     }
10710
10711     toX = moveList[currentMove][2] - AAA;
10712     toY = moveList[currentMove][3] - ONE;
10713
10714     if (moveList[currentMove][1] == '@') {
10715         if (appData.highlightLastMove) {
10716             SetHighlights(-1, -1, toX, toY);
10717         }
10718     } else {
10719         fromX = moveList[currentMove][0] - AAA;
10720         fromY = moveList[currentMove][1] - ONE;
10721
10722         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10723
10724         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10725
10726         if (appData.highlightLastMove) {
10727             SetHighlights(fromX, fromY, toX, toY);
10728         }
10729     }
10730     DisplayMove(currentMove);
10731     SendMoveToProgram(currentMove++, &first);
10732     DisplayBothClocks();
10733     DrawPosition(FALSE, boards[currentMove]);
10734     // [HGM] PV info: always display, routine tests if empty
10735     DisplayComment(currentMove - 1, commentList[currentMove]);
10736     return TRUE;
10737 }
10738
10739
10740 int
10741 LoadGameOneMove(readAhead)
10742      ChessMove readAhead;
10743 {
10744     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10745     char promoChar = NULLCHAR;
10746     ChessMove moveType;
10747     char move[MSG_SIZ];
10748     char *p, *q;
10749
10750     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10751         gameMode != AnalyzeMode && gameMode != Training) {
10752         gameFileFP = NULL;
10753         return FALSE;
10754     }
10755
10756     yyboardindex = forwardMostMove;
10757     if (readAhead != EndOfFile) {
10758       moveType = readAhead;
10759     } else {
10760       if (gameFileFP == NULL)
10761           return FALSE;
10762       moveType = (ChessMove) Myylex();
10763     }
10764
10765     done = FALSE;
10766     switch (moveType) {
10767       case Comment:
10768         if (appData.debugMode)
10769           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10770         p = yy_text;
10771
10772         /* append the comment but don't display it */
10773         AppendComment(currentMove, p, FALSE);
10774         return TRUE;
10775
10776       case WhiteCapturesEnPassant:
10777       case BlackCapturesEnPassant:
10778       case WhitePromotion:
10779       case BlackPromotion:
10780       case WhiteNonPromotion:
10781       case BlackNonPromotion:
10782       case NormalMove:
10783       case WhiteKingSideCastle:
10784       case WhiteQueenSideCastle:
10785       case BlackKingSideCastle:
10786       case BlackQueenSideCastle:
10787       case WhiteKingSideCastleWild:
10788       case WhiteQueenSideCastleWild:
10789       case BlackKingSideCastleWild:
10790       case BlackQueenSideCastleWild:
10791       /* PUSH Fabien */
10792       case WhiteHSideCastleFR:
10793       case WhiteASideCastleFR:
10794       case BlackHSideCastleFR:
10795       case BlackASideCastleFR:
10796       /* POP Fabien */
10797         if (appData.debugMode)
10798           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10799         fromX = currentMoveString[0] - AAA;
10800         fromY = currentMoveString[1] - ONE;
10801         toX = currentMoveString[2] - AAA;
10802         toY = currentMoveString[3] - ONE;
10803         promoChar = currentMoveString[4];
10804         break;
10805
10806       case WhiteDrop:
10807       case BlackDrop:
10808         if (appData.debugMode)
10809           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10810         fromX = moveType == WhiteDrop ?
10811           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10812         (int) CharToPiece(ToLower(currentMoveString[0]));
10813         fromY = DROP_RANK;
10814         toX = currentMoveString[2] - AAA;
10815         toY = currentMoveString[3] - ONE;
10816         break;
10817
10818       case WhiteWins:
10819       case BlackWins:
10820       case GameIsDrawn:
10821       case GameUnfinished:
10822         if (appData.debugMode)
10823           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10824         p = strchr(yy_text, '{');
10825         if (p == NULL) p = strchr(yy_text, '(');
10826         if (p == NULL) {
10827             p = yy_text;
10828             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10829         } else {
10830             q = strchr(p, *p == '{' ? '}' : ')');
10831             if (q != NULL) *q = NULLCHAR;
10832             p++;
10833         }
10834         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10835         GameEnds(moveType, p, GE_FILE);
10836         done = TRUE;
10837         if (cmailMsgLoaded) {
10838             ClearHighlights();
10839             flipView = WhiteOnMove(currentMove);
10840             if (moveType == GameUnfinished) flipView = !flipView;
10841             if (appData.debugMode)
10842               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10843         }
10844         break;
10845
10846       case EndOfFile:
10847         if (appData.debugMode)
10848           fprintf(debugFP, "Parser hit end of file\n");
10849         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10850           case MT_NONE:
10851           case MT_CHECK:
10852             break;
10853           case MT_CHECKMATE:
10854           case MT_STAINMATE:
10855             if (WhiteOnMove(currentMove)) {
10856                 GameEnds(BlackWins, "Black mates", GE_FILE);
10857             } else {
10858                 GameEnds(WhiteWins, "White mates", GE_FILE);
10859             }
10860             break;
10861           case MT_STALEMATE:
10862             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10863             break;
10864         }
10865         done = TRUE;
10866         break;
10867
10868       case MoveNumberOne:
10869         if (lastLoadGameStart == GNUChessGame) {
10870             /* GNUChessGames have numbers, but they aren't move numbers */
10871             if (appData.debugMode)
10872               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10873                       yy_text, (int) moveType);
10874             return LoadGameOneMove(EndOfFile); /* tail recursion */
10875         }
10876         /* else fall thru */
10877
10878       case XBoardGame:
10879       case GNUChessGame:
10880       case PGNTag:
10881         /* Reached start of next game in file */
10882         if (appData.debugMode)
10883           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10884         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10885           case MT_NONE:
10886           case MT_CHECK:
10887             break;
10888           case MT_CHECKMATE:
10889           case MT_STAINMATE:
10890             if (WhiteOnMove(currentMove)) {
10891                 GameEnds(BlackWins, "Black mates", GE_FILE);
10892             } else {
10893                 GameEnds(WhiteWins, "White mates", GE_FILE);
10894             }
10895             break;
10896           case MT_STALEMATE:
10897             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10898             break;
10899         }
10900         done = TRUE;
10901         break;
10902
10903       case PositionDiagram:     /* should not happen; ignore */
10904       case ElapsedTime:         /* ignore */
10905       case NAG:                 /* ignore */
10906         if (appData.debugMode)
10907           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10908                   yy_text, (int) moveType);
10909         return LoadGameOneMove(EndOfFile); /* tail recursion */
10910
10911       case IllegalMove:
10912         if (appData.testLegality) {
10913             if (appData.debugMode)
10914               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10915             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10916                     (forwardMostMove / 2) + 1,
10917                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10918             DisplayError(move, 0);
10919             done = TRUE;
10920         } else {
10921             if (appData.debugMode)
10922               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10923                       yy_text, currentMoveString);
10924             fromX = currentMoveString[0] - AAA;
10925             fromY = currentMoveString[1] - ONE;
10926             toX = currentMoveString[2] - AAA;
10927             toY = currentMoveString[3] - ONE;
10928             promoChar = currentMoveString[4];
10929         }
10930         break;
10931
10932       case AmbiguousMove:
10933         if (appData.debugMode)
10934           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10935         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10936                 (forwardMostMove / 2) + 1,
10937                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10938         DisplayError(move, 0);
10939         done = TRUE;
10940         break;
10941
10942       default:
10943       case ImpossibleMove:
10944         if (appData.debugMode)
10945           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10946         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10947                 (forwardMostMove / 2) + 1,
10948                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10949         DisplayError(move, 0);
10950         done = TRUE;
10951         break;
10952     }
10953
10954     if (done) {
10955         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10956             DrawPosition(FALSE, boards[currentMove]);
10957             DisplayBothClocks();
10958             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10959               DisplayComment(currentMove - 1, commentList[currentMove]);
10960         }
10961         (void) StopLoadGameTimer();
10962         gameFileFP = NULL;
10963         cmailOldMove = forwardMostMove;
10964         return FALSE;
10965     } else {
10966         /* currentMoveString is set as a side-effect of yylex */
10967
10968         thinkOutput[0] = NULLCHAR;
10969         MakeMove(fromX, fromY, toX, toY, promoChar);
10970         currentMove = forwardMostMove;
10971         return TRUE;
10972     }
10973 }
10974
10975 /* Load the nth game from the given file */
10976 int
10977 LoadGameFromFile(filename, n, title, useList)
10978      char *filename;
10979      int n;
10980      char *title;
10981      /*Boolean*/ int useList;
10982 {
10983     FILE *f;
10984     char buf[MSG_SIZ];
10985
10986     if (strcmp(filename, "-") == 0) {
10987         f = stdin;
10988         title = "stdin";
10989     } else {
10990         f = fopen(filename, "rb");
10991         if (f == NULL) {
10992           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10993             DisplayError(buf, errno);
10994             return FALSE;
10995         }
10996     }
10997     if (fseek(f, 0, 0) == -1) {
10998         /* f is not seekable; probably a pipe */
10999         useList = FALSE;
11000     }
11001     if (useList && n == 0) {
11002         int error = GameListBuild(f);
11003         if (error) {
11004             DisplayError(_("Cannot build game list"), error);
11005         } else if (!ListEmpty(&gameList) &&
11006                    ((ListGame *) gameList.tailPred)->number > 1) {
11007             GameListPopUp(f, title);
11008             return TRUE;
11009         }
11010         GameListDestroy();
11011         n = 1;
11012     }
11013     if (n == 0) n = 1;
11014     return LoadGame(f, n, title, FALSE);
11015 }
11016
11017
11018 void
11019 MakeRegisteredMove()
11020 {
11021     int fromX, fromY, toX, toY;
11022     char promoChar;
11023     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11024         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11025           case CMAIL_MOVE:
11026           case CMAIL_DRAW:
11027             if (appData.debugMode)
11028               fprintf(debugFP, "Restoring %s for game %d\n",
11029                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11030
11031             thinkOutput[0] = NULLCHAR;
11032             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11033             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11034             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11035             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11036             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11037             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11038             MakeMove(fromX, fromY, toX, toY, promoChar);
11039             ShowMove(fromX, fromY, toX, toY);
11040
11041             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11042               case MT_NONE:
11043               case MT_CHECK:
11044                 break;
11045
11046               case MT_CHECKMATE:
11047               case MT_STAINMATE:
11048                 if (WhiteOnMove(currentMove)) {
11049                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11050                 } else {
11051                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11052                 }
11053                 break;
11054
11055               case MT_STALEMATE:
11056                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11057                 break;
11058             }
11059
11060             break;
11061
11062           case CMAIL_RESIGN:
11063             if (WhiteOnMove(currentMove)) {
11064                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11065             } else {
11066                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11067             }
11068             break;
11069
11070           case CMAIL_ACCEPT:
11071             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11072             break;
11073
11074           default:
11075             break;
11076         }
11077     }
11078
11079     return;
11080 }
11081
11082 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11083 int
11084 CmailLoadGame(f, gameNumber, title, useList)
11085      FILE *f;
11086      int gameNumber;
11087      char *title;
11088      int useList;
11089 {
11090     int retVal;
11091
11092     if (gameNumber > nCmailGames) {
11093         DisplayError(_("No more games in this message"), 0);
11094         return FALSE;
11095     }
11096     if (f == lastLoadGameFP) {
11097         int offset = gameNumber - lastLoadGameNumber;
11098         if (offset == 0) {
11099             cmailMsg[0] = NULLCHAR;
11100             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11101                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11102                 nCmailMovesRegistered--;
11103             }
11104             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11105             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11106                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11107             }
11108         } else {
11109             if (! RegisterMove()) return FALSE;
11110         }
11111     }
11112
11113     retVal = LoadGame(f, gameNumber, title, useList);
11114
11115     /* Make move registered during previous look at this game, if any */
11116     MakeRegisteredMove();
11117
11118     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11119         commentList[currentMove]
11120           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11121         DisplayComment(currentMove - 1, commentList[currentMove]);
11122     }
11123
11124     return retVal;
11125 }
11126
11127 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11128 int
11129 ReloadGame(offset)
11130      int offset;
11131 {
11132     int gameNumber = lastLoadGameNumber + offset;
11133     if (lastLoadGameFP == NULL) {
11134         DisplayError(_("No game has been loaded yet"), 0);
11135         return FALSE;
11136     }
11137     if (gameNumber <= 0) {
11138         DisplayError(_("Can't back up any further"), 0);
11139         return FALSE;
11140     }
11141     if (cmailMsgLoaded) {
11142         return CmailLoadGame(lastLoadGameFP, gameNumber,
11143                              lastLoadGameTitle, lastLoadGameUseList);
11144     } else {
11145         return LoadGame(lastLoadGameFP, gameNumber,
11146                         lastLoadGameTitle, lastLoadGameUseList);
11147     }
11148 }
11149
11150 int keys[EmptySquare+1];
11151
11152 int
11153 PositionMatches(Board b1, Board b2)
11154 {
11155     int r, f, sum=0;
11156     switch(appData.searchMode) {
11157         case 1: return CompareWithRights(b1, b2);
11158         case 2:
11159             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11160                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11161             }
11162             return TRUE;
11163         case 3:
11164             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11165               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11166                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11167             }
11168             return sum==0;
11169         case 4:
11170             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11171                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11172             }
11173             return sum==0;
11174     }
11175     return TRUE;
11176 }
11177
11178 #define Q_PROMO  4
11179 #define Q_EP     3
11180 #define Q_BCASTL 2
11181 #define Q_WCASTL 1
11182
11183 int pieceList[256], quickBoard[256];
11184 ChessSquare pieceType[256] = { EmptySquare };
11185 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11186 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11187 int soughtTotal, turn;
11188 Boolean epOK, flipSearch;
11189
11190 typedef struct {
11191     unsigned char piece, to;
11192 } Move;
11193
11194 #define DSIZE (250000)
11195
11196 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11197 Move *moveDatabase = initialSpace;
11198 unsigned int movePtr, dataSize = DSIZE;
11199
11200 int MakePieceList(Board board, int *counts)
11201 {
11202     int r, f, n=Q_PROMO, total=0;
11203     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11204     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11205         int sq = f + (r<<4);
11206         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11207             quickBoard[sq] = ++n;
11208             pieceList[n] = sq;
11209             pieceType[n] = board[r][f];
11210             counts[board[r][f]]++;
11211             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11212             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11213             total++;
11214         }
11215     }
11216     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11217     return total;
11218 }
11219
11220 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11221 {
11222     int sq = fromX + (fromY<<4);
11223     int piece = quickBoard[sq];
11224     quickBoard[sq] = 0;
11225     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11226     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11227         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11228         moveDatabase[movePtr++].piece = Q_WCASTL;
11229         quickBoard[sq] = piece;
11230         piece = quickBoard[from]; quickBoard[from] = 0;
11231         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11232     } else
11233     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11234         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11235         moveDatabase[movePtr++].piece = Q_BCASTL;
11236         quickBoard[sq] = piece;
11237         piece = quickBoard[from]; quickBoard[from] = 0;
11238         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11239     } else
11240     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11241         quickBoard[(fromY<<4)+toX] = 0;
11242         moveDatabase[movePtr].piece = Q_EP;
11243         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11244         moveDatabase[movePtr].to = sq;
11245     } else
11246     if(promoPiece != pieceType[piece]) {
11247         moveDatabase[movePtr++].piece = Q_PROMO;
11248         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11249     }
11250     moveDatabase[movePtr].piece = piece;
11251     quickBoard[sq] = piece;
11252     movePtr++;
11253 }
11254
11255 int PackGame(Board board)
11256 {
11257     Move *newSpace = NULL;
11258     moveDatabase[movePtr].piece = 0; // terminate previous game
11259     if(movePtr > dataSize) {
11260         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11261         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11262         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11263         if(newSpace) {
11264             int i;
11265             Move *p = moveDatabase, *q = newSpace;
11266             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11267             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11268             moveDatabase = newSpace;
11269         } else { // calloc failed, we must be out of memory. Too bad...
11270             dataSize = 0; // prevent calloc events for all subsequent games
11271             return 0;     // and signal this one isn't cached
11272         }
11273     }
11274     movePtr++;
11275     MakePieceList(board, counts);
11276     return movePtr;
11277 }
11278
11279 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11280 {   // compare according to search mode
11281     int r, f;
11282     switch(appData.searchMode)
11283     {
11284       case 1: // exact position match
11285         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11286         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11287             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11288         }
11289         break;
11290       case 2: // can have extra material on empty squares
11291         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11292             if(board[r][f] == EmptySquare) continue;
11293             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11294         }
11295         break;
11296       case 3: // material with exact Pawn structure
11297         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11298             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11299             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11300         } // fall through to material comparison
11301       case 4: // exact material
11302         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11303         break;
11304       case 6: // material range with given imbalance
11305         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11306         // fall through to range comparison
11307       case 5: // material range
11308         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11309     }
11310     return TRUE;
11311 }
11312
11313 int QuickScan(Board board, Move *move)
11314 {   // reconstruct game,and compare all positions in it
11315     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11316     do {
11317         int piece = move->piece;
11318         int to = move->to, from = pieceList[piece];
11319         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11320           if(!piece) return -1;
11321           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11322             piece = (++move)->piece;
11323             from = pieceList[piece];
11324             counts[pieceType[piece]]--;
11325             pieceType[piece] = (ChessSquare) move->to;
11326             counts[move->to]++;
11327           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11328             counts[pieceType[quickBoard[to]]]--;
11329             quickBoard[to] = 0; total--;
11330             move++;
11331             continue;
11332           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11333             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11334             from  = pieceList[piece]; // so this must be King
11335             quickBoard[from] = 0;
11336             quickBoard[to] = piece;
11337             pieceList[piece] = to;
11338             move++;
11339             continue;
11340           }
11341         }
11342         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11343         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11344         quickBoard[from] = 0;
11345         quickBoard[to] = piece;
11346         pieceList[piece] = to;
11347         cnt++; turn ^= 3;
11348         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11349            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11350            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11351                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11352           ) {
11353             static int lastCounts[EmptySquare+1];
11354             int i;
11355             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11356             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11357         } else stretch = 0;
11358         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11359         move++;
11360     } while(1);
11361 }
11362
11363 void InitSearch()
11364 {
11365     int r, f;
11366     flipSearch = FALSE;
11367     CopyBoard(soughtBoard, boards[currentMove]);
11368     soughtTotal = MakePieceList(soughtBoard, maxSought);
11369     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11370     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11371     CopyBoard(reverseBoard, boards[currentMove]);
11372     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11373         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11374         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11375         reverseBoard[r][f] = piece;
11376     }
11377     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11378     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11379     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11380                  || (boards[currentMove][CASTLING][2] == NoRights || 
11381                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11382                  && (boards[currentMove][CASTLING][5] == NoRights || 
11383                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11384       ) {
11385         flipSearch = TRUE;
11386         CopyBoard(flipBoard, soughtBoard);
11387         CopyBoard(rotateBoard, reverseBoard);
11388         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11389             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11390             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11391         }
11392     }
11393     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11394     if(appData.searchMode >= 5) {
11395         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11396         MakePieceList(soughtBoard, minSought);
11397         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11398     }
11399     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11400         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11401 }
11402
11403 GameInfo dummyInfo;
11404
11405 int GameContainsPosition(FILE *f, ListGame *lg)
11406 {
11407     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11408     int fromX, fromY, toX, toY;
11409     char promoChar;
11410     static int initDone=FALSE;
11411
11412     // weed out games based on numerical tag comparison
11413     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11414     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11415     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11416     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11417     if(!initDone) {
11418         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11419         initDone = TRUE;
11420     }
11421     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11422     else CopyBoard(boards[scratch], initialPosition); // default start position
11423     if(lg->moves) {
11424         turn = btm + 1;
11425         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11426         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11427     }
11428     if(btm) plyNr++;
11429     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11430     fseek(f, lg->offset, 0);
11431     yynewfile(f);
11432     while(1) {
11433         yyboardindex = scratch;
11434         quickFlag = plyNr+1;
11435         next = Myylex();
11436         quickFlag = 0;
11437         switch(next) {
11438             case PGNTag:
11439                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11440             default:
11441                 continue;
11442
11443             case XBoardGame:
11444             case GNUChessGame:
11445                 if(plyNr) return -1; // after we have seen moves, this is for new game
11446               continue;
11447
11448             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11449             case ImpossibleMove:
11450             case WhiteWins: // game ends here with these four
11451             case BlackWins:
11452             case GameIsDrawn:
11453             case GameUnfinished:
11454                 return -1;
11455
11456             case IllegalMove:
11457                 if(appData.testLegality) return -1;
11458             case WhiteCapturesEnPassant:
11459             case BlackCapturesEnPassant:
11460             case WhitePromotion:
11461             case BlackPromotion:
11462             case WhiteNonPromotion:
11463             case BlackNonPromotion:
11464             case NormalMove:
11465             case WhiteKingSideCastle:
11466             case WhiteQueenSideCastle:
11467             case BlackKingSideCastle:
11468             case BlackQueenSideCastle:
11469             case WhiteKingSideCastleWild:
11470             case WhiteQueenSideCastleWild:
11471             case BlackKingSideCastleWild:
11472             case BlackQueenSideCastleWild:
11473             case WhiteHSideCastleFR:
11474             case WhiteASideCastleFR:
11475             case BlackHSideCastleFR:
11476             case BlackASideCastleFR:
11477                 fromX = currentMoveString[0] - AAA;
11478                 fromY = currentMoveString[1] - ONE;
11479                 toX = currentMoveString[2] - AAA;
11480                 toY = currentMoveString[3] - ONE;
11481                 promoChar = currentMoveString[4];
11482                 break;
11483             case WhiteDrop:
11484             case BlackDrop:
11485                 fromX = next == WhiteDrop ?
11486                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11487                   (int) CharToPiece(ToLower(currentMoveString[0]));
11488                 fromY = DROP_RANK;
11489                 toX = currentMoveString[2] - AAA;
11490                 toY = currentMoveString[3] - ONE;
11491                 promoChar = 0;
11492                 break;
11493         }
11494         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11495         plyNr++;
11496         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11497         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11498         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11499         if(appData.findMirror) {
11500             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11501             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11502         }
11503     }
11504 }
11505
11506 /* Load the nth game from open file f */
11507 int
11508 LoadGame(f, gameNumber, title, useList)
11509      FILE *f;
11510      int gameNumber;
11511      char *title;
11512      int useList;
11513 {
11514     ChessMove cm;
11515     char buf[MSG_SIZ];
11516     int gn = gameNumber;
11517     ListGame *lg = NULL;
11518     int numPGNTags = 0;
11519     int err, pos = -1;
11520     GameMode oldGameMode;
11521     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11522
11523     if (appData.debugMode)
11524         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11525
11526     if (gameMode == Training )
11527         SetTrainingModeOff();
11528
11529     oldGameMode = gameMode;
11530     if (gameMode != BeginningOfGame) {
11531       Reset(FALSE, TRUE);
11532     }
11533
11534     gameFileFP = f;
11535     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11536         fclose(lastLoadGameFP);
11537     }
11538
11539     if (useList) {
11540         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11541
11542         if (lg) {
11543             fseek(f, lg->offset, 0);
11544             GameListHighlight(gameNumber);
11545             pos = lg->position;
11546             gn = 1;
11547         }
11548         else {
11549             DisplayError(_("Game number out of range"), 0);
11550             return FALSE;
11551         }
11552     } else {
11553         GameListDestroy();
11554         if (fseek(f, 0, 0) == -1) {
11555             if (f == lastLoadGameFP ?
11556                 gameNumber == lastLoadGameNumber + 1 :
11557                 gameNumber == 1) {
11558                 gn = 1;
11559             } else {
11560                 DisplayError(_("Can't seek on game file"), 0);
11561                 return FALSE;
11562             }
11563         }
11564     }
11565     lastLoadGameFP = f;
11566     lastLoadGameNumber = gameNumber;
11567     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11568     lastLoadGameUseList = useList;
11569
11570     yynewfile(f);
11571
11572     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11573       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11574                 lg->gameInfo.black);
11575             DisplayTitle(buf);
11576     } else if (*title != NULLCHAR) {
11577         if (gameNumber > 1) {
11578           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11579             DisplayTitle(buf);
11580         } else {
11581             DisplayTitle(title);
11582         }
11583     }
11584
11585     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11586         gameMode = PlayFromGameFile;
11587         ModeHighlight();
11588     }
11589
11590     currentMove = forwardMostMove = backwardMostMove = 0;
11591     CopyBoard(boards[0], initialPosition);
11592     StopClocks();
11593
11594     /*
11595      * Skip the first gn-1 games in the file.
11596      * Also skip over anything that precedes an identifiable
11597      * start of game marker, to avoid being confused by
11598      * garbage at the start of the file.  Currently
11599      * recognized start of game markers are the move number "1",
11600      * the pattern "gnuchess .* game", the pattern
11601      * "^[#;%] [^ ]* game file", and a PGN tag block.
11602      * A game that starts with one of the latter two patterns
11603      * will also have a move number 1, possibly
11604      * following a position diagram.
11605      * 5-4-02: Let's try being more lenient and allowing a game to
11606      * start with an unnumbered move.  Does that break anything?
11607      */
11608     cm = lastLoadGameStart = EndOfFile;
11609     while (gn > 0) {
11610         yyboardindex = forwardMostMove;
11611         cm = (ChessMove) Myylex();
11612         switch (cm) {
11613           case EndOfFile:
11614             if (cmailMsgLoaded) {
11615                 nCmailGames = CMAIL_MAX_GAMES - gn;
11616             } else {
11617                 Reset(TRUE, TRUE);
11618                 DisplayError(_("Game not found in file"), 0);
11619             }
11620             return FALSE;
11621
11622           case GNUChessGame:
11623           case XBoardGame:
11624             gn--;
11625             lastLoadGameStart = cm;
11626             break;
11627
11628           case MoveNumberOne:
11629             switch (lastLoadGameStart) {
11630               case GNUChessGame:
11631               case XBoardGame:
11632               case PGNTag:
11633                 break;
11634               case MoveNumberOne:
11635               case EndOfFile:
11636                 gn--;           /* count this game */
11637                 lastLoadGameStart = cm;
11638                 break;
11639               default:
11640                 /* impossible */
11641                 break;
11642             }
11643             break;
11644
11645           case PGNTag:
11646             switch (lastLoadGameStart) {
11647               case GNUChessGame:
11648               case PGNTag:
11649               case MoveNumberOne:
11650               case EndOfFile:
11651                 gn--;           /* count this game */
11652                 lastLoadGameStart = cm;
11653                 break;
11654               case XBoardGame:
11655                 lastLoadGameStart = cm; /* game counted already */
11656                 break;
11657               default:
11658                 /* impossible */
11659                 break;
11660             }
11661             if (gn > 0) {
11662                 do {
11663                     yyboardindex = forwardMostMove;
11664                     cm = (ChessMove) Myylex();
11665                 } while (cm == PGNTag || cm == Comment);
11666             }
11667             break;
11668
11669           case WhiteWins:
11670           case BlackWins:
11671           case GameIsDrawn:
11672             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11673                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11674                     != CMAIL_OLD_RESULT) {
11675                     nCmailResults ++ ;
11676                     cmailResult[  CMAIL_MAX_GAMES
11677                                 - gn - 1] = CMAIL_OLD_RESULT;
11678                 }
11679             }
11680             break;
11681
11682           case NormalMove:
11683             /* Only a NormalMove can be at the start of a game
11684              * without a position diagram. */
11685             if (lastLoadGameStart == EndOfFile ) {
11686               gn--;
11687               lastLoadGameStart = MoveNumberOne;
11688             }
11689             break;
11690
11691           default:
11692             break;
11693         }
11694     }
11695
11696     if (appData.debugMode)
11697       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11698
11699     if (cm == XBoardGame) {
11700         /* Skip any header junk before position diagram and/or move 1 */
11701         for (;;) {
11702             yyboardindex = forwardMostMove;
11703             cm = (ChessMove) Myylex();
11704
11705             if (cm == EndOfFile ||
11706                 cm == GNUChessGame || cm == XBoardGame) {
11707                 /* Empty game; pretend end-of-file and handle later */
11708                 cm = EndOfFile;
11709                 break;
11710             }
11711
11712             if (cm == MoveNumberOne || cm == PositionDiagram ||
11713                 cm == PGNTag || cm == Comment)
11714               break;
11715         }
11716     } else if (cm == GNUChessGame) {
11717         if (gameInfo.event != NULL) {
11718             free(gameInfo.event);
11719         }
11720         gameInfo.event = StrSave(yy_text);
11721     }
11722
11723     startedFromSetupPosition = FALSE;
11724     while (cm == PGNTag) {
11725         if (appData.debugMode)
11726           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11727         err = ParsePGNTag(yy_text, &gameInfo);
11728         if (!err) numPGNTags++;
11729
11730         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11731         if(gameInfo.variant != oldVariant) {
11732             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11733             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11734             InitPosition(TRUE);
11735             oldVariant = gameInfo.variant;
11736             if (appData.debugMode)
11737               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11738         }
11739
11740
11741         if (gameInfo.fen != NULL) {
11742           Board initial_position;
11743           startedFromSetupPosition = TRUE;
11744           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11745             Reset(TRUE, TRUE);
11746             DisplayError(_("Bad FEN position in file"), 0);
11747             return FALSE;
11748           }
11749           CopyBoard(boards[0], initial_position);
11750           if (blackPlaysFirst) {
11751             currentMove = forwardMostMove = backwardMostMove = 1;
11752             CopyBoard(boards[1], initial_position);
11753             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11754             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11755             timeRemaining[0][1] = whiteTimeRemaining;
11756             timeRemaining[1][1] = blackTimeRemaining;
11757             if (commentList[0] != NULL) {
11758               commentList[1] = commentList[0];
11759               commentList[0] = NULL;
11760             }
11761           } else {
11762             currentMove = forwardMostMove = backwardMostMove = 0;
11763           }
11764           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11765           {   int i;
11766               initialRulePlies = FENrulePlies;
11767               for( i=0; i< nrCastlingRights; i++ )
11768                   initialRights[i] = initial_position[CASTLING][i];
11769           }
11770           yyboardindex = forwardMostMove;
11771           free(gameInfo.fen);
11772           gameInfo.fen = NULL;
11773         }
11774
11775         yyboardindex = forwardMostMove;
11776         cm = (ChessMove) Myylex();
11777
11778         /* Handle comments interspersed among the tags */
11779         while (cm == Comment) {
11780             char *p;
11781             if (appData.debugMode)
11782               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11783             p = yy_text;
11784             AppendComment(currentMove, p, FALSE);
11785             yyboardindex = forwardMostMove;
11786             cm = (ChessMove) Myylex();
11787         }
11788     }
11789
11790     /* don't rely on existence of Event tag since if game was
11791      * pasted from clipboard the Event tag may not exist
11792      */
11793     if (numPGNTags > 0){
11794         char *tags;
11795         if (gameInfo.variant == VariantNormal) {
11796           VariantClass v = StringToVariant(gameInfo.event);
11797           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11798           if(v < VariantShogi) gameInfo.variant = v;
11799         }
11800         if (!matchMode) {
11801           if( appData.autoDisplayTags ) {
11802             tags = PGNTags(&gameInfo);
11803             TagsPopUp(tags, CmailMsg());
11804             free(tags);
11805           }
11806         }
11807     } else {
11808         /* Make something up, but don't display it now */
11809         SetGameInfo();
11810         TagsPopDown();
11811     }
11812
11813     if (cm == PositionDiagram) {
11814         int i, j;
11815         char *p;
11816         Board initial_position;
11817
11818         if (appData.debugMode)
11819           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11820
11821         if (!startedFromSetupPosition) {
11822             p = yy_text;
11823             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11824               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11825                 switch (*p) {
11826                   case '{':
11827                   case '[':
11828                   case '-':
11829                   case ' ':
11830                   case '\t':
11831                   case '\n':
11832                   case '\r':
11833                     break;
11834                   default:
11835                     initial_position[i][j++] = CharToPiece(*p);
11836                     break;
11837                 }
11838             while (*p == ' ' || *p == '\t' ||
11839                    *p == '\n' || *p == '\r') p++;
11840
11841             if (strncmp(p, "black", strlen("black"))==0)
11842               blackPlaysFirst = TRUE;
11843             else
11844               blackPlaysFirst = FALSE;
11845             startedFromSetupPosition = TRUE;
11846
11847             CopyBoard(boards[0], initial_position);
11848             if (blackPlaysFirst) {
11849                 currentMove = forwardMostMove = backwardMostMove = 1;
11850                 CopyBoard(boards[1], initial_position);
11851                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11852                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11853                 timeRemaining[0][1] = whiteTimeRemaining;
11854                 timeRemaining[1][1] = blackTimeRemaining;
11855                 if (commentList[0] != NULL) {
11856                     commentList[1] = commentList[0];
11857                     commentList[0] = NULL;
11858                 }
11859             } else {
11860                 currentMove = forwardMostMove = backwardMostMove = 0;
11861             }
11862         }
11863         yyboardindex = forwardMostMove;
11864         cm = (ChessMove) Myylex();
11865     }
11866
11867     if (first.pr == NoProc) {
11868         StartChessProgram(&first);
11869     }
11870     InitChessProgram(&first, FALSE);
11871     SendToProgram("force\n", &first);
11872     if (startedFromSetupPosition) {
11873         SendBoard(&first, forwardMostMove);
11874     if (appData.debugMode) {
11875         fprintf(debugFP, "Load Game\n");
11876     }
11877         DisplayBothClocks();
11878     }
11879
11880     /* [HGM] server: flag to write setup moves in broadcast file as one */
11881     loadFlag = appData.suppressLoadMoves;
11882
11883     while (cm == Comment) {
11884         char *p;
11885         if (appData.debugMode)
11886           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11887         p = yy_text;
11888         AppendComment(currentMove, p, FALSE);
11889         yyboardindex = forwardMostMove;
11890         cm = (ChessMove) Myylex();
11891     }
11892
11893     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11894         cm == WhiteWins || cm == BlackWins ||
11895         cm == GameIsDrawn || cm == GameUnfinished) {
11896         DisplayMessage("", _("No moves in game"));
11897         if (cmailMsgLoaded) {
11898             if (appData.debugMode)
11899               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11900             ClearHighlights();
11901             flipView = FALSE;
11902         }
11903         DrawPosition(FALSE, boards[currentMove]);
11904         DisplayBothClocks();
11905         gameMode = EditGame;
11906         ModeHighlight();
11907         gameFileFP = NULL;
11908         cmailOldMove = 0;
11909         return TRUE;
11910     }
11911
11912     // [HGM] PV info: routine tests if comment empty
11913     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11914         DisplayComment(currentMove - 1, commentList[currentMove]);
11915     }
11916     if (!matchMode && appData.timeDelay != 0)
11917       DrawPosition(FALSE, boards[currentMove]);
11918
11919     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11920       programStats.ok_to_send = 1;
11921     }
11922
11923     /* if the first token after the PGN tags is a move
11924      * and not move number 1, retrieve it from the parser
11925      */
11926     if (cm != MoveNumberOne)
11927         LoadGameOneMove(cm);
11928
11929     /* load the remaining moves from the file */
11930     while (LoadGameOneMove(EndOfFile)) {
11931       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11932       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11933     }
11934
11935     /* rewind to the start of the game */
11936     currentMove = backwardMostMove;
11937
11938     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11939
11940     if (oldGameMode == AnalyzeFile ||
11941         oldGameMode == AnalyzeMode) {
11942       AnalyzeFileEvent();
11943     }
11944
11945     if (!matchMode && pos >= 0) {
11946         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11947     } else
11948     if (matchMode || appData.timeDelay == 0) {
11949       ToEndEvent();
11950     } else if (appData.timeDelay > 0) {
11951       AutoPlayGameLoop();
11952     }
11953
11954     if (appData.debugMode)
11955         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11956
11957     loadFlag = 0; /* [HGM] true game starts */
11958     return TRUE;
11959 }
11960
11961 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11962 int
11963 ReloadPosition(offset)
11964      int offset;
11965 {
11966     int positionNumber = lastLoadPositionNumber + offset;
11967     if (lastLoadPositionFP == NULL) {
11968         DisplayError(_("No position has been loaded yet"), 0);
11969         return FALSE;
11970     }
11971     if (positionNumber <= 0) {
11972         DisplayError(_("Can't back up any further"), 0);
11973         return FALSE;
11974     }
11975     return LoadPosition(lastLoadPositionFP, positionNumber,
11976                         lastLoadPositionTitle);
11977 }
11978
11979 /* Load the nth position from the given file */
11980 int
11981 LoadPositionFromFile(filename, n, title)
11982      char *filename;
11983      int n;
11984      char *title;
11985 {
11986     FILE *f;
11987     char buf[MSG_SIZ];
11988
11989     if (strcmp(filename, "-") == 0) {
11990         return LoadPosition(stdin, n, "stdin");
11991     } else {
11992         f = fopen(filename, "rb");
11993         if (f == NULL) {
11994             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11995             DisplayError(buf, errno);
11996             return FALSE;
11997         } else {
11998             return LoadPosition(f, n, title);
11999         }
12000     }
12001 }
12002
12003 /* Load the nth position from the given open file, and close it */
12004 int
12005 LoadPosition(f, positionNumber, title)
12006      FILE *f;
12007      int positionNumber;
12008      char *title;
12009 {
12010     char *p, line[MSG_SIZ];
12011     Board initial_position;
12012     int i, j, fenMode, pn;
12013
12014     if (gameMode == Training )
12015         SetTrainingModeOff();
12016
12017     if (gameMode != BeginningOfGame) {
12018         Reset(FALSE, TRUE);
12019     }
12020     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12021         fclose(lastLoadPositionFP);
12022     }
12023     if (positionNumber == 0) positionNumber = 1;
12024     lastLoadPositionFP = f;
12025     lastLoadPositionNumber = positionNumber;
12026     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12027     if (first.pr == NoProc && !appData.noChessProgram) {
12028       StartChessProgram(&first);
12029       InitChessProgram(&first, FALSE);
12030     }
12031     pn = positionNumber;
12032     if (positionNumber < 0) {
12033         /* Negative position number means to seek to that byte offset */
12034         if (fseek(f, -positionNumber, 0) == -1) {
12035             DisplayError(_("Can't seek on position file"), 0);
12036             return FALSE;
12037         };
12038         pn = 1;
12039     } else {
12040         if (fseek(f, 0, 0) == -1) {
12041             if (f == lastLoadPositionFP ?
12042                 positionNumber == lastLoadPositionNumber + 1 :
12043                 positionNumber == 1) {
12044                 pn = 1;
12045             } else {
12046                 DisplayError(_("Can't seek on position file"), 0);
12047                 return FALSE;
12048             }
12049         }
12050     }
12051     /* See if this file is FEN or old-style xboard */
12052     if (fgets(line, MSG_SIZ, f) == NULL) {
12053         DisplayError(_("Position not found in file"), 0);
12054         return FALSE;
12055     }
12056     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12057     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12058
12059     if (pn >= 2) {
12060         if (fenMode || line[0] == '#') pn--;
12061         while (pn > 0) {
12062             /* skip positions before number pn */
12063             if (fgets(line, MSG_SIZ, f) == NULL) {
12064                 Reset(TRUE, TRUE);
12065                 DisplayError(_("Position not found in file"), 0);
12066                 return FALSE;
12067             }
12068             if (fenMode || line[0] == '#') pn--;
12069         }
12070     }
12071
12072     if (fenMode) {
12073         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12074             DisplayError(_("Bad FEN position in file"), 0);
12075             return FALSE;
12076         }
12077     } else {
12078         (void) fgets(line, MSG_SIZ, f);
12079         (void) fgets(line, MSG_SIZ, f);
12080
12081         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12082             (void) fgets(line, MSG_SIZ, f);
12083             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12084                 if (*p == ' ')
12085                   continue;
12086                 initial_position[i][j++] = CharToPiece(*p);
12087             }
12088         }
12089
12090         blackPlaysFirst = FALSE;
12091         if (!feof(f)) {
12092             (void) fgets(line, MSG_SIZ, f);
12093             if (strncmp(line, "black", strlen("black"))==0)
12094               blackPlaysFirst = TRUE;
12095         }
12096     }
12097     startedFromSetupPosition = TRUE;
12098
12099     CopyBoard(boards[0], initial_position);
12100     if (blackPlaysFirst) {
12101         currentMove = forwardMostMove = backwardMostMove = 1;
12102         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12103         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12104         CopyBoard(boards[1], initial_position);
12105         DisplayMessage("", _("Black to play"));
12106     } else {
12107         currentMove = forwardMostMove = backwardMostMove = 0;
12108         DisplayMessage("", _("White to play"));
12109     }
12110     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12111     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12112         SendToProgram("force\n", &first);
12113         SendBoard(&first, forwardMostMove);
12114     }
12115     if (appData.debugMode) {
12116 int i, j;
12117   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12118   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12119         fprintf(debugFP, "Load Position\n");
12120     }
12121
12122     if (positionNumber > 1) {
12123       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12124         DisplayTitle(line);
12125     } else {
12126         DisplayTitle(title);
12127     }
12128     gameMode = EditGame;
12129     ModeHighlight();
12130     ResetClocks();
12131     timeRemaining[0][1] = whiteTimeRemaining;
12132     timeRemaining[1][1] = blackTimeRemaining;
12133     DrawPosition(FALSE, boards[currentMove]);
12134
12135     return TRUE;
12136 }
12137
12138
12139 void
12140 CopyPlayerNameIntoFileName(dest, src)
12141      char **dest, *src;
12142 {
12143     while (*src != NULLCHAR && *src != ',') {
12144         if (*src == ' ') {
12145             *(*dest)++ = '_';
12146             src++;
12147         } else {
12148             *(*dest)++ = *src++;
12149         }
12150     }
12151 }
12152
12153 char *DefaultFileName(ext)
12154      char *ext;
12155 {
12156     static char def[MSG_SIZ];
12157     char *p;
12158
12159     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12160         p = def;
12161         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12162         *p++ = '-';
12163         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12164         *p++ = '.';
12165         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12166     } else {
12167         def[0] = NULLCHAR;
12168     }
12169     return def;
12170 }
12171
12172 /* Save the current game to the given file */
12173 int
12174 SaveGameToFile(filename, append)
12175      char *filename;
12176      int append;
12177 {
12178     FILE *f;
12179     char buf[MSG_SIZ];
12180     int result, i, t,tot=0;
12181
12182     if (strcmp(filename, "-") == 0) {
12183         return SaveGame(stdout, 0, NULL);
12184     } else {
12185         for(i=0; i<10; i++) { // upto 10 tries
12186              f = fopen(filename, append ? "a" : "w");
12187              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12188              if(f || errno != 13) break;
12189              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12190              tot += t;
12191         }
12192         if (f == NULL) {
12193             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12194             DisplayError(buf, errno);
12195             return FALSE;
12196         } else {
12197             safeStrCpy(buf, lastMsg, MSG_SIZ);
12198             DisplayMessage(_("Waiting for access to save file"), "");
12199             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12200             DisplayMessage(_("Saving game"), "");
12201             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
12202             result = SaveGame(f, 0, NULL);
12203             DisplayMessage(buf, "");
12204             return result;
12205         }
12206     }
12207 }
12208
12209 char *
12210 SavePart(str)
12211      char *str;
12212 {
12213     static char buf[MSG_SIZ];
12214     char *p;
12215
12216     p = strchr(str, ' ');
12217     if (p == NULL) return str;
12218     strncpy(buf, str, p - str);
12219     buf[p - str] = NULLCHAR;
12220     return buf;
12221 }
12222
12223 #define PGN_MAX_LINE 75
12224
12225 #define PGN_SIDE_WHITE  0
12226 #define PGN_SIDE_BLACK  1
12227
12228 /* [AS] */
12229 static int FindFirstMoveOutOfBook( int side )
12230 {
12231     int result = -1;
12232
12233     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12234         int index = backwardMostMove;
12235         int has_book_hit = 0;
12236
12237         if( (index % 2) != side ) {
12238             index++;
12239         }
12240
12241         while( index < forwardMostMove ) {
12242             /* Check to see if engine is in book */
12243             int depth = pvInfoList[index].depth;
12244             int score = pvInfoList[index].score;
12245             int in_book = 0;
12246
12247             if( depth <= 2 ) {
12248                 in_book = 1;
12249             }
12250             else if( score == 0 && depth == 63 ) {
12251                 in_book = 1; /* Zappa */
12252             }
12253             else if( score == 2 && depth == 99 ) {
12254                 in_book = 1; /* Abrok */
12255             }
12256
12257             has_book_hit += in_book;
12258
12259             if( ! in_book ) {
12260                 result = index;
12261
12262                 break;
12263             }
12264
12265             index += 2;
12266         }
12267     }
12268
12269     return result;
12270 }
12271
12272 /* [AS] */
12273 void GetOutOfBookInfo( char * buf )
12274 {
12275     int oob[2];
12276     int i;
12277     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12278
12279     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12280     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12281
12282     *buf = '\0';
12283
12284     if( oob[0] >= 0 || oob[1] >= 0 ) {
12285         for( i=0; i<2; i++ ) {
12286             int idx = oob[i];
12287
12288             if( idx >= 0 ) {
12289                 if( i > 0 && oob[0] >= 0 ) {
12290                     strcat( buf, "   " );
12291                 }
12292
12293                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12294                 sprintf( buf+strlen(buf), "%s%.2f",
12295                     pvInfoList[idx].score >= 0 ? "+" : "",
12296                     pvInfoList[idx].score / 100.0 );
12297             }
12298         }
12299     }
12300 }
12301
12302 /* Save game in PGN style and close the file */
12303 int
12304 SaveGamePGN(f)
12305      FILE *f;
12306 {
12307     int i, offset, linelen, newblock;
12308     time_t tm;
12309 //    char *movetext;
12310     char numtext[32];
12311     int movelen, numlen, blank;
12312     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12313
12314     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12315
12316     tm = time((time_t *) NULL);
12317
12318     PrintPGNTags(f, &gameInfo);
12319
12320     if (backwardMostMove > 0 || startedFromSetupPosition) {
12321         char *fen = PositionToFEN(backwardMostMove, NULL);
12322         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12323         fprintf(f, "\n{--------------\n");
12324         PrintPosition(f, backwardMostMove);
12325         fprintf(f, "--------------}\n");
12326         free(fen);
12327     }
12328     else {
12329         /* [AS] Out of book annotation */
12330         if( appData.saveOutOfBookInfo ) {
12331             char buf[64];
12332
12333             GetOutOfBookInfo( buf );
12334
12335             if( buf[0] != '\0' ) {
12336                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12337             }
12338         }
12339
12340         fprintf(f, "\n");
12341     }
12342
12343     i = backwardMostMove;
12344     linelen = 0;
12345     newblock = TRUE;
12346
12347     while (i < forwardMostMove) {
12348         /* Print comments preceding this move */
12349         if (commentList[i] != NULL) {
12350             if (linelen > 0) fprintf(f, "\n");
12351             fprintf(f, "%s", commentList[i]);
12352             linelen = 0;
12353             newblock = TRUE;
12354         }
12355
12356         /* Format move number */
12357         if ((i % 2) == 0)
12358           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12359         else
12360           if (newblock)
12361             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12362           else
12363             numtext[0] = NULLCHAR;
12364
12365         numlen = strlen(numtext);
12366         newblock = FALSE;
12367
12368         /* Print move number */
12369         blank = linelen > 0 && numlen > 0;
12370         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12371             fprintf(f, "\n");
12372             linelen = 0;
12373             blank = 0;
12374         }
12375         if (blank) {
12376             fprintf(f, " ");
12377             linelen++;
12378         }
12379         fprintf(f, "%s", numtext);
12380         linelen += numlen;
12381
12382         /* Get move */
12383         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12384         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12385
12386         /* Print move */
12387         blank = linelen > 0 && movelen > 0;
12388         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12389             fprintf(f, "\n");
12390             linelen = 0;
12391             blank = 0;
12392         }
12393         if (blank) {
12394             fprintf(f, " ");
12395             linelen++;
12396         }
12397         fprintf(f, "%s", move_buffer);
12398         linelen += movelen;
12399
12400         /* [AS] Add PV info if present */
12401         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12402             /* [HGM] add time */
12403             char buf[MSG_SIZ]; int seconds;
12404
12405             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12406
12407             if( seconds <= 0)
12408               buf[0] = 0;
12409             else
12410               if( seconds < 30 )
12411                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12412               else
12413                 {
12414                   seconds = (seconds + 4)/10; // round to full seconds
12415                   if( seconds < 60 )
12416                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12417                   else
12418                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12419                 }
12420
12421             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12422                       pvInfoList[i].score >= 0 ? "+" : "",
12423                       pvInfoList[i].score / 100.0,
12424                       pvInfoList[i].depth,
12425                       buf );
12426
12427             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12428
12429             /* Print score/depth */
12430             blank = linelen > 0 && movelen > 0;
12431             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12432                 fprintf(f, "\n");
12433                 linelen = 0;
12434                 blank = 0;
12435             }
12436             if (blank) {
12437                 fprintf(f, " ");
12438                 linelen++;
12439             }
12440             fprintf(f, "%s", move_buffer);
12441             linelen += movelen;
12442         }
12443
12444         i++;
12445     }
12446
12447     /* Start a new line */
12448     if (linelen > 0) fprintf(f, "\n");
12449
12450     /* Print comments after last move */
12451     if (commentList[i] != NULL) {
12452         fprintf(f, "%s\n", commentList[i]);
12453     }
12454
12455     /* Print result */
12456     if (gameInfo.resultDetails != NULL &&
12457         gameInfo.resultDetails[0] != NULLCHAR) {
12458         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12459                 PGNResult(gameInfo.result));
12460     } else {
12461         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12462     }
12463
12464     fclose(f);
12465     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12466     return TRUE;
12467 }
12468
12469 /* Save game in old style and close the file */
12470 int
12471 SaveGameOldStyle(f)
12472      FILE *f;
12473 {
12474     int i, offset;
12475     time_t tm;
12476
12477     tm = time((time_t *) NULL);
12478
12479     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12480     PrintOpponents(f);
12481
12482     if (backwardMostMove > 0 || startedFromSetupPosition) {
12483         fprintf(f, "\n[--------------\n");
12484         PrintPosition(f, backwardMostMove);
12485         fprintf(f, "--------------]\n");
12486     } else {
12487         fprintf(f, "\n");
12488     }
12489
12490     i = backwardMostMove;
12491     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12492
12493     while (i < forwardMostMove) {
12494         if (commentList[i] != NULL) {
12495             fprintf(f, "[%s]\n", commentList[i]);
12496         }
12497
12498         if ((i % 2) == 1) {
12499             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12500             i++;
12501         } else {
12502             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12503             i++;
12504             if (commentList[i] != NULL) {
12505                 fprintf(f, "\n");
12506                 continue;
12507             }
12508             if (i >= forwardMostMove) {
12509                 fprintf(f, "\n");
12510                 break;
12511             }
12512             fprintf(f, "%s\n", parseList[i]);
12513             i++;
12514         }
12515     }
12516
12517     if (commentList[i] != NULL) {
12518         fprintf(f, "[%s]\n", commentList[i]);
12519     }
12520
12521     /* This isn't really the old style, but it's close enough */
12522     if (gameInfo.resultDetails != NULL &&
12523         gameInfo.resultDetails[0] != NULLCHAR) {
12524         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12525                 gameInfo.resultDetails);
12526     } else {
12527         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12528     }
12529
12530     fclose(f);
12531     return TRUE;
12532 }
12533
12534 /* Save the current game to open file f and close the file */
12535 int
12536 SaveGame(f, dummy, dummy2)
12537      FILE *f;
12538      int dummy;
12539      char *dummy2;
12540 {
12541     if (gameMode == EditPosition) EditPositionDone(TRUE);
12542     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12543     if (appData.oldSaveStyle)
12544       return SaveGameOldStyle(f);
12545     else
12546       return SaveGamePGN(f);
12547 }
12548
12549 /* Save the current position to the given file */
12550 int
12551 SavePositionToFile(filename)
12552      char *filename;
12553 {
12554     FILE *f;
12555     char buf[MSG_SIZ];
12556
12557     if (strcmp(filename, "-") == 0) {
12558         return SavePosition(stdout, 0, NULL);
12559     } else {
12560         f = fopen(filename, "a");
12561         if (f == NULL) {
12562             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12563             DisplayError(buf, errno);
12564             return FALSE;
12565         } else {
12566             safeStrCpy(buf, lastMsg, MSG_SIZ);
12567             DisplayMessage(_("Waiting for access to save file"), "");
12568             flock(fileno(f), LOCK_EX); // [HGM] lock
12569             DisplayMessage(_("Saving position"), "");
12570             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12571             SavePosition(f, 0, NULL);
12572             DisplayMessage(buf, "");
12573             return TRUE;
12574         }
12575     }
12576 }
12577
12578 /* Save the current position to the given open file and close the file */
12579 int
12580 SavePosition(f, dummy, dummy2)
12581      FILE *f;
12582      int dummy;
12583      char *dummy2;
12584 {
12585     time_t tm;
12586     char *fen;
12587
12588     if (gameMode == EditPosition) EditPositionDone(TRUE);
12589     if (appData.oldSaveStyle) {
12590         tm = time((time_t *) NULL);
12591
12592         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12593         PrintOpponents(f);
12594         fprintf(f, "[--------------\n");
12595         PrintPosition(f, currentMove);
12596         fprintf(f, "--------------]\n");
12597     } else {
12598         fen = PositionToFEN(currentMove, NULL);
12599         fprintf(f, "%s\n", fen);
12600         free(fen);
12601     }
12602     fclose(f);
12603     return TRUE;
12604 }
12605
12606 void
12607 ReloadCmailMsgEvent(unregister)
12608      int unregister;
12609 {
12610 #if !WIN32
12611     static char *inFilename = NULL;
12612     static char *outFilename;
12613     int i;
12614     struct stat inbuf, outbuf;
12615     int status;
12616
12617     /* Any registered moves are unregistered if unregister is set, */
12618     /* i.e. invoked by the signal handler */
12619     if (unregister) {
12620         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12621             cmailMoveRegistered[i] = FALSE;
12622             if (cmailCommentList[i] != NULL) {
12623                 free(cmailCommentList[i]);
12624                 cmailCommentList[i] = NULL;
12625             }
12626         }
12627         nCmailMovesRegistered = 0;
12628     }
12629
12630     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12631         cmailResult[i] = CMAIL_NOT_RESULT;
12632     }
12633     nCmailResults = 0;
12634
12635     if (inFilename == NULL) {
12636         /* Because the filenames are static they only get malloced once  */
12637         /* and they never get freed                                      */
12638         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12639         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12640
12641         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12642         sprintf(outFilename, "%s.out", appData.cmailGameName);
12643     }
12644
12645     status = stat(outFilename, &outbuf);
12646     if (status < 0) {
12647         cmailMailedMove = FALSE;
12648     } else {
12649         status = stat(inFilename, &inbuf);
12650         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12651     }
12652
12653     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12654        counts the games, notes how each one terminated, etc.
12655
12656        It would be nice to remove this kludge and instead gather all
12657        the information while building the game list.  (And to keep it
12658        in the game list nodes instead of having a bunch of fixed-size
12659        parallel arrays.)  Note this will require getting each game's
12660        termination from the PGN tags, as the game list builder does
12661        not process the game moves.  --mann
12662        */
12663     cmailMsgLoaded = TRUE;
12664     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12665
12666     /* Load first game in the file or popup game menu */
12667     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12668
12669 #endif /* !WIN32 */
12670     return;
12671 }
12672
12673 int
12674 RegisterMove()
12675 {
12676     FILE *f;
12677     char string[MSG_SIZ];
12678
12679     if (   cmailMailedMove
12680         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12681         return TRUE;            /* Allow free viewing  */
12682     }
12683
12684     /* Unregister move to ensure that we don't leave RegisterMove        */
12685     /* with the move registered when the conditions for registering no   */
12686     /* longer hold                                                       */
12687     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12688         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12689         nCmailMovesRegistered --;
12690
12691         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12692           {
12693               free(cmailCommentList[lastLoadGameNumber - 1]);
12694               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12695           }
12696     }
12697
12698     if (cmailOldMove == -1) {
12699         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12700         return FALSE;
12701     }
12702
12703     if (currentMove > cmailOldMove + 1) {
12704         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12705         return FALSE;
12706     }
12707
12708     if (currentMove < cmailOldMove) {
12709         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12710         return FALSE;
12711     }
12712
12713     if (forwardMostMove > currentMove) {
12714         /* Silently truncate extra moves */
12715         TruncateGame();
12716     }
12717
12718     if (   (currentMove == cmailOldMove + 1)
12719         || (   (currentMove == cmailOldMove)
12720             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12721                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12722         if (gameInfo.result != GameUnfinished) {
12723             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12724         }
12725
12726         if (commentList[currentMove] != NULL) {
12727             cmailCommentList[lastLoadGameNumber - 1]
12728               = StrSave(commentList[currentMove]);
12729         }
12730         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12731
12732         if (appData.debugMode)
12733           fprintf(debugFP, "Saving %s for game %d\n",
12734                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12735
12736         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12737
12738         f = fopen(string, "w");
12739         if (appData.oldSaveStyle) {
12740             SaveGameOldStyle(f); /* also closes the file */
12741
12742             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12743             f = fopen(string, "w");
12744             SavePosition(f, 0, NULL); /* also closes the file */
12745         } else {
12746             fprintf(f, "{--------------\n");
12747             PrintPosition(f, currentMove);
12748             fprintf(f, "--------------}\n\n");
12749
12750             SaveGame(f, 0, NULL); /* also closes the file*/
12751         }
12752
12753         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12754         nCmailMovesRegistered ++;
12755     } else if (nCmailGames == 1) {
12756         DisplayError(_("You have not made a move yet"), 0);
12757         return FALSE;
12758     }
12759
12760     return TRUE;
12761 }
12762
12763 void
12764 MailMoveEvent()
12765 {
12766 #if !WIN32
12767     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12768     FILE *commandOutput;
12769     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12770     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12771     int nBuffers;
12772     int i;
12773     int archived;
12774     char *arcDir;
12775
12776     if (! cmailMsgLoaded) {
12777         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12778         return;
12779     }
12780
12781     if (nCmailGames == nCmailResults) {
12782         DisplayError(_("No unfinished games"), 0);
12783         return;
12784     }
12785
12786 #if CMAIL_PROHIBIT_REMAIL
12787     if (cmailMailedMove) {
12788       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);
12789         DisplayError(msg, 0);
12790         return;
12791     }
12792 #endif
12793
12794     if (! (cmailMailedMove || RegisterMove())) return;
12795
12796     if (   cmailMailedMove
12797         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12798       snprintf(string, MSG_SIZ, partCommandString,
12799                appData.debugMode ? " -v" : "", appData.cmailGameName);
12800         commandOutput = popen(string, "r");
12801
12802         if (commandOutput == NULL) {
12803             DisplayError(_("Failed to invoke cmail"), 0);
12804         } else {
12805             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12806                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12807             }
12808             if (nBuffers > 1) {
12809                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12810                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12811                 nBytes = MSG_SIZ - 1;
12812             } else {
12813                 (void) memcpy(msg, buffer, nBytes);
12814             }
12815             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12816
12817             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12818                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12819
12820                 archived = TRUE;
12821                 for (i = 0; i < nCmailGames; i ++) {
12822                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12823                         archived = FALSE;
12824                     }
12825                 }
12826                 if (   archived
12827                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12828                         != NULL)) {
12829                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12830                            arcDir,
12831                            appData.cmailGameName,
12832                            gameInfo.date);
12833                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12834                     cmailMsgLoaded = FALSE;
12835                 }
12836             }
12837
12838             DisplayInformation(msg);
12839             pclose(commandOutput);
12840         }
12841     } else {
12842         if ((*cmailMsg) != '\0') {
12843             DisplayInformation(cmailMsg);
12844         }
12845     }
12846
12847     return;
12848 #endif /* !WIN32 */
12849 }
12850
12851 char *
12852 CmailMsg()
12853 {
12854 #if WIN32
12855     return NULL;
12856 #else
12857     int  prependComma = 0;
12858     char number[5];
12859     char string[MSG_SIZ];       /* Space for game-list */
12860     int  i;
12861
12862     if (!cmailMsgLoaded) return "";
12863
12864     if (cmailMailedMove) {
12865       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12866     } else {
12867         /* Create a list of games left */
12868       snprintf(string, MSG_SIZ, "[");
12869         for (i = 0; i < nCmailGames; i ++) {
12870             if (! (   cmailMoveRegistered[i]
12871                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12872                 if (prependComma) {
12873                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12874                 } else {
12875                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12876                     prependComma = 1;
12877                 }
12878
12879                 strcat(string, number);
12880             }
12881         }
12882         strcat(string, "]");
12883
12884         if (nCmailMovesRegistered + nCmailResults == 0) {
12885             switch (nCmailGames) {
12886               case 1:
12887                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12888                 break;
12889
12890               case 2:
12891                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12892                 break;
12893
12894               default:
12895                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12896                          nCmailGames);
12897                 break;
12898             }
12899         } else {
12900             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12901               case 1:
12902                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12903                          string);
12904                 break;
12905
12906               case 0:
12907                 if (nCmailResults == nCmailGames) {
12908                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12909                 } else {
12910                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12911                 }
12912                 break;
12913
12914               default:
12915                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12916                          string);
12917             }
12918         }
12919     }
12920     return cmailMsg;
12921 #endif /* WIN32 */
12922 }
12923
12924 void
12925 ResetGameEvent()
12926 {
12927     if (gameMode == Training)
12928       SetTrainingModeOff();
12929
12930     Reset(TRUE, TRUE);
12931     cmailMsgLoaded = FALSE;
12932     if (appData.icsActive) {
12933       SendToICS(ics_prefix);
12934       SendToICS("refresh\n");
12935     }
12936 }
12937
12938 void
12939 ExitEvent(status)
12940      int status;
12941 {
12942     exiting++;
12943     if (exiting > 2) {
12944       /* Give up on clean exit */
12945       exit(status);
12946     }
12947     if (exiting > 1) {
12948       /* Keep trying for clean exit */
12949       return;
12950     }
12951
12952     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12953
12954     if (telnetISR != NULL) {
12955       RemoveInputSource(telnetISR);
12956     }
12957     if (icsPR != NoProc) {
12958       DestroyChildProcess(icsPR, TRUE);
12959     }
12960
12961     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12962     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12963
12964     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12965     /* make sure this other one finishes before killing it!                  */
12966     if(endingGame) { int count = 0;
12967         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12968         while(endingGame && count++ < 10) DoSleep(1);
12969         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12970     }
12971
12972     /* Kill off chess programs */
12973     if (first.pr != NoProc) {
12974         ExitAnalyzeMode();
12975
12976         DoSleep( appData.delayBeforeQuit );
12977         SendToProgram("quit\n", &first);
12978         DoSleep( appData.delayAfterQuit );
12979         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12980     }
12981     if (second.pr != NoProc) {
12982         DoSleep( appData.delayBeforeQuit );
12983         SendToProgram("quit\n", &second);
12984         DoSleep( appData.delayAfterQuit );
12985         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12986     }
12987     if (first.isr != NULL) {
12988         RemoveInputSource(first.isr);
12989     }
12990     if (second.isr != NULL) {
12991         RemoveInputSource(second.isr);
12992     }
12993
12994     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12995     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12996
12997     ShutDownFrontEnd();
12998     exit(status);
12999 }
13000
13001 void
13002 PauseEvent()
13003 {
13004     if (appData.debugMode)
13005         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13006     if (pausing) {
13007         pausing = FALSE;
13008         ModeHighlight();
13009         if (gameMode == MachinePlaysWhite ||
13010             gameMode == MachinePlaysBlack) {
13011             StartClocks();
13012         } else {
13013             DisplayBothClocks();
13014         }
13015         if (gameMode == PlayFromGameFile) {
13016             if (appData.timeDelay >= 0)
13017                 AutoPlayGameLoop();
13018         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13019             Reset(FALSE, TRUE);
13020             SendToICS(ics_prefix);
13021             SendToICS("refresh\n");
13022         } else if (currentMove < forwardMostMove) {
13023             ForwardInner(forwardMostMove);
13024         }
13025         pauseExamInvalid = FALSE;
13026     } else {
13027         switch (gameMode) {
13028           default:
13029             return;
13030           case IcsExamining:
13031             pauseExamForwardMostMove = forwardMostMove;
13032             pauseExamInvalid = FALSE;
13033             /* fall through */
13034           case IcsObserving:
13035           case IcsPlayingWhite:
13036           case IcsPlayingBlack:
13037             pausing = TRUE;
13038             ModeHighlight();
13039             return;
13040           case PlayFromGameFile:
13041             (void) StopLoadGameTimer();
13042             pausing = TRUE;
13043             ModeHighlight();
13044             break;
13045           case BeginningOfGame:
13046             if (appData.icsActive) return;
13047             /* else fall through */
13048           case MachinePlaysWhite:
13049           case MachinePlaysBlack:
13050           case TwoMachinesPlay:
13051             if (forwardMostMove == 0)
13052               return;           /* don't pause if no one has moved */
13053             if ((gameMode == MachinePlaysWhite &&
13054                  !WhiteOnMove(forwardMostMove)) ||
13055                 (gameMode == MachinePlaysBlack &&
13056                  WhiteOnMove(forwardMostMove))) {
13057                 StopClocks();
13058             }
13059             pausing = TRUE;
13060             ModeHighlight();
13061             break;
13062         }
13063     }
13064 }
13065
13066 void
13067 EditCommentEvent()
13068 {
13069     char title[MSG_SIZ];
13070
13071     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13072       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13073     } else {
13074       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13075                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13076                parseList[currentMove - 1]);
13077     }
13078
13079     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13080 }
13081
13082
13083 void
13084 EditTagsEvent()
13085 {
13086     char *tags = PGNTags(&gameInfo);
13087     bookUp = FALSE;
13088     EditTagsPopUp(tags, NULL);
13089     free(tags);
13090 }
13091
13092 void
13093 AnalyzeModeEvent()
13094 {
13095     if (appData.noChessProgram || gameMode == AnalyzeMode)
13096       return;
13097
13098     if (gameMode != AnalyzeFile) {
13099         if (!appData.icsEngineAnalyze) {
13100                EditGameEvent();
13101                if (gameMode != EditGame) return;
13102         }
13103         ResurrectChessProgram();
13104         SendToProgram("analyze\n", &first);
13105         first.analyzing = TRUE;
13106         /*first.maybeThinking = TRUE;*/
13107         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13108         EngineOutputPopUp();
13109     }
13110     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13111     pausing = FALSE;
13112     ModeHighlight();
13113     SetGameInfo();
13114
13115     StartAnalysisClock();
13116     GetTimeMark(&lastNodeCountTime);
13117     lastNodeCount = 0;
13118 }
13119
13120 void
13121 AnalyzeFileEvent()
13122 {
13123     if (appData.noChessProgram || gameMode == AnalyzeFile)
13124       return;
13125
13126     if (gameMode != AnalyzeMode) {
13127         EditGameEvent();
13128         if (gameMode != EditGame) return;
13129         ResurrectChessProgram();
13130         SendToProgram("analyze\n", &first);
13131         first.analyzing = TRUE;
13132         /*first.maybeThinking = TRUE;*/
13133         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13134         EngineOutputPopUp();
13135     }
13136     gameMode = AnalyzeFile;
13137     pausing = FALSE;
13138     ModeHighlight();
13139     SetGameInfo();
13140
13141     StartAnalysisClock();
13142     GetTimeMark(&lastNodeCountTime);
13143     lastNodeCount = 0;
13144     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13145 }
13146
13147 void
13148 MachineWhiteEvent()
13149 {
13150     char buf[MSG_SIZ];
13151     char *bookHit = NULL;
13152
13153     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13154       return;
13155
13156
13157     if (gameMode == PlayFromGameFile ||
13158         gameMode == TwoMachinesPlay  ||
13159         gameMode == Training         ||
13160         gameMode == AnalyzeMode      ||
13161         gameMode == EndOfGame)
13162         EditGameEvent();
13163
13164     if (gameMode == EditPosition)
13165         EditPositionDone(TRUE);
13166
13167     if (!WhiteOnMove(currentMove)) {
13168         DisplayError(_("It is not White's turn"), 0);
13169         return;
13170     }
13171
13172     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13173       ExitAnalyzeMode();
13174
13175     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13176         gameMode == AnalyzeFile)
13177         TruncateGame();
13178
13179     ResurrectChessProgram();    /* in case it isn't running */
13180     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13181         gameMode = MachinePlaysWhite;
13182         ResetClocks();
13183     } else
13184     gameMode = MachinePlaysWhite;
13185     pausing = FALSE;
13186     ModeHighlight();
13187     SetGameInfo();
13188     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13189     DisplayTitle(buf);
13190     if (first.sendName) {
13191       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13192       SendToProgram(buf, &first);
13193     }
13194     if (first.sendTime) {
13195       if (first.useColors) {
13196         SendToProgram("black\n", &first); /*gnu kludge*/
13197       }
13198       SendTimeRemaining(&first, TRUE);
13199     }
13200     if (first.useColors) {
13201       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13202     }
13203     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13204     SetMachineThinkingEnables();
13205     first.maybeThinking = TRUE;
13206     StartClocks();
13207     firstMove = FALSE;
13208
13209     if (appData.autoFlipView && !flipView) {
13210       flipView = !flipView;
13211       DrawPosition(FALSE, NULL);
13212       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13213     }
13214
13215     if(bookHit) { // [HGM] book: simulate book reply
13216         static char bookMove[MSG_SIZ]; // a bit generous?
13217
13218         programStats.nodes = programStats.depth = programStats.time =
13219         programStats.score = programStats.got_only_move = 0;
13220         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13221
13222         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13223         strcat(bookMove, bookHit);
13224         HandleMachineMove(bookMove, &first);
13225     }
13226 }
13227
13228 void
13229 MachineBlackEvent()
13230 {
13231   char buf[MSG_SIZ];
13232   char *bookHit = NULL;
13233
13234     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13235         return;
13236
13237
13238     if (gameMode == PlayFromGameFile ||
13239         gameMode == TwoMachinesPlay  ||
13240         gameMode == Training         ||
13241         gameMode == AnalyzeMode      ||
13242         gameMode == EndOfGame)
13243         EditGameEvent();
13244
13245     if (gameMode == EditPosition)
13246         EditPositionDone(TRUE);
13247
13248     if (WhiteOnMove(currentMove)) {
13249         DisplayError(_("It is not Black's turn"), 0);
13250         return;
13251     }
13252
13253     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13254       ExitAnalyzeMode();
13255
13256     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13257         gameMode == AnalyzeFile)
13258         TruncateGame();
13259
13260     ResurrectChessProgram();    /* in case it isn't running */
13261     gameMode = MachinePlaysBlack;
13262     pausing = FALSE;
13263     ModeHighlight();
13264     SetGameInfo();
13265     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13266     DisplayTitle(buf);
13267     if (first.sendName) {
13268       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13269       SendToProgram(buf, &first);
13270     }
13271     if (first.sendTime) {
13272       if (first.useColors) {
13273         SendToProgram("white\n", &first); /*gnu kludge*/
13274       }
13275       SendTimeRemaining(&first, FALSE);
13276     }
13277     if (first.useColors) {
13278       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13279     }
13280     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13281     SetMachineThinkingEnables();
13282     first.maybeThinking = TRUE;
13283     StartClocks();
13284
13285     if (appData.autoFlipView && flipView) {
13286       flipView = !flipView;
13287       DrawPosition(FALSE, NULL);
13288       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13289     }
13290     if(bookHit) { // [HGM] book: simulate book reply
13291         static char bookMove[MSG_SIZ]; // a bit generous?
13292
13293         programStats.nodes = programStats.depth = programStats.time =
13294         programStats.score = programStats.got_only_move = 0;
13295         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13296
13297         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13298         strcat(bookMove, bookHit);
13299         HandleMachineMove(bookMove, &first);
13300     }
13301 }
13302
13303
13304 void
13305 DisplayTwoMachinesTitle()
13306 {
13307     char buf[MSG_SIZ];
13308     if (appData.matchGames > 0) {
13309         if(appData.tourneyFile[0]) {
13310           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13311                    gameInfo.white, gameInfo.black,
13312                    nextGame+1, appData.matchGames+1,
13313                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13314         } else 
13315         if (first.twoMachinesColor[0] == 'w') {
13316           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13317                    gameInfo.white, gameInfo.black,
13318                    first.matchWins, second.matchWins,
13319                    matchGame - 1 - (first.matchWins + second.matchWins));
13320         } else {
13321           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13322                    gameInfo.white, gameInfo.black,
13323                    second.matchWins, first.matchWins,
13324                    matchGame - 1 - (first.matchWins + second.matchWins));
13325         }
13326     } else {
13327       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13328     }
13329     DisplayTitle(buf);
13330 }
13331
13332 void
13333 SettingsMenuIfReady()
13334 {
13335   if (second.lastPing != second.lastPong) {
13336     DisplayMessage("", _("Waiting for second chess program"));
13337     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13338     return;
13339   }
13340   ThawUI();
13341   DisplayMessage("", "");
13342   SettingsPopUp(&second);
13343 }
13344
13345 int
13346 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13347 {
13348     char buf[MSG_SIZ];
13349     if (cps->pr == NoProc) {
13350         StartChessProgram(cps);
13351         if (cps->protocolVersion == 1) {
13352           retry();
13353         } else {
13354           /* kludge: allow timeout for initial "feature" command */
13355           FreezeUI();
13356           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13357           DisplayMessage("", buf);
13358           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13359         }
13360         return 1;
13361     }
13362     return 0;
13363 }
13364
13365 void
13366 TwoMachinesEvent P((void))
13367 {
13368     int i;
13369     char buf[MSG_SIZ];
13370     ChessProgramState *onmove;
13371     char *bookHit = NULL;
13372     static int stalling = 0;
13373     TimeMark now;
13374     long wait;
13375
13376     if (appData.noChessProgram) return;
13377
13378     switch (gameMode) {
13379       case TwoMachinesPlay:
13380         return;
13381       case MachinePlaysWhite:
13382       case MachinePlaysBlack:
13383         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13384             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13385             return;
13386         }
13387         /* fall through */
13388       case BeginningOfGame:
13389       case PlayFromGameFile:
13390       case EndOfGame:
13391         EditGameEvent();
13392         if (gameMode != EditGame) return;
13393         break;
13394       case EditPosition:
13395         EditPositionDone(TRUE);
13396         break;
13397       case AnalyzeMode:
13398       case AnalyzeFile:
13399         ExitAnalyzeMode();
13400         break;
13401       case EditGame:
13402       default:
13403         break;
13404     }
13405
13406 //    forwardMostMove = currentMove;
13407     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13408
13409     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13410
13411     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13412     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13413       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13414       return;
13415     }
13416     if(!stalling) {
13417       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13418       SendToProgram("force\n", &second);
13419       stalling = 1;
13420       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13421       return;
13422     }
13423     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13424     if(appData.matchPause>10000 || appData.matchPause<10)
13425                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13426     wait = SubtractTimeMarks(&now, &pauseStart);
13427     if(wait < appData.matchPause) {
13428         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13429         return;
13430     }
13431     stalling = 0;
13432     DisplayMessage("", "");
13433     if (startedFromSetupPosition) {
13434         SendBoard(&second, backwardMostMove);
13435     if (appData.debugMode) {
13436         fprintf(debugFP, "Two Machines\n");
13437     }
13438     }
13439     for (i = backwardMostMove; i < forwardMostMove; i++) {
13440         SendMoveToProgram(i, &second);
13441     }
13442
13443     gameMode = TwoMachinesPlay;
13444     pausing = FALSE;
13445     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13446     SetGameInfo();
13447     DisplayTwoMachinesTitle();
13448     firstMove = TRUE;
13449     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13450         onmove = &first;
13451     } else {
13452         onmove = &second;
13453     }
13454     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13455     SendToProgram(first.computerString, &first);
13456     if (first.sendName) {
13457       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13458       SendToProgram(buf, &first);
13459     }
13460     SendToProgram(second.computerString, &second);
13461     if (second.sendName) {
13462       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13463       SendToProgram(buf, &second);
13464     }
13465
13466     ResetClocks();
13467     if (!first.sendTime || !second.sendTime) {
13468         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13469         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13470     }
13471     if (onmove->sendTime) {
13472       if (onmove->useColors) {
13473         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13474       }
13475       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13476     }
13477     if (onmove->useColors) {
13478       SendToProgram(onmove->twoMachinesColor, onmove);
13479     }
13480     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13481 //    SendToProgram("go\n", onmove);
13482     onmove->maybeThinking = TRUE;
13483     SetMachineThinkingEnables();
13484
13485     StartClocks();
13486
13487     if(bookHit) { // [HGM] book: simulate book reply
13488         static char bookMove[MSG_SIZ]; // a bit generous?
13489
13490         programStats.nodes = programStats.depth = programStats.time =
13491         programStats.score = programStats.got_only_move = 0;
13492         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13493
13494         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13495         strcat(bookMove, bookHit);
13496         savedMessage = bookMove; // args for deferred call
13497         savedState = onmove;
13498         ScheduleDelayedEvent(DeferredBookMove, 1);
13499     }
13500 }
13501
13502 void
13503 TrainingEvent()
13504 {
13505     if (gameMode == Training) {
13506       SetTrainingModeOff();
13507       gameMode = PlayFromGameFile;
13508       DisplayMessage("", _("Training mode off"));
13509     } else {
13510       gameMode = Training;
13511       animateTraining = appData.animate;
13512
13513       /* make sure we are not already at the end of the game */
13514       if (currentMove < forwardMostMove) {
13515         SetTrainingModeOn();
13516         DisplayMessage("", _("Training mode on"));
13517       } else {
13518         gameMode = PlayFromGameFile;
13519         DisplayError(_("Already at end of game"), 0);
13520       }
13521     }
13522     ModeHighlight();
13523 }
13524
13525 void
13526 IcsClientEvent()
13527 {
13528     if (!appData.icsActive) return;
13529     switch (gameMode) {
13530       case IcsPlayingWhite:
13531       case IcsPlayingBlack:
13532       case IcsObserving:
13533       case IcsIdle:
13534       case BeginningOfGame:
13535       case IcsExamining:
13536         return;
13537
13538       case EditGame:
13539         break;
13540
13541       case EditPosition:
13542         EditPositionDone(TRUE);
13543         break;
13544
13545       case AnalyzeMode:
13546       case AnalyzeFile:
13547         ExitAnalyzeMode();
13548         break;
13549
13550       default:
13551         EditGameEvent();
13552         break;
13553     }
13554
13555     gameMode = IcsIdle;
13556     ModeHighlight();
13557     return;
13558 }
13559
13560
13561 void
13562 EditGameEvent()
13563 {
13564     int i;
13565
13566     switch (gameMode) {
13567       case Training:
13568         SetTrainingModeOff();
13569         break;
13570       case MachinePlaysWhite:
13571       case MachinePlaysBlack:
13572       case BeginningOfGame:
13573         SendToProgram("force\n", &first);
13574         SetUserThinkingEnables();
13575         break;
13576       case PlayFromGameFile:
13577         (void) StopLoadGameTimer();
13578         if (gameFileFP != NULL) {
13579             gameFileFP = NULL;
13580         }
13581         break;
13582       case EditPosition:
13583         EditPositionDone(TRUE);
13584         break;
13585       case AnalyzeMode:
13586       case AnalyzeFile:
13587         ExitAnalyzeMode();
13588         SendToProgram("force\n", &first);
13589         break;
13590       case TwoMachinesPlay:
13591         GameEnds(EndOfFile, NULL, GE_PLAYER);
13592         ResurrectChessProgram();
13593         SetUserThinkingEnables();
13594         break;
13595       case EndOfGame:
13596         ResurrectChessProgram();
13597         break;
13598       case IcsPlayingBlack:
13599       case IcsPlayingWhite:
13600         DisplayError(_("Warning: You are still playing a game"), 0);
13601         break;
13602       case IcsObserving:
13603         DisplayError(_("Warning: You are still observing a game"), 0);
13604         break;
13605       case IcsExamining:
13606         DisplayError(_("Warning: You are still examining a game"), 0);
13607         break;
13608       case IcsIdle:
13609         break;
13610       case EditGame:
13611       default:
13612         return;
13613     }
13614
13615     pausing = FALSE;
13616     StopClocks();
13617     first.offeredDraw = second.offeredDraw = 0;
13618
13619     if (gameMode == PlayFromGameFile) {
13620         whiteTimeRemaining = timeRemaining[0][currentMove];
13621         blackTimeRemaining = timeRemaining[1][currentMove];
13622         DisplayTitle("");
13623     }
13624
13625     if (gameMode == MachinePlaysWhite ||
13626         gameMode == MachinePlaysBlack ||
13627         gameMode == TwoMachinesPlay ||
13628         gameMode == EndOfGame) {
13629         i = forwardMostMove;
13630         while (i > currentMove) {
13631             SendToProgram("undo\n", &first);
13632             i--;
13633         }
13634         if(!adjustedClock) {
13635         whiteTimeRemaining = timeRemaining[0][currentMove];
13636         blackTimeRemaining = timeRemaining[1][currentMove];
13637         DisplayBothClocks();
13638         }
13639         if (whiteFlag || blackFlag) {
13640             whiteFlag = blackFlag = 0;
13641         }
13642         DisplayTitle("");
13643     }
13644
13645     gameMode = EditGame;
13646     ModeHighlight();
13647     SetGameInfo();
13648 }
13649
13650
13651 void
13652 EditPositionEvent()
13653 {
13654     if (gameMode == EditPosition) {
13655         EditGameEvent();
13656         return;
13657     }
13658
13659     EditGameEvent();
13660     if (gameMode != EditGame) return;
13661
13662     gameMode = EditPosition;
13663     ModeHighlight();
13664     SetGameInfo();
13665     if (currentMove > 0)
13666       CopyBoard(boards[0], boards[currentMove]);
13667
13668     blackPlaysFirst = !WhiteOnMove(currentMove);
13669     ResetClocks();
13670     currentMove = forwardMostMove = backwardMostMove = 0;
13671     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13672     DisplayMove(-1);
13673 }
13674
13675 void
13676 ExitAnalyzeMode()
13677 {
13678     /* [DM] icsEngineAnalyze - possible call from other functions */
13679     if (appData.icsEngineAnalyze) {
13680         appData.icsEngineAnalyze = FALSE;
13681
13682         DisplayMessage("",_("Close ICS engine analyze..."));
13683     }
13684     if (first.analysisSupport && first.analyzing) {
13685       SendToProgram("exit\n", &first);
13686       first.analyzing = FALSE;
13687     }
13688     thinkOutput[0] = NULLCHAR;
13689 }
13690
13691 void
13692 EditPositionDone(Boolean fakeRights)
13693 {
13694     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13695
13696     startedFromSetupPosition = TRUE;
13697     InitChessProgram(&first, FALSE);
13698     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13699       boards[0][EP_STATUS] = EP_NONE;
13700       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13701     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13702         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13703         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13704       } else boards[0][CASTLING][2] = NoRights;
13705     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13706         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13707         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13708       } else boards[0][CASTLING][5] = NoRights;
13709     }
13710     SendToProgram("force\n", &first);
13711     if (blackPlaysFirst) {
13712         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13713         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13714         currentMove = forwardMostMove = backwardMostMove = 1;
13715         CopyBoard(boards[1], boards[0]);
13716     } else {
13717         currentMove = forwardMostMove = backwardMostMove = 0;
13718     }
13719     SendBoard(&first, forwardMostMove);
13720     if (appData.debugMode) {
13721         fprintf(debugFP, "EditPosDone\n");
13722     }
13723     DisplayTitle("");
13724     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13725     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13726     gameMode = EditGame;
13727     ModeHighlight();
13728     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13729     ClearHighlights(); /* [AS] */
13730 }
13731
13732 /* Pause for `ms' milliseconds */
13733 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13734 void
13735 TimeDelay(ms)
13736      long ms;
13737 {
13738     TimeMark m1, m2;
13739
13740     GetTimeMark(&m1);
13741     do {
13742         GetTimeMark(&m2);
13743     } while (SubtractTimeMarks(&m2, &m1) < ms);
13744 }
13745
13746 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13747 void
13748 SendMultiLineToICS(buf)
13749      char *buf;
13750 {
13751     char temp[MSG_SIZ+1], *p;
13752     int len;
13753
13754     len = strlen(buf);
13755     if (len > MSG_SIZ)
13756       len = MSG_SIZ;
13757
13758     strncpy(temp, buf, len);
13759     temp[len] = 0;
13760
13761     p = temp;
13762     while (*p) {
13763         if (*p == '\n' || *p == '\r')
13764           *p = ' ';
13765         ++p;
13766     }
13767
13768     strcat(temp, "\n");
13769     SendToICS(temp);
13770     SendToPlayer(temp, strlen(temp));
13771 }
13772
13773 void
13774 SetWhiteToPlayEvent()
13775 {
13776     if (gameMode == EditPosition) {
13777         blackPlaysFirst = FALSE;
13778         DisplayBothClocks();    /* works because currentMove is 0 */
13779     } else if (gameMode == IcsExamining) {
13780         SendToICS(ics_prefix);
13781         SendToICS("tomove white\n");
13782     }
13783 }
13784
13785 void
13786 SetBlackToPlayEvent()
13787 {
13788     if (gameMode == EditPosition) {
13789         blackPlaysFirst = TRUE;
13790         currentMove = 1;        /* kludge */
13791         DisplayBothClocks();
13792         currentMove = 0;
13793     } else if (gameMode == IcsExamining) {
13794         SendToICS(ics_prefix);
13795         SendToICS("tomove black\n");
13796     }
13797 }
13798
13799 void
13800 EditPositionMenuEvent(selection, x, y)
13801      ChessSquare selection;
13802      int x, y;
13803 {
13804     char buf[MSG_SIZ];
13805     ChessSquare piece = boards[0][y][x];
13806
13807     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13808
13809     switch (selection) {
13810       case ClearBoard:
13811         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13812             SendToICS(ics_prefix);
13813             SendToICS("bsetup clear\n");
13814         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13815             SendToICS(ics_prefix);
13816             SendToICS("clearboard\n");
13817         } else {
13818             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13819                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13820                 for (y = 0; y < BOARD_HEIGHT; y++) {
13821                     if (gameMode == IcsExamining) {
13822                         if (boards[currentMove][y][x] != EmptySquare) {
13823                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13824                                     AAA + x, ONE + y);
13825                             SendToICS(buf);
13826                         }
13827                     } else {
13828                         boards[0][y][x] = p;
13829                     }
13830                 }
13831             }
13832         }
13833         if (gameMode == EditPosition) {
13834             DrawPosition(FALSE, boards[0]);
13835         }
13836         break;
13837
13838       case WhitePlay:
13839         SetWhiteToPlayEvent();
13840         break;
13841
13842       case BlackPlay:
13843         SetBlackToPlayEvent();
13844         break;
13845
13846       case EmptySquare:
13847         if (gameMode == IcsExamining) {
13848             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13849             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13850             SendToICS(buf);
13851         } else {
13852             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13853                 if(x == BOARD_LEFT-2) {
13854                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13855                     boards[0][y][1] = 0;
13856                 } else
13857                 if(x == BOARD_RGHT+1) {
13858                     if(y >= gameInfo.holdingsSize) break;
13859                     boards[0][y][BOARD_WIDTH-2] = 0;
13860                 } else break;
13861             }
13862             boards[0][y][x] = EmptySquare;
13863             DrawPosition(FALSE, boards[0]);
13864         }
13865         break;
13866
13867       case PromotePiece:
13868         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13869            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13870             selection = (ChessSquare) (PROMOTED piece);
13871         } else if(piece == EmptySquare) selection = WhiteSilver;
13872         else selection = (ChessSquare)((int)piece - 1);
13873         goto defaultlabel;
13874
13875       case DemotePiece:
13876         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13877            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13878             selection = (ChessSquare) (DEMOTED piece);
13879         } else if(piece == EmptySquare) selection = BlackSilver;
13880         else selection = (ChessSquare)((int)piece + 1);
13881         goto defaultlabel;
13882
13883       case WhiteQueen:
13884       case BlackQueen:
13885         if(gameInfo.variant == VariantShatranj ||
13886            gameInfo.variant == VariantXiangqi  ||
13887            gameInfo.variant == VariantCourier  ||
13888            gameInfo.variant == VariantMakruk     )
13889             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13890         goto defaultlabel;
13891
13892       case WhiteKing:
13893       case BlackKing:
13894         if(gameInfo.variant == VariantXiangqi)
13895             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13896         if(gameInfo.variant == VariantKnightmate)
13897             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13898       default:
13899         defaultlabel:
13900         if (gameMode == IcsExamining) {
13901             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13902             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13903                      PieceToChar(selection), AAA + x, ONE + y);
13904             SendToICS(buf);
13905         } else {
13906             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13907                 int n;
13908                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13909                     n = PieceToNumber(selection - BlackPawn);
13910                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13911                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13912                     boards[0][BOARD_HEIGHT-1-n][1]++;
13913                 } else
13914                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13915                     n = PieceToNumber(selection);
13916                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13917                     boards[0][n][BOARD_WIDTH-1] = selection;
13918                     boards[0][n][BOARD_WIDTH-2]++;
13919                 }
13920             } else
13921             boards[0][y][x] = selection;
13922             DrawPosition(TRUE, boards[0]);
13923         }
13924         break;
13925     }
13926 }
13927
13928
13929 void
13930 DropMenuEvent(selection, x, y)
13931      ChessSquare selection;
13932      int x, y;
13933 {
13934     ChessMove moveType;
13935
13936     switch (gameMode) {
13937       case IcsPlayingWhite:
13938       case MachinePlaysBlack:
13939         if (!WhiteOnMove(currentMove)) {
13940             DisplayMoveError(_("It is Black's turn"));
13941             return;
13942         }
13943         moveType = WhiteDrop;
13944         break;
13945       case IcsPlayingBlack:
13946       case MachinePlaysWhite:
13947         if (WhiteOnMove(currentMove)) {
13948             DisplayMoveError(_("It is White's turn"));
13949             return;
13950         }
13951         moveType = BlackDrop;
13952         break;
13953       case EditGame:
13954         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13955         break;
13956       default:
13957         return;
13958     }
13959
13960     if (moveType == BlackDrop && selection < BlackPawn) {
13961       selection = (ChessSquare) ((int) selection
13962                                  + (int) BlackPawn - (int) WhitePawn);
13963     }
13964     if (boards[currentMove][y][x] != EmptySquare) {
13965         DisplayMoveError(_("That square is occupied"));
13966         return;
13967     }
13968
13969     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13970 }
13971
13972 void
13973 AcceptEvent()
13974 {
13975     /* Accept a pending offer of any kind from opponent */
13976
13977     if (appData.icsActive) {
13978         SendToICS(ics_prefix);
13979         SendToICS("accept\n");
13980     } else if (cmailMsgLoaded) {
13981         if (currentMove == cmailOldMove &&
13982             commentList[cmailOldMove] != NULL &&
13983             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13984                    "Black offers a draw" : "White offers a draw")) {
13985             TruncateGame();
13986             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13987             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13988         } else {
13989             DisplayError(_("There is no pending offer on this move"), 0);
13990             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13991         }
13992     } else {
13993         /* Not used for offers from chess program */
13994     }
13995 }
13996
13997 void
13998 DeclineEvent()
13999 {
14000     /* Decline a pending offer of any kind from opponent */
14001
14002     if (appData.icsActive) {
14003         SendToICS(ics_prefix);
14004         SendToICS("decline\n");
14005     } else if (cmailMsgLoaded) {
14006         if (currentMove == cmailOldMove &&
14007             commentList[cmailOldMove] != NULL &&
14008             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14009                    "Black offers a draw" : "White offers a draw")) {
14010 #ifdef NOTDEF
14011             AppendComment(cmailOldMove, "Draw declined", TRUE);
14012             DisplayComment(cmailOldMove - 1, "Draw declined");
14013 #endif /*NOTDEF*/
14014         } else {
14015             DisplayError(_("There is no pending offer on this move"), 0);
14016         }
14017     } else {
14018         /* Not used for offers from chess program */
14019     }
14020 }
14021
14022 void
14023 RematchEvent()
14024 {
14025     /* Issue ICS rematch command */
14026     if (appData.icsActive) {
14027         SendToICS(ics_prefix);
14028         SendToICS("rematch\n");
14029     }
14030 }
14031
14032 void
14033 CallFlagEvent()
14034 {
14035     /* Call your opponent's flag (claim a win on time) */
14036     if (appData.icsActive) {
14037         SendToICS(ics_prefix);
14038         SendToICS("flag\n");
14039     } else {
14040         switch (gameMode) {
14041           default:
14042             return;
14043           case MachinePlaysWhite:
14044             if (whiteFlag) {
14045                 if (blackFlag)
14046                   GameEnds(GameIsDrawn, "Both players ran out of time",
14047                            GE_PLAYER);
14048                 else
14049                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14050             } else {
14051                 DisplayError(_("Your opponent is not out of time"), 0);
14052             }
14053             break;
14054           case MachinePlaysBlack:
14055             if (blackFlag) {
14056                 if (whiteFlag)
14057                   GameEnds(GameIsDrawn, "Both players ran out of time",
14058                            GE_PLAYER);
14059                 else
14060                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14061             } else {
14062                 DisplayError(_("Your opponent is not out of time"), 0);
14063             }
14064             break;
14065         }
14066     }
14067 }
14068
14069 void
14070 ClockClick(int which)
14071 {       // [HGM] code moved to back-end from winboard.c
14072         if(which) { // black clock
14073           if (gameMode == EditPosition || gameMode == IcsExamining) {
14074             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14075             SetBlackToPlayEvent();
14076           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14077           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14078           } else if (shiftKey) {
14079             AdjustClock(which, -1);
14080           } else if (gameMode == IcsPlayingWhite ||
14081                      gameMode == MachinePlaysBlack) {
14082             CallFlagEvent();
14083           }
14084         } else { // white clock
14085           if (gameMode == EditPosition || gameMode == IcsExamining) {
14086             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14087             SetWhiteToPlayEvent();
14088           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14089           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14090           } else if (shiftKey) {
14091             AdjustClock(which, -1);
14092           } else if (gameMode == IcsPlayingBlack ||
14093                    gameMode == MachinePlaysWhite) {
14094             CallFlagEvent();
14095           }
14096         }
14097 }
14098
14099 void
14100 DrawEvent()
14101 {
14102     /* Offer draw or accept pending draw offer from opponent */
14103
14104     if (appData.icsActive) {
14105         /* Note: tournament rules require draw offers to be
14106            made after you make your move but before you punch
14107            your clock.  Currently ICS doesn't let you do that;
14108            instead, you immediately punch your clock after making
14109            a move, but you can offer a draw at any time. */
14110
14111         SendToICS(ics_prefix);
14112         SendToICS("draw\n");
14113         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14114     } else if (cmailMsgLoaded) {
14115         if (currentMove == cmailOldMove &&
14116             commentList[cmailOldMove] != NULL &&
14117             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14118                    "Black offers a draw" : "White offers a draw")) {
14119             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14120             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14121         } else if (currentMove == cmailOldMove + 1) {
14122             char *offer = WhiteOnMove(cmailOldMove) ?
14123               "White offers a draw" : "Black offers a draw";
14124             AppendComment(currentMove, offer, TRUE);
14125             DisplayComment(currentMove - 1, offer);
14126             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14127         } else {
14128             DisplayError(_("You must make your move before offering a draw"), 0);
14129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14130         }
14131     } else if (first.offeredDraw) {
14132         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14133     } else {
14134         if (first.sendDrawOffers) {
14135             SendToProgram("draw\n", &first);
14136             userOfferedDraw = TRUE;
14137         }
14138     }
14139 }
14140
14141 void
14142 AdjournEvent()
14143 {
14144     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14145
14146     if (appData.icsActive) {
14147         SendToICS(ics_prefix);
14148         SendToICS("adjourn\n");
14149     } else {
14150         /* Currently GNU Chess doesn't offer or accept Adjourns */
14151     }
14152 }
14153
14154
14155 void
14156 AbortEvent()
14157 {
14158     /* Offer Abort or accept pending Abort offer from opponent */
14159
14160     if (appData.icsActive) {
14161         SendToICS(ics_prefix);
14162         SendToICS("abort\n");
14163     } else {
14164         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14165     }
14166 }
14167
14168 void
14169 ResignEvent()
14170 {
14171     /* Resign.  You can do this even if it's not your turn. */
14172
14173     if (appData.icsActive) {
14174         SendToICS(ics_prefix);
14175         SendToICS("resign\n");
14176     } else {
14177         switch (gameMode) {
14178           case MachinePlaysWhite:
14179             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14180             break;
14181           case MachinePlaysBlack:
14182             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14183             break;
14184           case EditGame:
14185             if (cmailMsgLoaded) {
14186                 TruncateGame();
14187                 if (WhiteOnMove(cmailOldMove)) {
14188                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14189                 } else {
14190                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14191                 }
14192                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14193             }
14194             break;
14195           default:
14196             break;
14197         }
14198     }
14199 }
14200
14201
14202 void
14203 StopObservingEvent()
14204 {
14205     /* Stop observing current games */
14206     SendToICS(ics_prefix);
14207     SendToICS("unobserve\n");
14208 }
14209
14210 void
14211 StopExaminingEvent()
14212 {
14213     /* Stop observing current game */
14214     SendToICS(ics_prefix);
14215     SendToICS("unexamine\n");
14216 }
14217
14218 void
14219 ForwardInner(target)
14220      int target;
14221 {
14222     int limit;
14223
14224     if (appData.debugMode)
14225         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14226                 target, currentMove, forwardMostMove);
14227
14228     if (gameMode == EditPosition)
14229       return;
14230
14231     MarkTargetSquares(1);
14232
14233     if (gameMode == PlayFromGameFile && !pausing)
14234       PauseEvent();
14235
14236     if (gameMode == IcsExamining && pausing)
14237       limit = pauseExamForwardMostMove;
14238     else
14239       limit = forwardMostMove;
14240
14241     if (target > limit) target = limit;
14242
14243     if (target > 0 && moveList[target - 1][0]) {
14244         int fromX, fromY, toX, toY;
14245         toX = moveList[target - 1][2] - AAA;
14246         toY = moveList[target - 1][3] - ONE;
14247         if (moveList[target - 1][1] == '@') {
14248             if (appData.highlightLastMove) {
14249                 SetHighlights(-1, -1, toX, toY);
14250             }
14251         } else {
14252             fromX = moveList[target - 1][0] - AAA;
14253             fromY = moveList[target - 1][1] - ONE;
14254             if (target == currentMove + 1) {
14255                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14256             }
14257             if (appData.highlightLastMove) {
14258                 SetHighlights(fromX, fromY, toX, toY);
14259             }
14260         }
14261     }
14262     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14263         gameMode == Training || gameMode == PlayFromGameFile ||
14264         gameMode == AnalyzeFile) {
14265         while (currentMove < target) {
14266             SendMoveToProgram(currentMove++, &first);
14267         }
14268     } else {
14269         currentMove = target;
14270     }
14271
14272     if (gameMode == EditGame || gameMode == EndOfGame) {
14273         whiteTimeRemaining = timeRemaining[0][currentMove];
14274         blackTimeRemaining = timeRemaining[1][currentMove];
14275     }
14276     DisplayBothClocks();
14277     DisplayMove(currentMove - 1);
14278     DrawPosition(FALSE, boards[currentMove]);
14279     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14280     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14281         DisplayComment(currentMove - 1, commentList[currentMove]);
14282     }
14283 }
14284
14285
14286 void
14287 ForwardEvent()
14288 {
14289     if (gameMode == IcsExamining && !pausing) {
14290         SendToICS(ics_prefix);
14291         SendToICS("forward\n");
14292     } else {
14293         ForwardInner(currentMove + 1);
14294     }
14295 }
14296
14297 void
14298 ToEndEvent()
14299 {
14300     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14301         /* to optimze, we temporarily turn off analysis mode while we feed
14302          * the remaining moves to the engine. Otherwise we get analysis output
14303          * after each move.
14304          */
14305         if (first.analysisSupport) {
14306           SendToProgram("exit\nforce\n", &first);
14307           first.analyzing = FALSE;
14308         }
14309     }
14310
14311     if (gameMode == IcsExamining && !pausing) {
14312         SendToICS(ics_prefix);
14313         SendToICS("forward 999999\n");
14314     } else {
14315         ForwardInner(forwardMostMove);
14316     }
14317
14318     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14319         /* we have fed all the moves, so reactivate analysis mode */
14320         SendToProgram("analyze\n", &first);
14321         first.analyzing = TRUE;
14322         /*first.maybeThinking = TRUE;*/
14323         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14324     }
14325 }
14326
14327 void
14328 BackwardInner(target)
14329      int target;
14330 {
14331     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14332
14333     if (appData.debugMode)
14334         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14335                 target, currentMove, forwardMostMove);
14336
14337     if (gameMode == EditPosition) return;
14338     MarkTargetSquares(1);
14339     if (currentMove <= backwardMostMove) {
14340         ClearHighlights();
14341         DrawPosition(full_redraw, boards[currentMove]);
14342         return;
14343     }
14344     if (gameMode == PlayFromGameFile && !pausing)
14345       PauseEvent();
14346
14347     if (moveList[target][0]) {
14348         int fromX, fromY, toX, toY;
14349         toX = moveList[target][2] - AAA;
14350         toY = moveList[target][3] - ONE;
14351         if (moveList[target][1] == '@') {
14352             if (appData.highlightLastMove) {
14353                 SetHighlights(-1, -1, toX, toY);
14354             }
14355         } else {
14356             fromX = moveList[target][0] - AAA;
14357             fromY = moveList[target][1] - ONE;
14358             if (target == currentMove - 1) {
14359                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14360             }
14361             if (appData.highlightLastMove) {
14362                 SetHighlights(fromX, fromY, toX, toY);
14363             }
14364         }
14365     }
14366     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14367         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14368         while (currentMove > target) {
14369             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14370                 // null move cannot be undone. Reload program with move history before it.
14371                 int i;
14372                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14373                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14374                 }
14375                 SendBoard(&first, i); 
14376                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14377                 break;
14378             }
14379             SendToProgram("undo\n", &first);
14380             currentMove--;
14381         }
14382     } else {
14383         currentMove = target;
14384     }
14385
14386     if (gameMode == EditGame || gameMode == EndOfGame) {
14387         whiteTimeRemaining = timeRemaining[0][currentMove];
14388         blackTimeRemaining = timeRemaining[1][currentMove];
14389     }
14390     DisplayBothClocks();
14391     DisplayMove(currentMove - 1);
14392     DrawPosition(full_redraw, boards[currentMove]);
14393     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14394     // [HGM] PV info: routine tests if comment empty
14395     DisplayComment(currentMove - 1, commentList[currentMove]);
14396 }
14397
14398 void
14399 BackwardEvent()
14400 {
14401     if (gameMode == IcsExamining && !pausing) {
14402         SendToICS(ics_prefix);
14403         SendToICS("backward\n");
14404     } else {
14405         BackwardInner(currentMove - 1);
14406     }
14407 }
14408
14409 void
14410 ToStartEvent()
14411 {
14412     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14413         /* to optimize, we temporarily turn off analysis mode while we undo
14414          * all the moves. Otherwise we get analysis output after each undo.
14415          */
14416         if (first.analysisSupport) {
14417           SendToProgram("exit\nforce\n", &first);
14418           first.analyzing = FALSE;
14419         }
14420     }
14421
14422     if (gameMode == IcsExamining && !pausing) {
14423         SendToICS(ics_prefix);
14424         SendToICS("backward 999999\n");
14425     } else {
14426         BackwardInner(backwardMostMove);
14427     }
14428
14429     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14430         /* we have fed all the moves, so reactivate analysis mode */
14431         SendToProgram("analyze\n", &first);
14432         first.analyzing = TRUE;
14433         /*first.maybeThinking = TRUE;*/
14434         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14435     }
14436 }
14437
14438 void
14439 ToNrEvent(int to)
14440 {
14441   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14442   if (to >= forwardMostMove) to = forwardMostMove;
14443   if (to <= backwardMostMove) to = backwardMostMove;
14444   if (to < currentMove) {
14445     BackwardInner(to);
14446   } else {
14447     ForwardInner(to);
14448   }
14449 }
14450
14451 void
14452 RevertEvent(Boolean annotate)
14453 {
14454     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14455         return;
14456     }
14457     if (gameMode != IcsExamining) {
14458         DisplayError(_("You are not examining a game"), 0);
14459         return;
14460     }
14461     if (pausing) {
14462         DisplayError(_("You can't revert while pausing"), 0);
14463         return;
14464     }
14465     SendToICS(ics_prefix);
14466     SendToICS("revert\n");
14467 }
14468
14469 void
14470 RetractMoveEvent()
14471 {
14472     switch (gameMode) {
14473       case MachinePlaysWhite:
14474       case MachinePlaysBlack:
14475         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14476             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14477             return;
14478         }
14479         if (forwardMostMove < 2) return;
14480         currentMove = forwardMostMove = forwardMostMove - 2;
14481         whiteTimeRemaining = timeRemaining[0][currentMove];
14482         blackTimeRemaining = timeRemaining[1][currentMove];
14483         DisplayBothClocks();
14484         DisplayMove(currentMove - 1);
14485         ClearHighlights();/*!! could figure this out*/
14486         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14487         SendToProgram("remove\n", &first);
14488         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14489         break;
14490
14491       case BeginningOfGame:
14492       default:
14493         break;
14494
14495       case IcsPlayingWhite:
14496       case IcsPlayingBlack:
14497         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14498             SendToICS(ics_prefix);
14499             SendToICS("takeback 2\n");
14500         } else {
14501             SendToICS(ics_prefix);
14502             SendToICS("takeback 1\n");
14503         }
14504         break;
14505     }
14506 }
14507
14508 void
14509 MoveNowEvent()
14510 {
14511     ChessProgramState *cps;
14512
14513     switch (gameMode) {
14514       case MachinePlaysWhite:
14515         if (!WhiteOnMove(forwardMostMove)) {
14516             DisplayError(_("It is your turn"), 0);
14517             return;
14518         }
14519         cps = &first;
14520         break;
14521       case MachinePlaysBlack:
14522         if (WhiteOnMove(forwardMostMove)) {
14523             DisplayError(_("It is your turn"), 0);
14524             return;
14525         }
14526         cps = &first;
14527         break;
14528       case TwoMachinesPlay:
14529         if (WhiteOnMove(forwardMostMove) ==
14530             (first.twoMachinesColor[0] == 'w')) {
14531             cps = &first;
14532         } else {
14533             cps = &second;
14534         }
14535         break;
14536       case BeginningOfGame:
14537       default:
14538         return;
14539     }
14540     SendToProgram("?\n", cps);
14541 }
14542
14543 void
14544 TruncateGameEvent()
14545 {
14546     EditGameEvent();
14547     if (gameMode != EditGame) return;
14548     TruncateGame();
14549 }
14550
14551 void
14552 TruncateGame()
14553 {
14554     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14555     if (forwardMostMove > currentMove) {
14556         if (gameInfo.resultDetails != NULL) {
14557             free(gameInfo.resultDetails);
14558             gameInfo.resultDetails = NULL;
14559             gameInfo.result = GameUnfinished;
14560         }
14561         forwardMostMove = currentMove;
14562         HistorySet(parseList, backwardMostMove, forwardMostMove,
14563                    currentMove-1);
14564     }
14565 }
14566
14567 void
14568 HintEvent()
14569 {
14570     if (appData.noChessProgram) return;
14571     switch (gameMode) {
14572       case MachinePlaysWhite:
14573         if (WhiteOnMove(forwardMostMove)) {
14574             DisplayError(_("Wait until your turn"), 0);
14575             return;
14576         }
14577         break;
14578       case BeginningOfGame:
14579       case MachinePlaysBlack:
14580         if (!WhiteOnMove(forwardMostMove)) {
14581             DisplayError(_("Wait until your turn"), 0);
14582             return;
14583         }
14584         break;
14585       default:
14586         DisplayError(_("No hint available"), 0);
14587         return;
14588     }
14589     SendToProgram("hint\n", &first);
14590     hintRequested = TRUE;
14591 }
14592
14593 void
14594 BookEvent()
14595 {
14596     if (appData.noChessProgram) return;
14597     switch (gameMode) {
14598       case MachinePlaysWhite:
14599         if (WhiteOnMove(forwardMostMove)) {
14600             DisplayError(_("Wait until your turn"), 0);
14601             return;
14602         }
14603         break;
14604       case BeginningOfGame:
14605       case MachinePlaysBlack:
14606         if (!WhiteOnMove(forwardMostMove)) {
14607             DisplayError(_("Wait until your turn"), 0);
14608             return;
14609         }
14610         break;
14611       case EditPosition:
14612         EditPositionDone(TRUE);
14613         break;
14614       case TwoMachinesPlay:
14615         return;
14616       default:
14617         break;
14618     }
14619     SendToProgram("bk\n", &first);
14620     bookOutput[0] = NULLCHAR;
14621     bookRequested = TRUE;
14622 }
14623
14624 void
14625 AboutGameEvent()
14626 {
14627     char *tags = PGNTags(&gameInfo);
14628     TagsPopUp(tags, CmailMsg());
14629     free(tags);
14630 }
14631
14632 /* end button procedures */
14633
14634 void
14635 PrintPosition(fp, move)
14636      FILE *fp;
14637      int move;
14638 {
14639     int i, j;
14640
14641     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14642         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14643             char c = PieceToChar(boards[move][i][j]);
14644             fputc(c == 'x' ? '.' : c, fp);
14645             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14646         }
14647     }
14648     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14649       fprintf(fp, "white to play\n");
14650     else
14651       fprintf(fp, "black to play\n");
14652 }
14653
14654 void
14655 PrintOpponents(fp)
14656      FILE *fp;
14657 {
14658     if (gameInfo.white != NULL) {
14659         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14660     } else {
14661         fprintf(fp, "\n");
14662     }
14663 }
14664
14665 /* Find last component of program's own name, using some heuristics */
14666 void
14667 TidyProgramName(prog, host, buf)
14668      char *prog, *host, buf[MSG_SIZ];
14669 {
14670     char *p, *q;
14671     int local = (strcmp(host, "localhost") == 0);
14672     while (!local && (p = strchr(prog, ';')) != NULL) {
14673         p++;
14674         while (*p == ' ') p++;
14675         prog = p;
14676     }
14677     if (*prog == '"' || *prog == '\'') {
14678         q = strchr(prog + 1, *prog);
14679     } else {
14680         q = strchr(prog, ' ');
14681     }
14682     if (q == NULL) q = prog + strlen(prog);
14683     p = q;
14684     while (p >= prog && *p != '/' && *p != '\\') p--;
14685     p++;
14686     if(p == prog && *p == '"') p++;
14687     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14688     memcpy(buf, p, q - p);
14689     buf[q - p] = NULLCHAR;
14690     if (!local) {
14691         strcat(buf, "@");
14692         strcat(buf, host);
14693     }
14694 }
14695
14696 char *
14697 TimeControlTagValue()
14698 {
14699     char buf[MSG_SIZ];
14700     if (!appData.clockMode) {
14701       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14702     } else if (movesPerSession > 0) {
14703       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14704     } else if (timeIncrement == 0) {
14705       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14706     } else {
14707       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14708     }
14709     return StrSave(buf);
14710 }
14711
14712 void
14713 SetGameInfo()
14714 {
14715     /* This routine is used only for certain modes */
14716     VariantClass v = gameInfo.variant;
14717     ChessMove r = GameUnfinished;
14718     char *p = NULL;
14719
14720     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14721         r = gameInfo.result;
14722         p = gameInfo.resultDetails;
14723         gameInfo.resultDetails = NULL;
14724     }
14725     ClearGameInfo(&gameInfo);
14726     gameInfo.variant = v;
14727
14728     switch (gameMode) {
14729       case MachinePlaysWhite:
14730         gameInfo.event = StrSave( appData.pgnEventHeader );
14731         gameInfo.site = StrSave(HostName());
14732         gameInfo.date = PGNDate();
14733         gameInfo.round = StrSave("-");
14734         gameInfo.white = StrSave(first.tidy);
14735         gameInfo.black = StrSave(UserName());
14736         gameInfo.timeControl = TimeControlTagValue();
14737         break;
14738
14739       case MachinePlaysBlack:
14740         gameInfo.event = StrSave( appData.pgnEventHeader );
14741         gameInfo.site = StrSave(HostName());
14742         gameInfo.date = PGNDate();
14743         gameInfo.round = StrSave("-");
14744         gameInfo.white = StrSave(UserName());
14745         gameInfo.black = StrSave(first.tidy);
14746         gameInfo.timeControl = TimeControlTagValue();
14747         break;
14748
14749       case TwoMachinesPlay:
14750         gameInfo.event = StrSave( appData.pgnEventHeader );
14751         gameInfo.site = StrSave(HostName());
14752         gameInfo.date = PGNDate();
14753         if (roundNr > 0) {
14754             char buf[MSG_SIZ];
14755             snprintf(buf, MSG_SIZ, "%d", roundNr);
14756             gameInfo.round = StrSave(buf);
14757         } else {
14758             gameInfo.round = StrSave("-");
14759         }
14760         if (first.twoMachinesColor[0] == 'w') {
14761             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14762             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14763         } else {
14764             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14765             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14766         }
14767         gameInfo.timeControl = TimeControlTagValue();
14768         break;
14769
14770       case EditGame:
14771         gameInfo.event = StrSave("Edited game");
14772         gameInfo.site = StrSave(HostName());
14773         gameInfo.date = PGNDate();
14774         gameInfo.round = StrSave("-");
14775         gameInfo.white = StrSave("-");
14776         gameInfo.black = StrSave("-");
14777         gameInfo.result = r;
14778         gameInfo.resultDetails = p;
14779         break;
14780
14781       case EditPosition:
14782         gameInfo.event = StrSave("Edited position");
14783         gameInfo.site = StrSave(HostName());
14784         gameInfo.date = PGNDate();
14785         gameInfo.round = StrSave("-");
14786         gameInfo.white = StrSave("-");
14787         gameInfo.black = StrSave("-");
14788         break;
14789
14790       case IcsPlayingWhite:
14791       case IcsPlayingBlack:
14792       case IcsObserving:
14793       case IcsExamining:
14794         break;
14795
14796       case PlayFromGameFile:
14797         gameInfo.event = StrSave("Game from non-PGN file");
14798         gameInfo.site = StrSave(HostName());
14799         gameInfo.date = PGNDate();
14800         gameInfo.round = StrSave("-");
14801         gameInfo.white = StrSave("?");
14802         gameInfo.black = StrSave("?");
14803         break;
14804
14805       default:
14806         break;
14807     }
14808 }
14809
14810 void
14811 ReplaceComment(index, text)
14812      int index;
14813      char *text;
14814 {
14815     int len;
14816     char *p;
14817     float score;
14818
14819     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14820        pvInfoList[index-1].depth == len &&
14821        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14822        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14823     while (*text == '\n') text++;
14824     len = strlen(text);
14825     while (len > 0 && text[len - 1] == '\n') len--;
14826
14827     if (commentList[index] != NULL)
14828       free(commentList[index]);
14829
14830     if (len == 0) {
14831         commentList[index] = NULL;
14832         return;
14833     }
14834   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14835       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14836       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14837     commentList[index] = (char *) malloc(len + 2);
14838     strncpy(commentList[index], text, len);
14839     commentList[index][len] = '\n';
14840     commentList[index][len + 1] = NULLCHAR;
14841   } else {
14842     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14843     char *p;
14844     commentList[index] = (char *) malloc(len + 7);
14845     safeStrCpy(commentList[index], "{\n", 3);
14846     safeStrCpy(commentList[index]+2, text, len+1);
14847     commentList[index][len+2] = NULLCHAR;
14848     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14849     strcat(commentList[index], "\n}\n");
14850   }
14851 }
14852
14853 void
14854 CrushCRs(text)
14855      char *text;
14856 {
14857   char *p = text;
14858   char *q = text;
14859   char ch;
14860
14861   do {
14862     ch = *p++;
14863     if (ch == '\r') continue;
14864     *q++ = ch;
14865   } while (ch != '\0');
14866 }
14867
14868 void
14869 AppendComment(index, text, addBraces)
14870      int index;
14871      char *text;
14872      Boolean addBraces; // [HGM] braces: tells if we should add {}
14873 {
14874     int oldlen, len;
14875     char *old;
14876
14877 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14878     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14879
14880     CrushCRs(text);
14881     while (*text == '\n') text++;
14882     len = strlen(text);
14883     while (len > 0 && text[len - 1] == '\n') len--;
14884
14885     if (len == 0) return;
14886
14887     if (commentList[index] != NULL) {
14888       Boolean addClosingBrace = addBraces;
14889         old = commentList[index];
14890         oldlen = strlen(old);
14891         while(commentList[index][oldlen-1] ==  '\n')
14892           commentList[index][--oldlen] = NULLCHAR;
14893         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14894         safeStrCpy(commentList[index], old, oldlen + len + 6);
14895         free(old);
14896         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14897         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14898           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14899           while (*text == '\n') { text++; len--; }
14900           commentList[index][--oldlen] = NULLCHAR;
14901       }
14902         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14903         else          strcat(commentList[index], "\n");
14904         strcat(commentList[index], text);
14905         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14906         else          strcat(commentList[index], "\n");
14907     } else {
14908         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14909         if(addBraces)
14910           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14911         else commentList[index][0] = NULLCHAR;
14912         strcat(commentList[index], text);
14913         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14914         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14915     }
14916 }
14917
14918 static char * FindStr( char * text, char * sub_text )
14919 {
14920     char * result = strstr( text, sub_text );
14921
14922     if( result != NULL ) {
14923         result += strlen( sub_text );
14924     }
14925
14926     return result;
14927 }
14928
14929 /* [AS] Try to extract PV info from PGN comment */
14930 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14931 char *GetInfoFromComment( int index, char * text )
14932 {
14933     char * sep = text, *p;
14934
14935     if( text != NULL && index > 0 ) {
14936         int score = 0;
14937         int depth = 0;
14938         int time = -1, sec = 0, deci;
14939         char * s_eval = FindStr( text, "[%eval " );
14940         char * s_emt = FindStr( text, "[%emt " );
14941
14942         if( s_eval != NULL || s_emt != NULL ) {
14943             /* New style */
14944             char delim;
14945
14946             if( s_eval != NULL ) {
14947                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14948                     return text;
14949                 }
14950
14951                 if( delim != ']' ) {
14952                     return text;
14953                 }
14954             }
14955
14956             if( s_emt != NULL ) {
14957             }
14958                 return text;
14959         }
14960         else {
14961             /* We expect something like: [+|-]nnn.nn/dd */
14962             int score_lo = 0;
14963
14964             if(*text != '{') return text; // [HGM] braces: must be normal comment
14965
14966             sep = strchr( text, '/' );
14967             if( sep == NULL || sep < (text+4) ) {
14968                 return text;
14969             }
14970
14971             p = text;
14972             if(p[1] == '(') { // comment starts with PV
14973                p = strchr(p, ')'); // locate end of PV
14974                if(p == NULL || sep < p+5) return text;
14975                // at this point we have something like "{(.*) +0.23/6 ..."
14976                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14977                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14978                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14979             }
14980             time = -1; sec = -1; deci = -1;
14981             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14982                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14983                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14984                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14985                 return text;
14986             }
14987
14988             if( score_lo < 0 || score_lo >= 100 ) {
14989                 return text;
14990             }
14991
14992             if(sec >= 0) time = 600*time + 10*sec; else
14993             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14994
14995             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14996
14997             /* [HGM] PV time: now locate end of PV info */
14998             while( *++sep >= '0' && *sep <= '9'); // strip depth
14999             if(time >= 0)
15000             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15001             if(sec >= 0)
15002             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15003             if(deci >= 0)
15004             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15005             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15006         }
15007
15008         if( depth <= 0 ) {
15009             return text;
15010         }
15011
15012         if( time < 0 ) {
15013             time = -1;
15014         }
15015
15016         pvInfoList[index-1].depth = depth;
15017         pvInfoList[index-1].score = score;
15018         pvInfoList[index-1].time  = 10*time; // centi-sec
15019         if(*sep == '}') *sep = 0; else *--sep = '{';
15020         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15021     }
15022     return sep;
15023 }
15024
15025 void
15026 SendToProgram(message, cps)
15027      char *message;
15028      ChessProgramState *cps;
15029 {
15030     int count, outCount, error;
15031     char buf[MSG_SIZ];
15032
15033     if (cps->pr == NoProc) return;
15034     Attention(cps);
15035
15036     if (appData.debugMode) {
15037         TimeMark now;
15038         GetTimeMark(&now);
15039         fprintf(debugFP, "%ld >%-6s: %s",
15040                 SubtractTimeMarks(&now, &programStartTime),
15041                 cps->which, message);
15042     }
15043
15044     count = strlen(message);
15045     outCount = OutputToProcess(cps->pr, message, count, &error);
15046     if (outCount < count && !exiting
15047                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15048       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15049       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15050         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15051             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15052                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15053                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15054                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15055             } else {
15056                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15057                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15058                 gameInfo.result = res;
15059             }
15060             gameInfo.resultDetails = StrSave(buf);
15061         }
15062         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15063         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15064     }
15065 }
15066
15067 void
15068 ReceiveFromProgram(isr, closure, message, count, error)
15069      InputSourceRef isr;
15070      VOIDSTAR closure;
15071      char *message;
15072      int count;
15073      int error;
15074 {
15075     char *end_str;
15076     char buf[MSG_SIZ];
15077     ChessProgramState *cps = (ChessProgramState *)closure;
15078
15079     if (isr != cps->isr) return; /* Killed intentionally */
15080     if (count <= 0) {
15081         if (count == 0) {
15082             RemoveInputSource(cps->isr);
15083             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15084             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15085                     _(cps->which), cps->program);
15086         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15087                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15088                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15089                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15090                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15091                 } else {
15092                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15093                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15094                     gameInfo.result = res;
15095                 }
15096                 gameInfo.resultDetails = StrSave(buf);
15097             }
15098             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15099             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15100         } else {
15101             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15102                     _(cps->which), cps->program);
15103             RemoveInputSource(cps->isr);
15104
15105             /* [AS] Program is misbehaving badly... kill it */
15106             if( count == -2 ) {
15107                 DestroyChildProcess( cps->pr, 9 );
15108                 cps->pr = NoProc;
15109             }
15110
15111             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15112         }
15113         return;
15114     }
15115
15116     if ((end_str = strchr(message, '\r')) != NULL)
15117       *end_str = NULLCHAR;
15118     if ((end_str = strchr(message, '\n')) != NULL)
15119       *end_str = NULLCHAR;
15120
15121     if (appData.debugMode) {
15122         TimeMark now; int print = 1;
15123         char *quote = ""; char c; int i;
15124
15125         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15126                 char start = message[0];
15127                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15128                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15129                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15130                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15131                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15132                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15133                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15134                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15135                    sscanf(message, "hint: %c", &c)!=1 && 
15136                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15137                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15138                     print = (appData.engineComments >= 2);
15139                 }
15140                 message[0] = start; // restore original message
15141         }
15142         if(print) {
15143                 GetTimeMark(&now);
15144                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15145                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15146                         quote,
15147                         message);
15148         }
15149     }
15150
15151     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15152     if (appData.icsEngineAnalyze) {
15153         if (strstr(message, "whisper") != NULL ||
15154              strstr(message, "kibitz") != NULL ||
15155             strstr(message, "tellics") != NULL) return;
15156     }
15157
15158     HandleMachineMove(message, cps);
15159 }
15160
15161
15162 void
15163 SendTimeControl(cps, mps, tc, inc, sd, st)
15164      ChessProgramState *cps;
15165      int mps, inc, sd, st;
15166      long tc;
15167 {
15168     char buf[MSG_SIZ];
15169     int seconds;
15170
15171     if( timeControl_2 > 0 ) {
15172         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15173             tc = timeControl_2;
15174         }
15175     }
15176     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15177     inc /= cps->timeOdds;
15178     st  /= cps->timeOdds;
15179
15180     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15181
15182     if (st > 0) {
15183       /* Set exact time per move, normally using st command */
15184       if (cps->stKludge) {
15185         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15186         seconds = st % 60;
15187         if (seconds == 0) {
15188           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15189         } else {
15190           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15191         }
15192       } else {
15193         snprintf(buf, MSG_SIZ, "st %d\n", st);
15194       }
15195     } else {
15196       /* Set conventional or incremental time control, using level command */
15197       if (seconds == 0) {
15198         /* Note old gnuchess bug -- minutes:seconds used to not work.
15199            Fixed in later versions, but still avoid :seconds
15200            when seconds is 0. */
15201         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15202       } else {
15203         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15204                  seconds, inc/1000.);
15205       }
15206     }
15207     SendToProgram(buf, cps);
15208
15209     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15210     /* Orthogonally, limit search to given depth */
15211     if (sd > 0) {
15212       if (cps->sdKludge) {
15213         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15214       } else {
15215         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15216       }
15217       SendToProgram(buf, cps);
15218     }
15219
15220     if(cps->nps >= 0) { /* [HGM] nps */
15221         if(cps->supportsNPS == FALSE)
15222           cps->nps = -1; // don't use if engine explicitly says not supported!
15223         else {
15224           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15225           SendToProgram(buf, cps);
15226         }
15227     }
15228 }
15229
15230 ChessProgramState *WhitePlayer()
15231 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15232 {
15233     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15234        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15235         return &second;
15236     return &first;
15237 }
15238
15239 void
15240 SendTimeRemaining(cps, machineWhite)
15241      ChessProgramState *cps;
15242      int /*boolean*/ machineWhite;
15243 {
15244     char message[MSG_SIZ];
15245     long time, otime;
15246
15247     /* Note: this routine must be called when the clocks are stopped
15248        or when they have *just* been set or switched; otherwise
15249        it will be off by the time since the current tick started.
15250     */
15251     if (machineWhite) {
15252         time = whiteTimeRemaining / 10;
15253         otime = blackTimeRemaining / 10;
15254     } else {
15255         time = blackTimeRemaining / 10;
15256         otime = whiteTimeRemaining / 10;
15257     }
15258     /* [HGM] translate opponent's time by time-odds factor */
15259     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15260     if (appData.debugMode) {
15261         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15262     }
15263
15264     if (time <= 0) time = 1;
15265     if (otime <= 0) otime = 1;
15266
15267     snprintf(message, MSG_SIZ, "time %ld\n", time);
15268     SendToProgram(message, cps);
15269
15270     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15271     SendToProgram(message, cps);
15272 }
15273
15274 int
15275 BoolFeature(p, name, loc, cps)
15276      char **p;
15277      char *name;
15278      int *loc;
15279      ChessProgramState *cps;
15280 {
15281   char buf[MSG_SIZ];
15282   int len = strlen(name);
15283   int val;
15284
15285   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15286     (*p) += len + 1;
15287     sscanf(*p, "%d", &val);
15288     *loc = (val != 0);
15289     while (**p && **p != ' ')
15290       (*p)++;
15291     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15292     SendToProgram(buf, cps);
15293     return TRUE;
15294   }
15295   return FALSE;
15296 }
15297
15298 int
15299 IntFeature(p, name, loc, cps)
15300      char **p;
15301      char *name;
15302      int *loc;
15303      ChessProgramState *cps;
15304 {
15305   char buf[MSG_SIZ];
15306   int len = strlen(name);
15307   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15308     (*p) += len + 1;
15309     sscanf(*p, "%d", loc);
15310     while (**p && **p != ' ') (*p)++;
15311     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15312     SendToProgram(buf, cps);
15313     return TRUE;
15314   }
15315   return FALSE;
15316 }
15317
15318 int
15319 StringFeature(p, name, loc, cps)
15320      char **p;
15321      char *name;
15322      char loc[];
15323      ChessProgramState *cps;
15324 {
15325   char buf[MSG_SIZ];
15326   int len = strlen(name);
15327   if (strncmp((*p), name, len) == 0
15328       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15329     (*p) += len + 2;
15330     sscanf(*p, "%[^\"]", loc);
15331     while (**p && **p != '\"') (*p)++;
15332     if (**p == '\"') (*p)++;
15333     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15334     SendToProgram(buf, cps);
15335     return TRUE;
15336   }
15337   return FALSE;
15338 }
15339
15340 int
15341 ParseOption(Option *opt, ChessProgramState *cps)
15342 // [HGM] options: process the string that defines an engine option, and determine
15343 // name, type, default value, and allowed value range
15344 {
15345         char *p, *q, buf[MSG_SIZ];
15346         int n, min = (-1)<<31, max = 1<<31, def;
15347
15348         if(p = strstr(opt->name, " -spin ")) {
15349             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15350             if(max < min) max = min; // enforce consistency
15351             if(def < min) def = min;
15352             if(def > max) def = max;
15353             opt->value = def;
15354             opt->min = min;
15355             opt->max = max;
15356             opt->type = Spin;
15357         } else if((p = strstr(opt->name, " -slider "))) {
15358             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15359             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15360             if(max < min) max = min; // enforce consistency
15361             if(def < min) def = min;
15362             if(def > max) def = max;
15363             opt->value = def;
15364             opt->min = min;
15365             opt->max = max;
15366             opt->type = Spin; // Slider;
15367         } else if((p = strstr(opt->name, " -string "))) {
15368             opt->textValue = p+9;
15369             opt->type = TextBox;
15370         } else if((p = strstr(opt->name, " -file "))) {
15371             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15372             opt->textValue = p+7;
15373             opt->type = FileName; // FileName;
15374         } else if((p = strstr(opt->name, " -path "))) {
15375             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15376             opt->textValue = p+7;
15377             opt->type = PathName; // PathName;
15378         } else if(p = strstr(opt->name, " -check ")) {
15379             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15380             opt->value = (def != 0);
15381             opt->type = CheckBox;
15382         } else if(p = strstr(opt->name, " -combo ")) {
15383             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15384             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15385             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15386             opt->value = n = 0;
15387             while(q = StrStr(q, " /// ")) {
15388                 n++; *q = 0;    // count choices, and null-terminate each of them
15389                 q += 5;
15390                 if(*q == '*') { // remember default, which is marked with * prefix
15391                     q++;
15392                     opt->value = n;
15393                 }
15394                 cps->comboList[cps->comboCnt++] = q;
15395             }
15396             cps->comboList[cps->comboCnt++] = NULL;
15397             opt->max = n + 1;
15398             opt->type = ComboBox;
15399         } else if(p = strstr(opt->name, " -button")) {
15400             opt->type = Button;
15401         } else if(p = strstr(opt->name, " -save")) {
15402             opt->type = SaveButton;
15403         } else return FALSE;
15404         *p = 0; // terminate option name
15405         // now look if the command-line options define a setting for this engine option.
15406         if(cps->optionSettings && cps->optionSettings[0])
15407             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15408         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15409           snprintf(buf, MSG_SIZ, "option %s", p);
15410                 if(p = strstr(buf, ",")) *p = 0;
15411                 if(q = strchr(buf, '=')) switch(opt->type) {
15412                     case ComboBox:
15413                         for(n=0; n<opt->max; n++)
15414                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15415                         break;
15416                     case TextBox:
15417                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15418                         break;
15419                     case Spin:
15420                     case CheckBox:
15421                         opt->value = atoi(q+1);
15422                     default:
15423                         break;
15424                 }
15425                 strcat(buf, "\n");
15426                 SendToProgram(buf, cps);
15427         }
15428         return TRUE;
15429 }
15430
15431 void
15432 FeatureDone(cps, val)
15433      ChessProgramState* cps;
15434      int val;
15435 {
15436   DelayedEventCallback cb = GetDelayedEvent();
15437   if ((cb == InitBackEnd3 && cps == &first) ||
15438       (cb == SettingsMenuIfReady && cps == &second) ||
15439       (cb == LoadEngine) ||
15440       (cb == TwoMachinesEventIfReady)) {
15441     CancelDelayedEvent();
15442     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15443   }
15444   cps->initDone = val;
15445 }
15446
15447 /* Parse feature command from engine */
15448 void
15449 ParseFeatures(args, cps)
15450      char* args;
15451      ChessProgramState *cps;
15452 {
15453   char *p = args;
15454   char *q;
15455   int val;
15456   char buf[MSG_SIZ];
15457
15458   for (;;) {
15459     while (*p == ' ') p++;
15460     if (*p == NULLCHAR) return;
15461
15462     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15463     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15464     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15465     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15466     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15467     if (BoolFeature(&p, "reuse", &val, cps)) {
15468       /* Engine can disable reuse, but can't enable it if user said no */
15469       if (!val) cps->reuse = FALSE;
15470       continue;
15471     }
15472     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15473     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15474       if (gameMode == TwoMachinesPlay) {
15475         DisplayTwoMachinesTitle();
15476       } else {
15477         DisplayTitle("");
15478       }
15479       continue;
15480     }
15481     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15482     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15483     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15484     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15485     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15486     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15487     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15488     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15489     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15490     if (IntFeature(&p, "done", &val, cps)) {
15491       FeatureDone(cps, val);
15492       continue;
15493     }
15494     /* Added by Tord: */
15495     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15496     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15497     /* End of additions by Tord */
15498
15499     /* [HGM] added features: */
15500     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15501     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15502     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15503     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15504     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15505     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15506     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15507         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15508           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15509             SendToProgram(buf, cps);
15510             continue;
15511         }
15512         if(cps->nrOptions >= MAX_OPTIONS) {
15513             cps->nrOptions--;
15514             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15515             DisplayError(buf, 0);
15516         }
15517         continue;
15518     }
15519     /* End of additions by HGM */
15520
15521     /* unknown feature: complain and skip */
15522     q = p;
15523     while (*q && *q != '=') q++;
15524     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15525     SendToProgram(buf, cps);
15526     p = q;
15527     if (*p == '=') {
15528       p++;
15529       if (*p == '\"') {
15530         p++;
15531         while (*p && *p != '\"') p++;
15532         if (*p == '\"') p++;
15533       } else {
15534         while (*p && *p != ' ') p++;
15535       }
15536     }
15537   }
15538
15539 }
15540
15541 void
15542 PeriodicUpdatesEvent(newState)
15543      int newState;
15544 {
15545     if (newState == appData.periodicUpdates)
15546       return;
15547
15548     appData.periodicUpdates=newState;
15549
15550     /* Display type changes, so update it now */
15551 //    DisplayAnalysis();
15552
15553     /* Get the ball rolling again... */
15554     if (newState) {
15555         AnalysisPeriodicEvent(1);
15556         StartAnalysisClock();
15557     }
15558 }
15559
15560 void
15561 PonderNextMoveEvent(newState)
15562      int newState;
15563 {
15564     if (newState == appData.ponderNextMove) return;
15565     if (gameMode == EditPosition) EditPositionDone(TRUE);
15566     if (newState) {
15567         SendToProgram("hard\n", &first);
15568         if (gameMode == TwoMachinesPlay) {
15569             SendToProgram("hard\n", &second);
15570         }
15571     } else {
15572         SendToProgram("easy\n", &first);
15573         thinkOutput[0] = NULLCHAR;
15574         if (gameMode == TwoMachinesPlay) {
15575             SendToProgram("easy\n", &second);
15576         }
15577     }
15578     appData.ponderNextMove = newState;
15579 }
15580
15581 void
15582 NewSettingEvent(option, feature, command, value)
15583      char *command;
15584      int option, value, *feature;
15585 {
15586     char buf[MSG_SIZ];
15587
15588     if (gameMode == EditPosition) EditPositionDone(TRUE);
15589     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15590     if(feature == NULL || *feature) SendToProgram(buf, &first);
15591     if (gameMode == TwoMachinesPlay) {
15592         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15593     }
15594 }
15595
15596 void
15597 ShowThinkingEvent()
15598 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15599 {
15600     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15601     int newState = appData.showThinking
15602         // [HGM] thinking: other features now need thinking output as well
15603         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15604
15605     if (oldState == newState) return;
15606     oldState = newState;
15607     if (gameMode == EditPosition) EditPositionDone(TRUE);
15608     if (oldState) {
15609         SendToProgram("post\n", &first);
15610         if (gameMode == TwoMachinesPlay) {
15611             SendToProgram("post\n", &second);
15612         }
15613     } else {
15614         SendToProgram("nopost\n", &first);
15615         thinkOutput[0] = NULLCHAR;
15616         if (gameMode == TwoMachinesPlay) {
15617             SendToProgram("nopost\n", &second);
15618         }
15619     }
15620 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15621 }
15622
15623 void
15624 AskQuestionEvent(title, question, replyPrefix, which)
15625      char *title; char *question; char *replyPrefix; char *which;
15626 {
15627   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15628   if (pr == NoProc) return;
15629   AskQuestion(title, question, replyPrefix, pr);
15630 }
15631
15632 void
15633 TypeInEvent(char firstChar)
15634 {
15635     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15636         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15637         gameMode == AnalyzeMode || gameMode == EditGame || 
15638         gameMode == EditPosition || gameMode == IcsExamining ||
15639         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15640         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15641                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15642                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15643         gameMode == Training) PopUpMoveDialog(firstChar);
15644 }
15645
15646 void
15647 TypeInDoneEvent(char *move)
15648 {
15649         Board board;
15650         int n, fromX, fromY, toX, toY;
15651         char promoChar;
15652         ChessMove moveType;
15653
15654         // [HGM] FENedit
15655         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15656                 EditPositionPasteFEN(move);
15657                 return;
15658         }
15659         // [HGM] movenum: allow move number to be typed in any mode
15660         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15661           ToNrEvent(2*n-1);
15662           return;
15663         }
15664
15665       if (gameMode != EditGame && currentMove != forwardMostMove && 
15666         gameMode != Training) {
15667         DisplayMoveError(_("Displayed move is not current"));
15668       } else {
15669         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15670           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15671         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15672         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15673           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15674           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15675         } else {
15676           DisplayMoveError(_("Could not parse move"));
15677         }
15678       }
15679 }
15680
15681 void
15682 DisplayMove(moveNumber)
15683      int moveNumber;
15684 {
15685     char message[MSG_SIZ];
15686     char res[MSG_SIZ];
15687     char cpThinkOutput[MSG_SIZ];
15688
15689     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15690
15691     if (moveNumber == forwardMostMove - 1 ||
15692         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15693
15694         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15695
15696         if (strchr(cpThinkOutput, '\n')) {
15697             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15698         }
15699     } else {
15700         *cpThinkOutput = NULLCHAR;
15701     }
15702
15703     /* [AS] Hide thinking from human user */
15704     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15705         *cpThinkOutput = NULLCHAR;
15706         if( thinkOutput[0] != NULLCHAR ) {
15707             int i;
15708
15709             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15710                 cpThinkOutput[i] = '.';
15711             }
15712             cpThinkOutput[i] = NULLCHAR;
15713             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15714         }
15715     }
15716
15717     if (moveNumber == forwardMostMove - 1 &&
15718         gameInfo.resultDetails != NULL) {
15719         if (gameInfo.resultDetails[0] == NULLCHAR) {
15720           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15721         } else {
15722           snprintf(res, MSG_SIZ, " {%s} %s",
15723                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15724         }
15725     } else {
15726         res[0] = NULLCHAR;
15727     }
15728
15729     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15730         DisplayMessage(res, cpThinkOutput);
15731     } else {
15732       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15733                 WhiteOnMove(moveNumber) ? " " : ".. ",
15734                 parseList[moveNumber], res);
15735         DisplayMessage(message, cpThinkOutput);
15736     }
15737 }
15738
15739 void
15740 DisplayComment(moveNumber, text)
15741      int moveNumber;
15742      char *text;
15743 {
15744     char title[MSG_SIZ];
15745
15746     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15747       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15748     } else {
15749       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15750               WhiteOnMove(moveNumber) ? " " : ".. ",
15751               parseList[moveNumber]);
15752     }
15753     if (text != NULL && (appData.autoDisplayComment || commentUp))
15754         CommentPopUp(title, text);
15755 }
15756
15757 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15758  * might be busy thinking or pondering.  It can be omitted if your
15759  * gnuchess is configured to stop thinking immediately on any user
15760  * input.  However, that gnuchess feature depends on the FIONREAD
15761  * ioctl, which does not work properly on some flavors of Unix.
15762  */
15763 void
15764 Attention(cps)
15765      ChessProgramState *cps;
15766 {
15767 #if ATTENTION
15768     if (!cps->useSigint) return;
15769     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15770     switch (gameMode) {
15771       case MachinePlaysWhite:
15772       case MachinePlaysBlack:
15773       case TwoMachinesPlay:
15774       case IcsPlayingWhite:
15775       case IcsPlayingBlack:
15776       case AnalyzeMode:
15777       case AnalyzeFile:
15778         /* Skip if we know it isn't thinking */
15779         if (!cps->maybeThinking) return;
15780         if (appData.debugMode)
15781           fprintf(debugFP, "Interrupting %s\n", cps->which);
15782         InterruptChildProcess(cps->pr);
15783         cps->maybeThinking = FALSE;
15784         break;
15785       default:
15786         break;
15787     }
15788 #endif /*ATTENTION*/
15789 }
15790
15791 int
15792 CheckFlags()
15793 {
15794     if (whiteTimeRemaining <= 0) {
15795         if (!whiteFlag) {
15796             whiteFlag = TRUE;
15797             if (appData.icsActive) {
15798                 if (appData.autoCallFlag &&
15799                     gameMode == IcsPlayingBlack && !blackFlag) {
15800                   SendToICS(ics_prefix);
15801                   SendToICS("flag\n");
15802                 }
15803             } else {
15804                 if (blackFlag) {
15805                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15806                 } else {
15807                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15808                     if (appData.autoCallFlag) {
15809                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15810                         return TRUE;
15811                     }
15812                 }
15813             }
15814         }
15815     }
15816     if (blackTimeRemaining <= 0) {
15817         if (!blackFlag) {
15818             blackFlag = TRUE;
15819             if (appData.icsActive) {
15820                 if (appData.autoCallFlag &&
15821                     gameMode == IcsPlayingWhite && !whiteFlag) {
15822                   SendToICS(ics_prefix);
15823                   SendToICS("flag\n");
15824                 }
15825             } else {
15826                 if (whiteFlag) {
15827                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15828                 } else {
15829                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15830                     if (appData.autoCallFlag) {
15831                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15832                         return TRUE;
15833                     }
15834                 }
15835             }
15836         }
15837     }
15838     return FALSE;
15839 }
15840
15841 void
15842 CheckTimeControl()
15843 {
15844     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15845         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15846
15847     /*
15848      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15849      */
15850     if ( !WhiteOnMove(forwardMostMove) ) {
15851         /* White made time control */
15852         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15853         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15854         /* [HGM] time odds: correct new time quota for time odds! */
15855                                             / WhitePlayer()->timeOdds;
15856         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15857     } else {
15858         lastBlack -= blackTimeRemaining;
15859         /* Black made time control */
15860         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15861                                             / WhitePlayer()->other->timeOdds;
15862         lastWhite = whiteTimeRemaining;
15863     }
15864 }
15865
15866 void
15867 DisplayBothClocks()
15868 {
15869     int wom = gameMode == EditPosition ?
15870       !blackPlaysFirst : WhiteOnMove(currentMove);
15871     DisplayWhiteClock(whiteTimeRemaining, wom);
15872     DisplayBlackClock(blackTimeRemaining, !wom);
15873 }
15874
15875
15876 /* Timekeeping seems to be a portability nightmare.  I think everyone
15877    has ftime(), but I'm really not sure, so I'm including some ifdefs
15878    to use other calls if you don't.  Clocks will be less accurate if
15879    you have neither ftime nor gettimeofday.
15880 */
15881
15882 /* VS 2008 requires the #include outside of the function */
15883 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15884 #include <sys/timeb.h>
15885 #endif
15886
15887 /* Get the current time as a TimeMark */
15888 void
15889 GetTimeMark(tm)
15890      TimeMark *tm;
15891 {
15892 #if HAVE_GETTIMEOFDAY
15893
15894     struct timeval timeVal;
15895     struct timezone timeZone;
15896
15897     gettimeofday(&timeVal, &timeZone);
15898     tm->sec = (long) timeVal.tv_sec;
15899     tm->ms = (int) (timeVal.tv_usec / 1000L);
15900
15901 #else /*!HAVE_GETTIMEOFDAY*/
15902 #if HAVE_FTIME
15903
15904 // include <sys/timeb.h> / moved to just above start of function
15905     struct timeb timeB;
15906
15907     ftime(&timeB);
15908     tm->sec = (long) timeB.time;
15909     tm->ms = (int) timeB.millitm;
15910
15911 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15912     tm->sec = (long) time(NULL);
15913     tm->ms = 0;
15914 #endif
15915 #endif
15916 }
15917
15918 /* Return the difference in milliseconds between two
15919    time marks.  We assume the difference will fit in a long!
15920 */
15921 long
15922 SubtractTimeMarks(tm2, tm1)
15923      TimeMark *tm2, *tm1;
15924 {
15925     return 1000L*(tm2->sec - tm1->sec) +
15926            (long) (tm2->ms - tm1->ms);
15927 }
15928
15929
15930 /*
15931  * Code to manage the game clocks.
15932  *
15933  * In tournament play, black starts the clock and then white makes a move.
15934  * We give the human user a slight advantage if he is playing white---the
15935  * clocks don't run until he makes his first move, so it takes zero time.
15936  * Also, we don't account for network lag, so we could get out of sync
15937  * with GNU Chess's clock -- but then, referees are always right.
15938  */
15939
15940 static TimeMark tickStartTM;
15941 static long intendedTickLength;
15942
15943 long
15944 NextTickLength(timeRemaining)
15945      long timeRemaining;
15946 {
15947     long nominalTickLength, nextTickLength;
15948
15949     if (timeRemaining > 0L && timeRemaining <= 10000L)
15950       nominalTickLength = 100L;
15951     else
15952       nominalTickLength = 1000L;
15953     nextTickLength = timeRemaining % nominalTickLength;
15954     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15955
15956     return nextTickLength;
15957 }
15958
15959 /* Adjust clock one minute up or down */
15960 void
15961 AdjustClock(Boolean which, int dir)
15962 {
15963     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15964     if(which) blackTimeRemaining += 60000*dir;
15965     else      whiteTimeRemaining += 60000*dir;
15966     DisplayBothClocks();
15967     adjustedClock = TRUE;
15968 }
15969
15970 /* Stop clocks and reset to a fresh time control */
15971 void
15972 ResetClocks()
15973 {
15974     (void) StopClockTimer();
15975     if (appData.icsActive) {
15976         whiteTimeRemaining = blackTimeRemaining = 0;
15977     } else if (searchTime) {
15978         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15979         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15980     } else { /* [HGM] correct new time quote for time odds */
15981         whiteTC = blackTC = fullTimeControlString;
15982         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15983         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15984     }
15985     if (whiteFlag || blackFlag) {
15986         DisplayTitle("");
15987         whiteFlag = blackFlag = FALSE;
15988     }
15989     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15990     DisplayBothClocks();
15991     adjustedClock = FALSE;
15992 }
15993
15994 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15995
15996 /* Decrement running clock by amount of time that has passed */
15997 void
15998 DecrementClocks()
15999 {
16000     long timeRemaining;
16001     long lastTickLength, fudge;
16002     TimeMark now;
16003
16004     if (!appData.clockMode) return;
16005     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16006
16007     GetTimeMark(&now);
16008
16009     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16010
16011     /* Fudge if we woke up a little too soon */
16012     fudge = intendedTickLength - lastTickLength;
16013     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16014
16015     if (WhiteOnMove(forwardMostMove)) {
16016         if(whiteNPS >= 0) lastTickLength = 0;
16017         timeRemaining = whiteTimeRemaining -= lastTickLength;
16018         if(timeRemaining < 0 && !appData.icsActive) {
16019             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16020             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16021                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16022                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16023             }
16024         }
16025         DisplayWhiteClock(whiteTimeRemaining - fudge,
16026                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16027     } else {
16028         if(blackNPS >= 0) lastTickLength = 0;
16029         timeRemaining = blackTimeRemaining -= lastTickLength;
16030         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16031             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16032             if(suddenDeath) {
16033                 blackStartMove = forwardMostMove;
16034                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16035             }
16036         }
16037         DisplayBlackClock(blackTimeRemaining - fudge,
16038                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16039     }
16040     if (CheckFlags()) return;
16041
16042     tickStartTM = now;
16043     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16044     StartClockTimer(intendedTickLength);
16045
16046     /* if the time remaining has fallen below the alarm threshold, sound the
16047      * alarm. if the alarm has sounded and (due to a takeback or time control
16048      * with increment) the time remaining has increased to a level above the
16049      * threshold, reset the alarm so it can sound again.
16050      */
16051
16052     if (appData.icsActive && appData.icsAlarm) {
16053
16054         /* make sure we are dealing with the user's clock */
16055         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16056                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16057            )) return;
16058
16059         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16060             alarmSounded = FALSE;
16061         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16062             PlayAlarmSound();
16063             alarmSounded = TRUE;
16064         }
16065     }
16066 }
16067
16068
16069 /* A player has just moved, so stop the previously running
16070    clock and (if in clock mode) start the other one.
16071    We redisplay both clocks in case we're in ICS mode, because
16072    ICS gives us an update to both clocks after every move.
16073    Note that this routine is called *after* forwardMostMove
16074    is updated, so the last fractional tick must be subtracted
16075    from the color that is *not* on move now.
16076 */
16077 void
16078 SwitchClocks(int newMoveNr)
16079 {
16080     long lastTickLength;
16081     TimeMark now;
16082     int flagged = FALSE;
16083
16084     GetTimeMark(&now);
16085
16086     if (StopClockTimer() && appData.clockMode) {
16087         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16088         if (!WhiteOnMove(forwardMostMove)) {
16089             if(blackNPS >= 0) lastTickLength = 0;
16090             blackTimeRemaining -= lastTickLength;
16091            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16092 //         if(pvInfoList[forwardMostMove].time == -1)
16093                  pvInfoList[forwardMostMove].time =               // use GUI time
16094                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16095         } else {
16096            if(whiteNPS >= 0) lastTickLength = 0;
16097            whiteTimeRemaining -= lastTickLength;
16098            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16099 //         if(pvInfoList[forwardMostMove].time == -1)
16100                  pvInfoList[forwardMostMove].time =
16101                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16102         }
16103         flagged = CheckFlags();
16104     }
16105     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16106     CheckTimeControl();
16107
16108     if (flagged || !appData.clockMode) return;
16109
16110     switch (gameMode) {
16111       case MachinePlaysBlack:
16112       case MachinePlaysWhite:
16113       case BeginningOfGame:
16114         if (pausing) return;
16115         break;
16116
16117       case EditGame:
16118       case PlayFromGameFile:
16119       case IcsExamining:
16120         return;
16121
16122       default:
16123         break;
16124     }
16125
16126     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16127         if(WhiteOnMove(forwardMostMove))
16128              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16129         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16130     }
16131
16132     tickStartTM = now;
16133     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16134       whiteTimeRemaining : blackTimeRemaining);
16135     StartClockTimer(intendedTickLength);
16136 }
16137
16138
16139 /* Stop both clocks */
16140 void
16141 StopClocks()
16142 {
16143     long lastTickLength;
16144     TimeMark now;
16145
16146     if (!StopClockTimer()) return;
16147     if (!appData.clockMode) return;
16148
16149     GetTimeMark(&now);
16150
16151     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16152     if (WhiteOnMove(forwardMostMove)) {
16153         if(whiteNPS >= 0) lastTickLength = 0;
16154         whiteTimeRemaining -= lastTickLength;
16155         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16156     } else {
16157         if(blackNPS >= 0) lastTickLength = 0;
16158         blackTimeRemaining -= lastTickLength;
16159         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16160     }
16161     CheckFlags();
16162 }
16163
16164 /* Start clock of player on move.  Time may have been reset, so
16165    if clock is already running, stop and restart it. */
16166 void
16167 StartClocks()
16168 {
16169     (void) StopClockTimer(); /* in case it was running already */
16170     DisplayBothClocks();
16171     if (CheckFlags()) return;
16172
16173     if (!appData.clockMode) return;
16174     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16175
16176     GetTimeMark(&tickStartTM);
16177     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16178       whiteTimeRemaining : blackTimeRemaining);
16179
16180    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16181     whiteNPS = blackNPS = -1;
16182     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16183        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16184         whiteNPS = first.nps;
16185     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16186        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16187         blackNPS = first.nps;
16188     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16189         whiteNPS = second.nps;
16190     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16191         blackNPS = second.nps;
16192     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16193
16194     StartClockTimer(intendedTickLength);
16195 }
16196
16197 char *
16198 TimeString(ms)
16199      long ms;
16200 {
16201     long second, minute, hour, day;
16202     char *sign = "";
16203     static char buf[32];
16204
16205     if (ms > 0 && ms <= 9900) {
16206       /* convert milliseconds to tenths, rounding up */
16207       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16208
16209       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16210       return buf;
16211     }
16212
16213     /* convert milliseconds to seconds, rounding up */
16214     /* use floating point to avoid strangeness of integer division
16215        with negative dividends on many machines */
16216     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16217
16218     if (second < 0) {
16219         sign = "-";
16220         second = -second;
16221     }
16222
16223     day = second / (60 * 60 * 24);
16224     second = second % (60 * 60 * 24);
16225     hour = second / (60 * 60);
16226     second = second % (60 * 60);
16227     minute = second / 60;
16228     second = second % 60;
16229
16230     if (day > 0)
16231       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16232               sign, day, hour, minute, second);
16233     else if (hour > 0)
16234       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16235     else
16236       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16237
16238     return buf;
16239 }
16240
16241
16242 /*
16243  * This is necessary because some C libraries aren't ANSI C compliant yet.
16244  */
16245 char *
16246 StrStr(string, match)
16247      char *string, *match;
16248 {
16249     int i, length;
16250
16251     length = strlen(match);
16252
16253     for (i = strlen(string) - length; i >= 0; i--, string++)
16254       if (!strncmp(match, string, length))
16255         return string;
16256
16257     return NULL;
16258 }
16259
16260 char *
16261 StrCaseStr(string, match)
16262      char *string, *match;
16263 {
16264     int i, j, length;
16265
16266     length = strlen(match);
16267
16268     for (i = strlen(string) - length; i >= 0; i--, string++) {
16269         for (j = 0; j < length; j++) {
16270             if (ToLower(match[j]) != ToLower(string[j]))
16271               break;
16272         }
16273         if (j == length) return string;
16274     }
16275
16276     return NULL;
16277 }
16278
16279 #ifndef _amigados
16280 int
16281 StrCaseCmp(s1, s2)
16282      char *s1, *s2;
16283 {
16284     char c1, c2;
16285
16286     for (;;) {
16287         c1 = ToLower(*s1++);
16288         c2 = ToLower(*s2++);
16289         if (c1 > c2) return 1;
16290         if (c1 < c2) return -1;
16291         if (c1 == NULLCHAR) return 0;
16292     }
16293 }
16294
16295
16296 int
16297 ToLower(c)
16298      int c;
16299 {
16300     return isupper(c) ? tolower(c) : c;
16301 }
16302
16303
16304 int
16305 ToUpper(c)
16306      int c;
16307 {
16308     return islower(c) ? toupper(c) : c;
16309 }
16310 #endif /* !_amigados    */
16311
16312 char *
16313 StrSave(s)
16314      char *s;
16315 {
16316   char *ret;
16317
16318   if ((ret = (char *) malloc(strlen(s) + 1)))
16319     {
16320       safeStrCpy(ret, s, strlen(s)+1);
16321     }
16322   return ret;
16323 }
16324
16325 char *
16326 StrSavePtr(s, savePtr)
16327      char *s, **savePtr;
16328 {
16329     if (*savePtr) {
16330         free(*savePtr);
16331     }
16332     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16333       safeStrCpy(*savePtr, s, strlen(s)+1);
16334     }
16335     return(*savePtr);
16336 }
16337
16338 char *
16339 PGNDate()
16340 {
16341     time_t clock;
16342     struct tm *tm;
16343     char buf[MSG_SIZ];
16344
16345     clock = time((time_t *)NULL);
16346     tm = localtime(&clock);
16347     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16348             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16349     return StrSave(buf);
16350 }
16351
16352
16353 char *
16354 PositionToFEN(move, overrideCastling)
16355      int move;
16356      char *overrideCastling;
16357 {
16358     int i, j, fromX, fromY, toX, toY;
16359     int whiteToPlay;
16360     char buf[MSG_SIZ];
16361     char *p, *q;
16362     int emptycount;
16363     ChessSquare piece;
16364
16365     whiteToPlay = (gameMode == EditPosition) ?
16366       !blackPlaysFirst : (move % 2 == 0);
16367     p = buf;
16368
16369     /* Piece placement data */
16370     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16371         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16372         emptycount = 0;
16373         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16374             if (boards[move][i][j] == EmptySquare) {
16375                 emptycount++;
16376             } else { ChessSquare piece = boards[move][i][j];
16377                 if (emptycount > 0) {
16378                     if(emptycount<10) /* [HGM] can be >= 10 */
16379                         *p++ = '0' + emptycount;
16380                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16381                     emptycount = 0;
16382                 }
16383                 if(PieceToChar(piece) == '+') {
16384                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16385                     *p++ = '+';
16386                     piece = (ChessSquare)(DEMOTED piece);
16387                 }
16388                 *p++ = PieceToChar(piece);
16389                 if(p[-1] == '~') {
16390                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16391                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16392                     *p++ = '~';
16393                 }
16394             }
16395         }
16396         if (emptycount > 0) {
16397             if(emptycount<10) /* [HGM] can be >= 10 */
16398                 *p++ = '0' + emptycount;
16399             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16400             emptycount = 0;
16401         }
16402         *p++ = '/';
16403     }
16404     *(p - 1) = ' ';
16405
16406     /* [HGM] print Crazyhouse or Shogi holdings */
16407     if( gameInfo.holdingsWidth ) {
16408         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16409         q = p;
16410         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16411             piece = boards[move][i][BOARD_WIDTH-1];
16412             if( piece != EmptySquare )
16413               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16414                   *p++ = PieceToChar(piece);
16415         }
16416         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16417             piece = boards[move][BOARD_HEIGHT-i-1][0];
16418             if( piece != EmptySquare )
16419               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16420                   *p++ = PieceToChar(piece);
16421         }
16422
16423         if( q == p ) *p++ = '-';
16424         *p++ = ']';
16425         *p++ = ' ';
16426     }
16427
16428     /* Active color */
16429     *p++ = whiteToPlay ? 'w' : 'b';
16430     *p++ = ' ';
16431
16432   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16433     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16434   } else {
16435   if(nrCastlingRights) {
16436      q = p;
16437      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16438        /* [HGM] write directly from rights */
16439            if(boards[move][CASTLING][2] != NoRights &&
16440               boards[move][CASTLING][0] != NoRights   )
16441                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16442            if(boards[move][CASTLING][2] != NoRights &&
16443               boards[move][CASTLING][1] != NoRights   )
16444                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16445            if(boards[move][CASTLING][5] != NoRights &&
16446               boards[move][CASTLING][3] != NoRights   )
16447                 *p++ = boards[move][CASTLING][3] + AAA;
16448            if(boards[move][CASTLING][5] != NoRights &&
16449               boards[move][CASTLING][4] != NoRights   )
16450                 *p++ = boards[move][CASTLING][4] + AAA;
16451      } else {
16452
16453         /* [HGM] write true castling rights */
16454         if( nrCastlingRights == 6 ) {
16455             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16456                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16457             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16458                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16459             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16460                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16461             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16462                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16463         }
16464      }
16465      if (q == p) *p++ = '-'; /* No castling rights */
16466      *p++ = ' ';
16467   }
16468
16469   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16470      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16471     /* En passant target square */
16472     if (move > backwardMostMove) {
16473         fromX = moveList[move - 1][0] - AAA;
16474         fromY = moveList[move - 1][1] - ONE;
16475         toX = moveList[move - 1][2] - AAA;
16476         toY = moveList[move - 1][3] - ONE;
16477         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16478             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16479             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16480             fromX == toX) {
16481             /* 2-square pawn move just happened */
16482             *p++ = toX + AAA;
16483             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16484         } else {
16485             *p++ = '-';
16486         }
16487     } else if(move == backwardMostMove) {
16488         // [HGM] perhaps we should always do it like this, and forget the above?
16489         if((signed char)boards[move][EP_STATUS] >= 0) {
16490             *p++ = boards[move][EP_STATUS] + AAA;
16491             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16492         } else {
16493             *p++ = '-';
16494         }
16495     } else {
16496         *p++ = '-';
16497     }
16498     *p++ = ' ';
16499   }
16500   }
16501
16502     /* [HGM] find reversible plies */
16503     {   int i = 0, j=move;
16504
16505         if (appData.debugMode) { int k;
16506             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16507             for(k=backwardMostMove; k<=forwardMostMove; k++)
16508                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16509
16510         }
16511
16512         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16513         if( j == backwardMostMove ) i += initialRulePlies;
16514         sprintf(p, "%d ", i);
16515         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16516     }
16517     /* Fullmove number */
16518     sprintf(p, "%d", (move / 2) + 1);
16519
16520     return StrSave(buf);
16521 }
16522
16523 Boolean
16524 ParseFEN(board, blackPlaysFirst, fen)
16525     Board board;
16526      int *blackPlaysFirst;
16527      char *fen;
16528 {
16529     int i, j;
16530     char *p, c;
16531     int emptycount;
16532     ChessSquare piece;
16533
16534     p = fen;
16535
16536     /* [HGM] by default clear Crazyhouse holdings, if present */
16537     if(gameInfo.holdingsWidth) {
16538        for(i=0; i<BOARD_HEIGHT; i++) {
16539            board[i][0]             = EmptySquare; /* black holdings */
16540            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16541            board[i][1]             = (ChessSquare) 0; /* black counts */
16542            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16543        }
16544     }
16545
16546     /* Piece placement data */
16547     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16548         j = 0;
16549         for (;;) {
16550             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16551                 if (*p == '/') p++;
16552                 emptycount = gameInfo.boardWidth - j;
16553                 while (emptycount--)
16554                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16555                 break;
16556 #if(BOARD_FILES >= 10)
16557             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16558                 p++; emptycount=10;
16559                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16560                 while (emptycount--)
16561                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16562 #endif
16563             } else if (isdigit(*p)) {
16564                 emptycount = *p++ - '0';
16565                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16566                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16567                 while (emptycount--)
16568                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16569             } else if (*p == '+' || isalpha(*p)) {
16570                 if (j >= gameInfo.boardWidth) return FALSE;
16571                 if(*p=='+') {
16572                     piece = CharToPiece(*++p);
16573                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16574                     piece = (ChessSquare) (PROMOTED piece ); p++;
16575                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16576                 } else piece = CharToPiece(*p++);
16577
16578                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16579                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16580                     piece = (ChessSquare) (PROMOTED piece);
16581                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16582                     p++;
16583                 }
16584                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16585             } else {
16586                 return FALSE;
16587             }
16588         }
16589     }
16590     while (*p == '/' || *p == ' ') p++;
16591
16592     /* [HGM] look for Crazyhouse holdings here */
16593     while(*p==' ') p++;
16594     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16595         if(*p == '[') p++;
16596         if(*p == '-' ) p++; /* empty holdings */ else {
16597             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16598             /* if we would allow FEN reading to set board size, we would   */
16599             /* have to add holdings and shift the board read so far here   */
16600             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16601                 p++;
16602                 if((int) piece >= (int) BlackPawn ) {
16603                     i = (int)piece - (int)BlackPawn;
16604                     i = PieceToNumber((ChessSquare)i);
16605                     if( i >= gameInfo.holdingsSize ) return FALSE;
16606                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16607                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16608                 } else {
16609                     i = (int)piece - (int)WhitePawn;
16610                     i = PieceToNumber((ChessSquare)i);
16611                     if( i >= gameInfo.holdingsSize ) return FALSE;
16612                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16613                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16614                 }
16615             }
16616         }
16617         if(*p == ']') p++;
16618     }
16619
16620     while(*p == ' ') p++;
16621
16622     /* Active color */
16623     c = *p++;
16624     if(appData.colorNickNames) {
16625       if( c == appData.colorNickNames[0] ) c = 'w'; else
16626       if( c == appData.colorNickNames[1] ) c = 'b';
16627     }
16628     switch (c) {
16629       case 'w':
16630         *blackPlaysFirst = FALSE;
16631         break;
16632       case 'b':
16633         *blackPlaysFirst = TRUE;
16634         break;
16635       default:
16636         return FALSE;
16637     }
16638
16639     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16640     /* return the extra info in global variiables             */
16641
16642     /* set defaults in case FEN is incomplete */
16643     board[EP_STATUS] = EP_UNKNOWN;
16644     for(i=0; i<nrCastlingRights; i++ ) {
16645         board[CASTLING][i] =
16646             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16647     }   /* assume possible unless obviously impossible */
16648     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16649     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16650     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16651                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16652     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16653     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16654     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16655                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16656     FENrulePlies = 0;
16657
16658     while(*p==' ') p++;
16659     if(nrCastlingRights) {
16660       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16661           /* castling indicator present, so default becomes no castlings */
16662           for(i=0; i<nrCastlingRights; i++ ) {
16663                  board[CASTLING][i] = NoRights;
16664           }
16665       }
16666       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16667              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16668              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16669              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16670         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16671
16672         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16673             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16674             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16675         }
16676         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16677             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16678         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16679                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16680         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16681                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16682         switch(c) {
16683           case'K':
16684               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16685               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16686               board[CASTLING][2] = whiteKingFile;
16687               break;
16688           case'Q':
16689               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16690               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16691               board[CASTLING][2] = whiteKingFile;
16692               break;
16693           case'k':
16694               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16695               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16696               board[CASTLING][5] = blackKingFile;
16697               break;
16698           case'q':
16699               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16700               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16701               board[CASTLING][5] = blackKingFile;
16702           case '-':
16703               break;
16704           default: /* FRC castlings */
16705               if(c >= 'a') { /* black rights */
16706                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16707                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16708                   if(i == BOARD_RGHT) break;
16709                   board[CASTLING][5] = i;
16710                   c -= AAA;
16711                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16712                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16713                   if(c > i)
16714                       board[CASTLING][3] = c;
16715                   else
16716                       board[CASTLING][4] = c;
16717               } else { /* white rights */
16718                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16719                     if(board[0][i] == WhiteKing) break;
16720                   if(i == BOARD_RGHT) break;
16721                   board[CASTLING][2] = i;
16722                   c -= AAA - 'a' + 'A';
16723                   if(board[0][c] >= WhiteKing) break;
16724                   if(c > i)
16725                       board[CASTLING][0] = c;
16726                   else
16727                       board[CASTLING][1] = c;
16728               }
16729         }
16730       }
16731       for(i=0; i<nrCastlingRights; i++)
16732         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16733     if (appData.debugMode) {
16734         fprintf(debugFP, "FEN castling rights:");
16735         for(i=0; i<nrCastlingRights; i++)
16736         fprintf(debugFP, " %d", board[CASTLING][i]);
16737         fprintf(debugFP, "\n");
16738     }
16739
16740       while(*p==' ') p++;
16741     }
16742
16743     /* read e.p. field in games that know e.p. capture */
16744     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16745        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16746       if(*p=='-') {
16747         p++; board[EP_STATUS] = EP_NONE;
16748       } else {
16749          char c = *p++ - AAA;
16750
16751          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16752          if(*p >= '0' && *p <='9') p++;
16753          board[EP_STATUS] = c;
16754       }
16755     }
16756
16757
16758     if(sscanf(p, "%d", &i) == 1) {
16759         FENrulePlies = i; /* 50-move ply counter */
16760         /* (The move number is still ignored)    */
16761     }
16762
16763     return TRUE;
16764 }
16765
16766 void
16767 EditPositionPasteFEN(char *fen)
16768 {
16769   if (fen != NULL) {
16770     Board initial_position;
16771
16772     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16773       DisplayError(_("Bad FEN position in clipboard"), 0);
16774       return ;
16775     } else {
16776       int savedBlackPlaysFirst = blackPlaysFirst;
16777       EditPositionEvent();
16778       blackPlaysFirst = savedBlackPlaysFirst;
16779       CopyBoard(boards[0], initial_position);
16780       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16781       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16782       DisplayBothClocks();
16783       DrawPosition(FALSE, boards[currentMove]);
16784     }
16785   }
16786 }
16787
16788 static char cseq[12] = "\\   ";
16789
16790 Boolean set_cont_sequence(char *new_seq)
16791 {
16792     int len;
16793     Boolean ret;
16794
16795     // handle bad attempts to set the sequence
16796         if (!new_seq)
16797                 return 0; // acceptable error - no debug
16798
16799     len = strlen(new_seq);
16800     ret = (len > 0) && (len < sizeof(cseq));
16801     if (ret)
16802       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16803     else if (appData.debugMode)
16804       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16805     return ret;
16806 }
16807
16808 /*
16809     reformat a source message so words don't cross the width boundary.  internal
16810     newlines are not removed.  returns the wrapped size (no null character unless
16811     included in source message).  If dest is NULL, only calculate the size required
16812     for the dest buffer.  lp argument indicats line position upon entry, and it's
16813     passed back upon exit.
16814 */
16815 int wrap(char *dest, char *src, int count, int width, int *lp)
16816 {
16817     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16818
16819     cseq_len = strlen(cseq);
16820     old_line = line = *lp;
16821     ansi = len = clen = 0;
16822
16823     for (i=0; i < count; i++)
16824     {
16825         if (src[i] == '\033')
16826             ansi = 1;
16827
16828         // if we hit the width, back up
16829         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16830         {
16831             // store i & len in case the word is too long
16832             old_i = i, old_len = len;
16833
16834             // find the end of the last word
16835             while (i && src[i] != ' ' && src[i] != '\n')
16836             {
16837                 i--;
16838                 len--;
16839             }
16840
16841             // word too long?  restore i & len before splitting it
16842             if ((old_i-i+clen) >= width)
16843             {
16844                 i = old_i;
16845                 len = old_len;
16846             }
16847
16848             // extra space?
16849             if (i && src[i-1] == ' ')
16850                 len--;
16851
16852             if (src[i] != ' ' && src[i] != '\n')
16853             {
16854                 i--;
16855                 if (len)
16856                     len--;
16857             }
16858
16859             // now append the newline and continuation sequence
16860             if (dest)
16861                 dest[len] = '\n';
16862             len++;
16863             if (dest)
16864                 strncpy(dest+len, cseq, cseq_len);
16865             len += cseq_len;
16866             line = cseq_len;
16867             clen = cseq_len;
16868             continue;
16869         }
16870
16871         if (dest)
16872             dest[len] = src[i];
16873         len++;
16874         if (!ansi)
16875             line++;
16876         if (src[i] == '\n')
16877             line = 0;
16878         if (src[i] == 'm')
16879             ansi = 0;
16880     }
16881     if (dest && appData.debugMode)
16882     {
16883         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16884             count, width, line, len, *lp);
16885         show_bytes(debugFP, src, count);
16886         fprintf(debugFP, "\ndest: ");
16887         show_bytes(debugFP, dest, len);
16888         fprintf(debugFP, "\n");
16889     }
16890     *lp = dest ? line : old_line;
16891
16892     return len;
16893 }
16894
16895 // [HGM] vari: routines for shelving variations
16896 Boolean modeRestore = FALSE;
16897
16898 void
16899 PushInner(int firstMove, int lastMove)
16900 {
16901         int i, j, nrMoves = lastMove - firstMove;
16902
16903         // push current tail of game on stack
16904         savedResult[storedGames] = gameInfo.result;
16905         savedDetails[storedGames] = gameInfo.resultDetails;
16906         gameInfo.resultDetails = NULL;
16907         savedFirst[storedGames] = firstMove;
16908         savedLast [storedGames] = lastMove;
16909         savedFramePtr[storedGames] = framePtr;
16910         framePtr -= nrMoves; // reserve space for the boards
16911         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16912             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16913             for(j=0; j<MOVE_LEN; j++)
16914                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16915             for(j=0; j<2*MOVE_LEN; j++)
16916                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16917             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16918             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16919             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16920             pvInfoList[firstMove+i-1].depth = 0;
16921             commentList[framePtr+i] = commentList[firstMove+i];
16922             commentList[firstMove+i] = NULL;
16923         }
16924
16925         storedGames++;
16926         forwardMostMove = firstMove; // truncate game so we can start variation
16927 }
16928
16929 void
16930 PushTail(int firstMove, int lastMove)
16931 {
16932         if(appData.icsActive) { // only in local mode
16933                 forwardMostMove = currentMove; // mimic old ICS behavior
16934                 return;
16935         }
16936         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16937
16938         PushInner(firstMove, lastMove);
16939         if(storedGames == 1) GreyRevert(FALSE);
16940         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16941 }
16942
16943 void
16944 PopInner(Boolean annotate)
16945 {
16946         int i, j, nrMoves;
16947         char buf[8000], moveBuf[20];
16948
16949         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16950         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16951         nrMoves = savedLast[storedGames] - currentMove;
16952         if(annotate) {
16953                 int cnt = 10;
16954                 if(!WhiteOnMove(currentMove))
16955                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16956                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16957                 for(i=currentMove; i<forwardMostMove; i++) {
16958                         if(WhiteOnMove(i))
16959                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16960                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16961                         strcat(buf, moveBuf);
16962                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16963                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16964                 }
16965                 strcat(buf, ")");
16966         }
16967         for(i=1; i<=nrMoves; i++) { // copy last variation back
16968             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16969             for(j=0; j<MOVE_LEN; j++)
16970                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16971             for(j=0; j<2*MOVE_LEN; j++)
16972                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16973             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16974             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16975             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16976             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16977             commentList[currentMove+i] = commentList[framePtr+i];
16978             commentList[framePtr+i] = NULL;
16979         }
16980         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16981         framePtr = savedFramePtr[storedGames];
16982         gameInfo.result = savedResult[storedGames];
16983         if(gameInfo.resultDetails != NULL) {
16984             free(gameInfo.resultDetails);
16985       }
16986         gameInfo.resultDetails = savedDetails[storedGames];
16987         forwardMostMove = currentMove + nrMoves;
16988 }
16989
16990 Boolean
16991 PopTail(Boolean annotate)
16992 {
16993         if(appData.icsActive) return FALSE; // only in local mode
16994         if(!storedGames) return FALSE; // sanity
16995         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16996
16997         PopInner(annotate);
16998         if(currentMove < forwardMostMove) ForwardEvent(); else
16999         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17000
17001         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17002         return TRUE;
17003 }
17004
17005 void
17006 CleanupTail()
17007 {       // remove all shelved variations
17008         int i;
17009         for(i=0; i<storedGames; i++) {
17010             if(savedDetails[i])
17011                 free(savedDetails[i]);
17012             savedDetails[i] = NULL;
17013         }
17014         for(i=framePtr; i<MAX_MOVES; i++) {
17015                 if(commentList[i]) free(commentList[i]);
17016                 commentList[i] = NULL;
17017         }
17018         framePtr = MAX_MOVES-1;
17019         storedGames = 0;
17020 }
17021
17022 void
17023 LoadVariation(int index, char *text)
17024 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17025         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17026         int level = 0, move;
17027
17028         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17029         // first find outermost bracketing variation
17030         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17031             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17032                 if(*p == '{') wait = '}'; else
17033                 if(*p == '[') wait = ']'; else
17034                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17035                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17036             }
17037             if(*p == wait) wait = NULLCHAR; // closing ]} found
17038             p++;
17039         }
17040         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17041         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17042         end[1] = NULLCHAR; // clip off comment beyond variation
17043         ToNrEvent(currentMove-1);
17044         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17045         // kludge: use ParsePV() to append variation to game
17046         move = currentMove;
17047         ParsePV(start, TRUE, TRUE);
17048         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17049         ClearPremoveHighlights();
17050         CommentPopDown();
17051         ToNrEvent(currentMove+1);
17052 }
17053