Add feature-override options
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 /* A point in time */
151 typedef struct {
152     long sec;  /* Assuming this is >= 32 bits */
153     int ms;    /* Assuming this is >= 16 bits */
154 } TimeMark;
155
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158                          char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160                       char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
172 void ShowMove P((int fromX, int fromY, int toX, int toY));
173 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
174                    /*char*/int promoChar));
175 void BackwardInner P((int target));
176 void ForwardInner P((int target));
177 int Adjudicate P((ChessProgramState *cps));
178 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
179 void EditPositionDone P((Boolean fakeRights));
180 void PrintOpponents P((FILE *fp));
181 void PrintPosition P((FILE *fp, int move));
182 void StartChessProgram P((ChessProgramState *cps));
183 void SendToProgram P((char *message, ChessProgramState *cps));
184 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
185 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
186                            char *buf, int count, int error));
187 void SendTimeControl P((ChessProgramState *cps,
188                         int mps, long tc, int inc, int sd, int st));
189 char *TimeControlTagValue P((void));
190 void Attention P((ChessProgramState *cps));
191 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
192 int ResurrectChessProgram P((void));
193 void DisplayComment P((int moveNumber, char *text));
194 void DisplayMove P((int moveNumber));
195
196 void ParseGameHistory P((char *game));
197 void ParseBoard12 P((char *string));
198 void KeepAlive P((void));
199 void StartClocks P((void));
200 void SwitchClocks P((int nr));
201 void StopClocks P((void));
202 void ResetClocks P((void));
203 char *PGNDate P((void));
204 void SetGameInfo P((void));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228 void NextMatchGame P((void));
229 int NextTourneyGame P((int nr, int *swap));
230 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
231 FILE *WriteTourneyFile P((char *results, FILE *f));
232 void DisplayTwoMachinesTitle P(());
233
234 #ifdef WIN32
235        extern void ConsoleCreate();
236 #endif
237
238 ChessProgramState *WhitePlayer();
239 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
240 int VerifyDisplayMode P(());
241
242 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
243 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
244 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
245 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
246 void ics_update_width P((int new_width));
247 extern char installDir[MSG_SIZ];
248 VariantClass startVariant; /* [HGM] nicks: initial variant */
249 Boolean abortMatch;
250
251 extern int tinyLayout, smallLayout;
252 ChessProgramStats programStats;
253 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
254 int endPV = -1;
255 static int exiting = 0; /* [HGM] moved to top */
256 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
257 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
258 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
259 int partnerHighlight[2];
260 Boolean partnerBoardValid = 0;
261 char partnerStatus[MSG_SIZ];
262 Boolean partnerUp;
263 Boolean originalFlip;
264 Boolean twoBoards = 0;
265 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
266 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
267 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
268 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
269 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
270 int opponentKibitzes;
271 int lastSavedGame; /* [HGM] save: ID of game */
272 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
273 extern int chatCount;
274 int chattingPartner;
275 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
276 char lastMsg[MSG_SIZ];
277 ChessSquare pieceSweep = EmptySquare;
278 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
279 int promoDefaultAltered;
280
281 /* States for ics_getting_history */
282 #define H_FALSE 0
283 #define H_REQUESTED 1
284 #define H_GOT_REQ_HEADER 2
285 #define H_GOT_UNREQ_HEADER 3
286 #define H_GETTING_MOVES 4
287 #define H_GOT_UNWANTED_HEADER 5
288
289 /* whosays values for GameEnds */
290 #define GE_ICS 0
291 #define GE_ENGINE 1
292 #define GE_PLAYER 2
293 #define GE_FILE 3
294 #define GE_XBOARD 4
295 #define GE_ENGINE1 5
296 #define GE_ENGINE2 6
297
298 /* Maximum number of games in a cmail message */
299 #define CMAIL_MAX_GAMES 20
300
301 /* Different types of move when calling RegisterMove */
302 #define CMAIL_MOVE   0
303 #define CMAIL_RESIGN 1
304 #define CMAIL_DRAW   2
305 #define CMAIL_ACCEPT 3
306
307 /* Different types of result to remember for each game */
308 #define CMAIL_NOT_RESULT 0
309 #define CMAIL_OLD_RESULT 1
310 #define CMAIL_NEW_RESULT 2
311
312 /* Telnet protocol constants */
313 #define TN_WILL 0373
314 #define TN_WONT 0374
315 #define TN_DO   0375
316 #define TN_DONT 0376
317 #define TN_IAC  0377
318 #define TN_ECHO 0001
319 #define TN_SGA  0003
320 #define TN_PORT 23
321
322 char*
323 safeStrCpy( char *dst, const char *src, size_t count )
324 { // [HGM] made safe
325   int i;
326   assert( dst != NULL );
327   assert( src != NULL );
328   assert( count > 0 );
329
330   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
331   if(  i == count && dst[count-1] != NULLCHAR)
332     {
333       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
334       if(appData.debugMode)
335       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
336     }
337
338   return dst;
339 }
340
341 /* Some compiler can't cast u64 to double
342  * This function do the job for us:
343
344  * We use the highest bit for cast, this only
345  * works if the highest bit is not
346  * in use (This should not happen)
347  *
348  * We used this for all compiler
349  */
350 double
351 u64ToDouble(u64 value)
352 {
353   double r;
354   u64 tmp = value & u64Const(0x7fffffffffffffff);
355   r = (double)(s64)tmp;
356   if (value & u64Const(0x8000000000000000))
357        r +=  9.2233720368547758080e18; /* 2^63 */
358  return r;
359 }
360
361 /* Fake up flags for now, as we aren't keeping track of castling
362    availability yet. [HGM] Change of logic: the flag now only
363    indicates the type of castlings allowed by the rule of the game.
364    The actual rights themselves are maintained in the array
365    castlingRights, as part of the game history, and are not probed
366    by this function.
367  */
368 int
369 PosFlags(index)
370 {
371   int flags = F_ALL_CASTLE_OK;
372   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
373   switch (gameInfo.variant) {
374   case VariantSuicide:
375     flags &= ~F_ALL_CASTLE_OK;
376   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
377     flags |= F_IGNORE_CHECK;
378   case VariantLosers:
379     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
380     break;
381   case VariantAtomic:
382     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
383     break;
384   case VariantKriegspiel:
385     flags |= F_KRIEGSPIEL_CAPTURE;
386     break;
387   case VariantCapaRandom:
388   case VariantFischeRandom:
389     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
390   case VariantNoCastle:
391   case VariantShatranj:
392   case VariantCourier:
393   case VariantMakruk:
394   case VariantGrand:
395     flags &= ~F_ALL_CASTLE_OK;
396     break;
397   default:
398     break;
399   }
400   return flags;
401 }
402
403 FILE *gameFileFP, *debugFP;
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second, pairing;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 int ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562
563 #if (BOARD_FILES>=10)
564 ChessSquare ShogiArray[2][BOARD_FILES] = {
565     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
566         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
567     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
568         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
569 };
570
571 ChessSquare XiangqiArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
573         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
575         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
576 };
577
578 ChessSquare CapablancaArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
580         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
582         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
583 };
584
585 ChessSquare GreatArray[2][BOARD_FILES] = {
586     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
587         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
588     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
589         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
590 };
591
592 ChessSquare JanusArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
594         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
595     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
596         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
597 };
598
599 ChessSquare GrandArray[2][BOARD_FILES] = {
600     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
601         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
602     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
603         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
604 };
605
606 #ifdef GOTHIC
607 ChessSquare GothicArray[2][BOARD_FILES] = {
608     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
609         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
610     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
611         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
612 };
613 #else // !GOTHIC
614 #define GothicArray CapablancaArray
615 #endif // !GOTHIC
616
617 #ifdef FALCON
618 ChessSquare FalconArray[2][BOARD_FILES] = {
619     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
620         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
621     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
622         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
623 };
624 #else // !FALCON
625 #define FalconArray CapablancaArray
626 #endif // !FALCON
627
628 #else // !(BOARD_FILES>=10)
629 #define XiangqiPosition FIDEArray
630 #define CapablancaArray FIDEArray
631 #define GothicArray FIDEArray
632 #define GreatArray FIDEArray
633 #endif // !(BOARD_FILES>=10)
634
635 #if (BOARD_FILES>=12)
636 ChessSquare CourierArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
638         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
639     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
640         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
641 };
642 #else // !(BOARD_FILES>=12)
643 #define CourierArray CapablancaArray
644 #endif // !(BOARD_FILES>=12)
645
646
647 Board initialPosition;
648
649
650 /* Convert str to a rating. Checks for special cases of "----",
651
652    "++++", etc. Also strips ()'s */
653 int
654 string_to_rating(str)
655   char *str;
656 {
657   while(*str && !isdigit(*str)) ++str;
658   if (!*str)
659     return 0;   /* One of the special "no rating" cases */
660   else
661     return atoi(str);
662 }
663
664 void
665 ClearProgramStats()
666 {
667     /* Init programStats */
668     programStats.movelist[0] = 0;
669     programStats.depth = 0;
670     programStats.nr_moves = 0;
671     programStats.moves_left = 0;
672     programStats.nodes = 0;
673     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
674     programStats.score = 0;
675     programStats.got_only_move = 0;
676     programStats.got_fail = 0;
677     programStats.line_is_book = 0;
678 }
679
680 void
681 CommonEngineInit()
682 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
683     if (appData.firstPlaysBlack) {
684         first.twoMachinesColor = "black\n";
685         second.twoMachinesColor = "white\n";
686     } else {
687         first.twoMachinesColor = "white\n";
688         second.twoMachinesColor = "black\n";
689     }
690
691     first.other = &second;
692     second.other = &first;
693
694     { float norm = 1;
695         if(appData.timeOddsMode) {
696             norm = appData.timeOdds[0];
697             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
698         }
699         first.timeOdds  = appData.timeOdds[0]/norm;
700         second.timeOdds = appData.timeOdds[1]/norm;
701     }
702
703     if(programVersion) free(programVersion);
704     if (appData.noChessProgram) {
705         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
706         sprintf(programVersion, "%s", PACKAGE_STRING);
707     } else {
708       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
709       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
710       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
711     }
712 }
713
714 void
715 UnloadEngine(ChessProgramState *cps)
716 {
717         /* Kill off first chess program */
718         if (cps->isr != NULL)
719           RemoveInputSource(cps->isr);
720         cps->isr = NULL;
721
722         if (cps->pr != NoProc) {
723             ExitAnalyzeMode();
724             DoSleep( appData.delayBeforeQuit );
725             SendToProgram("quit\n", cps);
726             DoSleep( appData.delayAfterQuit );
727             DestroyChildProcess(cps->pr, cps->useSigterm);
728         }
729         cps->pr = NoProc;
730         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
731 }
732
733 void
734 ClearOptions(ChessProgramState *cps)
735 {
736     int i;
737     cps->nrOptions = cps->comboCnt = 0;
738     for(i=0; i<MAX_OPTIONS; i++) {
739         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
740         cps->option[i].textValue = 0;
741     }
742 }
743
744 char *engineNames[] = {
745 "first",
746 "second"
747 };
748
749 void
750 InitEngine(ChessProgramState *cps, int n)
751 {   // [HGM] all engine initialiation put in a function that does one engine
752
753     ClearOptions(cps);
754
755     cps->which = engineNames[n];
756     cps->maybeThinking = FALSE;
757     cps->pr = NoProc;
758     cps->isr = NULL;
759     cps->sendTime = 2;
760     cps->sendDrawOffers = 1;
761
762     cps->program = appData.chessProgram[n];
763     cps->host = appData.host[n];
764     cps->dir = appData.directory[n];
765     cps->initString = appData.engInitString[n];
766     cps->computerString = appData.computerString[n];
767     cps->useSigint  = TRUE;
768     cps->useSigterm = TRUE;
769     cps->reuse = appData.reuse[n];
770     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
771     cps->useSetboard = FALSE;
772     cps->useSAN = FALSE;
773     cps->usePing = FALSE;
774     cps->lastPing = 0;
775     cps->lastPong = 0;
776     cps->usePlayother = FALSE;
777     cps->useColors = TRUE;
778     cps->useUsermove = FALSE;
779     cps->sendICS = FALSE;
780     cps->sendName = appData.icsActive;
781     cps->sdKludge = FALSE;
782     cps->stKludge = FALSE;
783     TidyProgramName(cps->program, cps->host, cps->tidy);
784     cps->matchWins = 0;
785     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786     cps->analysisSupport = 2; /* detect */
787     cps->analyzing = FALSE;
788     cps->initDone = FALSE;
789
790     /* New features added by Tord: */
791     cps->useFEN960 = FALSE;
792     cps->useOOCastle = TRUE;
793     /* End of new features added by Tord. */
794     cps->fenOverride  = appData.fenOverride[n];
795
796     /* [HGM] time odds: set factor for each machine */
797     cps->timeOdds  = appData.timeOdds[n];
798
799     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
800     cps->accumulateTC = appData.accumulateTC[n];
801     cps->maxNrOfSessions = 1;
802
803     /* [HGM] debug */
804     cps->debug = FALSE;
805
806     cps->supportsNPS = UNKNOWN;
807     cps->memSize = FALSE;
808     cps->maxCores = FALSE;
809     cps->egtFormats[0] = NULLCHAR;
810
811     /* [HGM] options */
812     cps->optionSettings  = appData.engOptions[n];
813
814     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
815     cps->isUCI = appData.isUCI[n]; /* [AS] */
816     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
817
818     if (appData.protocolVersion[n] > PROTOVER
819         || appData.protocolVersion[n] < 1)
820       {
821         char buf[MSG_SIZ];
822         int len;
823
824         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
825                        appData.protocolVersion[n]);
826         if( (len > MSG_SIZ) && appData.debugMode )
827           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
828
829         DisplayFatalError(buf, 0, 2);
830       }
831     else
832       {
833         cps->protocolVersion = appData.protocolVersion[n];
834       }
835
836     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
837     ParseFeatures(appData.featureDefaults, cps);
838 }
839
840 ChessProgramState *savCps;
841
842 void
843 LoadEngine()
844 {
845     int i;
846     if(WaitForEngine(savCps, LoadEngine)) return;
847     CommonEngineInit(); // recalculate time odds
848     if(gameInfo.variant != StringToVariant(appData.variant)) {
849         // we changed variant when loading the engine; this forces us to reset
850         Reset(TRUE, savCps != &first);
851         EditGameEvent(); // for consistency with other path, as Reset changes mode
852     }
853     InitChessProgram(savCps, FALSE);
854     SendToProgram("force\n", savCps);
855     DisplayMessage("", "");
856     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
857     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
858     ThawUI();
859     SetGNUMode();
860 }
861
862 void
863 ReplaceEngine(ChessProgramState *cps, int n)
864 {
865     EditGameEvent();
866     UnloadEngine(cps);
867     appData.noChessProgram = FALSE;
868     appData.clockMode = TRUE;
869     InitEngine(cps, n);
870     UpdateLogos(TRUE);
871     if(n) return; // only startup first engine immediately; second can wait
872     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
873     LoadEngine();
874 }
875
876 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
877 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
878
879 static char resetOptions[] = 
880         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
881         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
882         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
883
884 void
885 Load(ChessProgramState *cps, int i)
886 {
887     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
888     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
889         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
890         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
891         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
892         ParseArgsFromString(buf);
893         SwapEngines(i);
894         ReplaceEngine(cps, i);
895         return;
896     }
897     p = engineName;
898     while(q = strchr(p, SLASH)) p = q+1;
899     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
900     if(engineDir[0] != NULLCHAR)
901         appData.directory[i] = engineDir;
902     else if(p != engineName) { // derive directory from engine path, when not given
903         p[-1] = 0;
904         appData.directory[i] = strdup(engineName);
905         p[-1] = SLASH;
906     } else appData.directory[i] = ".";
907     if(params[0]) {
908         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
909         snprintf(command, MSG_SIZ, "%s %s", p, params);
910         p = command;
911     }
912     appData.chessProgram[i] = strdup(p);
913     appData.isUCI[i] = isUCI;
914     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
915     appData.hasOwnBookUCI[i] = hasBook;
916     if(!nickName[0]) useNick = FALSE;
917     if(useNick) ASSIGN(appData.pgnName[i], nickName);
918     if(addToList) {
919         int len;
920         char quote;
921         q = firstChessProgramNames;
922         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
923         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
924         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
925                         quote, p, quote, appData.directory[i], 
926                         useNick ? " -fn \"" : "",
927                         useNick ? nickName : "",
928                         useNick ? "\"" : "",
929                         v1 ? " -firstProtocolVersion 1" : "",
930                         hasBook ? "" : " -fNoOwnBookUCI",
931                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
932                         storeVariant ? " -variant " : "",
933                         storeVariant ? VariantName(gameInfo.variant) : "");
934         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
935         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
936         if(q)   free(q);
937     }
938     ReplaceEngine(cps, i);
939 }
940
941 void
942 InitTimeControls()
943 {
944     int matched, min, sec;
945     /*
946      * Parse timeControl resource
947      */
948     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
949                           appData.movesPerSession)) {
950         char buf[MSG_SIZ];
951         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
952         DisplayFatalError(buf, 0, 2);
953     }
954
955     /*
956      * Parse searchTime resource
957      */
958     if (*appData.searchTime != NULLCHAR) {
959         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
960         if (matched == 1) {
961             searchTime = min * 60;
962         } else if (matched == 2) {
963             searchTime = min * 60 + sec;
964         } else {
965             char buf[MSG_SIZ];
966             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
967             DisplayFatalError(buf, 0, 2);
968         }
969     }
970 }
971
972 void
973 InitBackEnd1()
974 {
975
976     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
977     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
978
979     GetTimeMark(&programStartTime);
980     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
981     appData.seedBase = random() + (random()<<15);
982     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
983
984     ClearProgramStats();
985     programStats.ok_to_send = 1;
986     programStats.seen_stat = 0;
987
988     /*
989      * Initialize game list
990      */
991     ListNew(&gameList);
992
993
994     /*
995      * Internet chess server status
996      */
997     if (appData.icsActive) {
998         appData.matchMode = FALSE;
999         appData.matchGames = 0;
1000 #if ZIPPY
1001         appData.noChessProgram = !appData.zippyPlay;
1002 #else
1003         appData.zippyPlay = FALSE;
1004         appData.zippyTalk = FALSE;
1005         appData.noChessProgram = TRUE;
1006 #endif
1007         if (*appData.icsHelper != NULLCHAR) {
1008             appData.useTelnet = TRUE;
1009             appData.telnetProgram = appData.icsHelper;
1010         }
1011     } else {
1012         appData.zippyTalk = appData.zippyPlay = FALSE;
1013     }
1014
1015     /* [AS] Initialize pv info list [HGM] and game state */
1016     {
1017         int i, j;
1018
1019         for( i=0; i<=framePtr; i++ ) {
1020             pvInfoList[i].depth = -1;
1021             boards[i][EP_STATUS] = EP_NONE;
1022             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1023         }
1024     }
1025
1026     InitTimeControls();
1027
1028     /* [AS] Adjudication threshold */
1029     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1030
1031     InitEngine(&first, 0);
1032     InitEngine(&second, 1);
1033     CommonEngineInit();
1034
1035     pairing.which = "pairing"; // pairing engine
1036     pairing.pr = NoProc;
1037     pairing.isr = NULL;
1038     pairing.program = appData.pairingEngine;
1039     pairing.host = "localhost";
1040     pairing.dir = ".";
1041
1042     if (appData.icsActive) {
1043         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1044     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1045         appData.clockMode = FALSE;
1046         first.sendTime = second.sendTime = 0;
1047     }
1048
1049 #if ZIPPY
1050     /* Override some settings from environment variables, for backward
1051        compatibility.  Unfortunately it's not feasible to have the env
1052        vars just set defaults, at least in xboard.  Ugh.
1053     */
1054     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1055       ZippyInit();
1056     }
1057 #endif
1058
1059     if (!appData.icsActive) {
1060       char buf[MSG_SIZ];
1061       int len;
1062
1063       /* Check for variants that are supported only in ICS mode,
1064          or not at all.  Some that are accepted here nevertheless
1065          have bugs; see comments below.
1066       */
1067       VariantClass variant = StringToVariant(appData.variant);
1068       switch (variant) {
1069       case VariantBughouse:     /* need four players and two boards */
1070       case VariantKriegspiel:   /* need to hide pieces and move details */
1071         /* case VariantFischeRandom: (Fabien: moved below) */
1072         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1073         if( (len > MSG_SIZ) && appData.debugMode )
1074           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1075
1076         DisplayFatalError(buf, 0, 2);
1077         return;
1078
1079       case VariantUnknown:
1080       case VariantLoadable:
1081       case Variant29:
1082       case Variant30:
1083       case Variant31:
1084       case Variant32:
1085       case Variant33:
1086       case Variant34:
1087       case Variant35:
1088       case Variant36:
1089       default:
1090         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1091         if( (len > MSG_SIZ) && appData.debugMode )
1092           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1093
1094         DisplayFatalError(buf, 0, 2);
1095         return;
1096
1097       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1098       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1099       case VariantGothic:     /* [HGM] should work */
1100       case VariantCapablanca: /* [HGM] should work */
1101       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1102       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1103       case VariantKnightmate: /* [HGM] should work */
1104       case VariantCylinder:   /* [HGM] untested */
1105       case VariantFalcon:     /* [HGM] untested */
1106       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1107                                  offboard interposition not understood */
1108       case VariantNormal:     /* definitely works! */
1109       case VariantWildCastle: /* pieces not automatically shuffled */
1110       case VariantNoCastle:   /* pieces not automatically shuffled */
1111       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1112       case VariantLosers:     /* should work except for win condition,
1113                                  and doesn't know captures are mandatory */
1114       case VariantSuicide:    /* should work except for win condition,
1115                                  and doesn't know captures are mandatory */
1116       case VariantGiveaway:   /* should work except for win condition,
1117                                  and doesn't know captures are mandatory */
1118       case VariantTwoKings:   /* should work */
1119       case VariantAtomic:     /* should work except for win condition */
1120       case Variant3Check:     /* should work except for win condition */
1121       case VariantShatranj:   /* should work except for all win conditions */
1122       case VariantMakruk:     /* should work except for draw countdown */
1123       case VariantBerolina:   /* might work if TestLegality is off */
1124       case VariantCapaRandom: /* should work */
1125       case VariantJanus:      /* should work */
1126       case VariantSuper:      /* experimental */
1127       case VariantGreat:      /* experimental, requires legality testing to be off */
1128       case VariantSChess:     /* S-Chess, should work */
1129       case VariantGrand:      /* should work */
1130       case VariantSpartan:    /* should work */
1131         break;
1132       }
1133     }
1134
1135 }
1136
1137 int NextIntegerFromString( char ** str, long * value )
1138 {
1139     int result = -1;
1140     char * s = *str;
1141
1142     while( *s == ' ' || *s == '\t' ) {
1143         s++;
1144     }
1145
1146     *value = 0;
1147
1148     if( *s >= '0' && *s <= '9' ) {
1149         while( *s >= '0' && *s <= '9' ) {
1150             *value = *value * 10 + (*s - '0');
1151             s++;
1152         }
1153
1154         result = 0;
1155     }
1156
1157     *str = s;
1158
1159     return result;
1160 }
1161
1162 int NextTimeControlFromString( char ** str, long * value )
1163 {
1164     long temp;
1165     int result = NextIntegerFromString( str, &temp );
1166
1167     if( result == 0 ) {
1168         *value = temp * 60; /* Minutes */
1169         if( **str == ':' ) {
1170             (*str)++;
1171             result = NextIntegerFromString( str, &temp );
1172             *value += temp; /* Seconds */
1173         }
1174     }
1175
1176     return result;
1177 }
1178
1179 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1180 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1181     int result = -1, type = 0; long temp, temp2;
1182
1183     if(**str != ':') return -1; // old params remain in force!
1184     (*str)++;
1185     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1186     if( NextIntegerFromString( str, &temp ) ) return -1;
1187     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1188
1189     if(**str != '/') {
1190         /* time only: incremental or sudden-death time control */
1191         if(**str == '+') { /* increment follows; read it */
1192             (*str)++;
1193             if(**str == '!') type = *(*str)++; // Bronstein TC
1194             if(result = NextIntegerFromString( str, &temp2)) return -1;
1195             *inc = temp2 * 1000;
1196             if(**str == '.') { // read fraction of increment
1197                 char *start = ++(*str);
1198                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1199                 temp2 *= 1000;
1200                 while(start++ < *str) temp2 /= 10;
1201                 *inc += temp2;
1202             }
1203         } else *inc = 0;
1204         *moves = 0; *tc = temp * 1000; *incType = type;
1205         return 0;
1206     }
1207
1208     (*str)++; /* classical time control */
1209     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1210
1211     if(result == 0) {
1212         *moves = temp;
1213         *tc    = temp2 * 1000;
1214         *inc   = 0;
1215         *incType = type;
1216     }
1217     return result;
1218 }
1219
1220 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1221 {   /* [HGM] get time to add from the multi-session time-control string */
1222     int incType, moves=1; /* kludge to force reading of first session */
1223     long time, increment;
1224     char *s = tcString;
1225
1226     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1227     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1228     do {
1229         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1230         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1231         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1232         if(movenr == -1) return time;    /* last move before new session     */
1233         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1234         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1235         if(!moves) return increment;     /* current session is incremental   */
1236         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1237     } while(movenr >= -1);               /* try again for next session       */
1238
1239     return 0; // no new time quota on this move
1240 }
1241
1242 int
1243 ParseTimeControl(tc, ti, mps)
1244      char *tc;
1245      float ti;
1246      int mps;
1247 {
1248   long tc1;
1249   long tc2;
1250   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1251   int min, sec=0;
1252
1253   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1254   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1255       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1256   if(ti > 0) {
1257
1258     if(mps)
1259       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1260     else 
1261       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1262   } else {
1263     if(mps)
1264       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1265     else 
1266       snprintf(buf, MSG_SIZ, ":%s", mytc);
1267   }
1268   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1269   
1270   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1271     return FALSE;
1272   }
1273
1274   if( *tc == '/' ) {
1275     /* Parse second time control */
1276     tc++;
1277
1278     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1279       return FALSE;
1280     }
1281
1282     if( tc2 == 0 ) {
1283       return FALSE;
1284     }
1285
1286     timeControl_2 = tc2 * 1000;
1287   }
1288   else {
1289     timeControl_2 = 0;
1290   }
1291
1292   if( tc1 == 0 ) {
1293     return FALSE;
1294   }
1295
1296   timeControl = tc1 * 1000;
1297
1298   if (ti >= 0) {
1299     timeIncrement = ti * 1000;  /* convert to ms */
1300     movesPerSession = 0;
1301   } else {
1302     timeIncrement = 0;
1303     movesPerSession = mps;
1304   }
1305   return TRUE;
1306 }
1307
1308 void
1309 InitBackEnd2()
1310 {
1311     if (appData.debugMode) {
1312         fprintf(debugFP, "%s\n", programVersion);
1313     }
1314
1315     set_cont_sequence(appData.wrapContSeq);
1316     if (appData.matchGames > 0) {
1317         appData.matchMode = TRUE;
1318     } else if (appData.matchMode) {
1319         appData.matchGames = 1;
1320     }
1321     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1322         appData.matchGames = appData.sameColorGames;
1323     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1324         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1325         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1326     }
1327     Reset(TRUE, FALSE);
1328     if (appData.noChessProgram || first.protocolVersion == 1) {
1329       InitBackEnd3();
1330     } else {
1331       /* kludge: allow timeout for initial "feature" commands */
1332       FreezeUI();
1333       DisplayMessage("", _("Starting chess program"));
1334       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1335     }
1336 }
1337
1338 int
1339 CalculateIndex(int index, int gameNr)
1340 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1341     int res;
1342     if(index > 0) return index; // fixed nmber
1343     if(index == 0) return 1;
1344     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1345     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1346     return res;
1347 }
1348
1349 int
1350 LoadGameOrPosition(int gameNr)
1351 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1352     if (*appData.loadGameFile != NULLCHAR) {
1353         if (!LoadGameFromFile(appData.loadGameFile,
1354                 CalculateIndex(appData.loadGameIndex, gameNr),
1355                               appData.loadGameFile, FALSE)) {
1356             DisplayFatalError(_("Bad game file"), 0, 1);
1357             return 0;
1358         }
1359     } else if (*appData.loadPositionFile != NULLCHAR) {
1360         if (!LoadPositionFromFile(appData.loadPositionFile,
1361                 CalculateIndex(appData.loadPositionIndex, gameNr),
1362                                   appData.loadPositionFile)) {
1363             DisplayFatalError(_("Bad position file"), 0, 1);
1364             return 0;
1365         }
1366     }
1367     return 1;
1368 }
1369
1370 void
1371 ReserveGame(int gameNr, char resChar)
1372 {
1373     FILE *tf = fopen(appData.tourneyFile, "r+");
1374     char *p, *q, c, buf[MSG_SIZ];
1375     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1376     safeStrCpy(buf, lastMsg, MSG_SIZ);
1377     DisplayMessage(_("Pick new game"), "");
1378     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1379     ParseArgsFromFile(tf);
1380     p = q = appData.results;
1381     if(appData.debugMode) {
1382       char *r = appData.participants;
1383       fprintf(debugFP, "results = '%s'\n", p);
1384       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1385       fprintf(debugFP, "\n");
1386     }
1387     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1388     nextGame = q - p;
1389     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1390     safeStrCpy(q, p, strlen(p) + 2);
1391     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1392     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1393     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1394         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1395         q[nextGame] = '*';
1396     }
1397     fseek(tf, -(strlen(p)+4), SEEK_END);
1398     c = fgetc(tf);
1399     if(c != '"') // depending on DOS or Unix line endings we can be one off
1400          fseek(tf, -(strlen(p)+2), SEEK_END);
1401     else fseek(tf, -(strlen(p)+3), SEEK_END);
1402     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1403     DisplayMessage(buf, "");
1404     free(p); appData.results = q;
1405     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1406        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1407         UnloadEngine(&first);  // next game belongs to other pairing;
1408         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1409     }
1410 }
1411
1412 void
1413 MatchEvent(int mode)
1414 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1415         int dummy;
1416         if(matchMode) { // already in match mode: switch it off
1417             abortMatch = TRUE;
1418             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1419             return;
1420         }
1421 //      if(gameMode != BeginningOfGame) {
1422 //          DisplayError(_("You can only start a match from the initial position."), 0);
1423 //          return;
1424 //      }
1425         abortMatch = FALSE;
1426         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1427         /* Set up machine vs. machine match */
1428         nextGame = 0;
1429         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1430         if(appData.tourneyFile[0]) {
1431             ReserveGame(-1, 0);
1432             if(nextGame > appData.matchGames) {
1433                 char buf[MSG_SIZ];
1434                 if(strchr(appData.results, '*') == NULL) {
1435                     FILE *f;
1436                     appData.tourneyCycles++;
1437                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1438                         fclose(f);
1439                         NextTourneyGame(-1, &dummy);
1440                         ReserveGame(-1, 0);
1441                         if(nextGame <= appData.matchGames) {
1442                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1443                             matchMode = mode;
1444                             ScheduleDelayedEvent(NextMatchGame, 10000);
1445                             return;
1446                         }
1447                     }
1448                 }
1449                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1450                 DisplayError(buf, 0);
1451                 appData.tourneyFile[0] = 0;
1452                 return;
1453             }
1454         } else
1455         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1456             DisplayFatalError(_("Can't have a match with no chess programs"),
1457                               0, 2);
1458             return;
1459         }
1460         matchMode = mode;
1461         matchGame = roundNr = 1;
1462         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1463         NextMatchGame();
1464 }
1465
1466 void
1467 InitBackEnd3 P((void))
1468 {
1469     GameMode initialMode;
1470     char buf[MSG_SIZ];
1471     int err, len;
1472
1473     InitChessProgram(&first, startedFromSetupPosition);
1474
1475     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1476         free(programVersion);
1477         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1478         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1479     }
1480
1481     if (appData.icsActive) {
1482 #ifdef WIN32
1483         /* [DM] Make a console window if needed [HGM] merged ifs */
1484         ConsoleCreate();
1485 #endif
1486         err = establish();
1487         if (err != 0)
1488           {
1489             if (*appData.icsCommPort != NULLCHAR)
1490               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1491                              appData.icsCommPort);
1492             else
1493               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1494                         appData.icsHost, appData.icsPort);
1495
1496             if( (len > MSG_SIZ) && appData.debugMode )
1497               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1498
1499             DisplayFatalError(buf, err, 1);
1500             return;
1501         }
1502         SetICSMode();
1503         telnetISR =
1504           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1505         fromUserISR =
1506           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1507         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1508             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1509     } else if (appData.noChessProgram) {
1510         SetNCPMode();
1511     } else {
1512         SetGNUMode();
1513     }
1514
1515     if (*appData.cmailGameName != NULLCHAR) {
1516         SetCmailMode();
1517         OpenLoopback(&cmailPR);
1518         cmailISR =
1519           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1520     }
1521
1522     ThawUI();
1523     DisplayMessage("", "");
1524     if (StrCaseCmp(appData.initialMode, "") == 0) {
1525       initialMode = BeginningOfGame;
1526       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1527         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1528         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1529         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1530         ModeHighlight();
1531       }
1532     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1533       initialMode = TwoMachinesPlay;
1534     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1535       initialMode = AnalyzeFile;
1536     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1537       initialMode = AnalyzeMode;
1538     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1539       initialMode = MachinePlaysWhite;
1540     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1541       initialMode = MachinePlaysBlack;
1542     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1543       initialMode = EditGame;
1544     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1545       initialMode = EditPosition;
1546     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1547       initialMode = Training;
1548     } else {
1549       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1550       if( (len > MSG_SIZ) && appData.debugMode )
1551         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1552
1553       DisplayFatalError(buf, 0, 2);
1554       return;
1555     }
1556
1557     if (appData.matchMode) {
1558         if(appData.tourneyFile[0]) { // start tourney from command line
1559             FILE *f;
1560             if(f = fopen(appData.tourneyFile, "r")) {
1561                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1562                 fclose(f);
1563                 appData.clockMode = TRUE;
1564                 SetGNUMode();
1565             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1566         }
1567         MatchEvent(TRUE);
1568     } else if (*appData.cmailGameName != NULLCHAR) {
1569         /* Set up cmail mode */
1570         ReloadCmailMsgEvent(TRUE);
1571     } else {
1572         /* Set up other modes */
1573         if (initialMode == AnalyzeFile) {
1574           if (*appData.loadGameFile == NULLCHAR) {
1575             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1576             return;
1577           }
1578         }
1579         if (*appData.loadGameFile != NULLCHAR) {
1580             (void) LoadGameFromFile(appData.loadGameFile,
1581                                     appData.loadGameIndex,
1582                                     appData.loadGameFile, TRUE);
1583         } else if (*appData.loadPositionFile != NULLCHAR) {
1584             (void) LoadPositionFromFile(appData.loadPositionFile,
1585                                         appData.loadPositionIndex,
1586                                         appData.loadPositionFile);
1587             /* [HGM] try to make self-starting even after FEN load */
1588             /* to allow automatic setup of fairy variants with wtm */
1589             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1590                 gameMode = BeginningOfGame;
1591                 setboardSpoiledMachineBlack = 1;
1592             }
1593             /* [HGM] loadPos: make that every new game uses the setup */
1594             /* from file as long as we do not switch variant          */
1595             if(!blackPlaysFirst) {
1596                 startedFromPositionFile = TRUE;
1597                 CopyBoard(filePosition, boards[0]);
1598             }
1599         }
1600         if (initialMode == AnalyzeMode) {
1601           if (appData.noChessProgram) {
1602             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1603             return;
1604           }
1605           if (appData.icsActive) {
1606             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1607             return;
1608           }
1609           AnalyzeModeEvent();
1610         } else if (initialMode == AnalyzeFile) {
1611           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1612           ShowThinkingEvent();
1613           AnalyzeFileEvent();
1614           AnalysisPeriodicEvent(1);
1615         } else if (initialMode == MachinePlaysWhite) {
1616           if (appData.noChessProgram) {
1617             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1618                               0, 2);
1619             return;
1620           }
1621           if (appData.icsActive) {
1622             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1623                               0, 2);
1624             return;
1625           }
1626           MachineWhiteEvent();
1627         } else if (initialMode == MachinePlaysBlack) {
1628           if (appData.noChessProgram) {
1629             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1630                               0, 2);
1631             return;
1632           }
1633           if (appData.icsActive) {
1634             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1635                               0, 2);
1636             return;
1637           }
1638           MachineBlackEvent();
1639         } else if (initialMode == TwoMachinesPlay) {
1640           if (appData.noChessProgram) {
1641             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1642                               0, 2);
1643             return;
1644           }
1645           if (appData.icsActive) {
1646             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1647                               0, 2);
1648             return;
1649           }
1650           TwoMachinesEvent();
1651         } else if (initialMode == EditGame) {
1652           EditGameEvent();
1653         } else if (initialMode == EditPosition) {
1654           EditPositionEvent();
1655         } else if (initialMode == Training) {
1656           if (*appData.loadGameFile == NULLCHAR) {
1657             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1658             return;
1659           }
1660           TrainingEvent();
1661         }
1662     }
1663 }
1664
1665 void
1666 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1667 {
1668     DisplayBook(current+1);
1669
1670     MoveHistorySet( movelist, first, last, current, pvInfoList );
1671
1672     EvalGraphSet( first, last, current, pvInfoList );
1673
1674     MakeEngineOutputTitle();
1675 }
1676
1677 /*
1678  * Establish will establish a contact to a remote host.port.
1679  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1680  *  used to talk to the host.
1681  * Returns 0 if okay, error code if not.
1682  */
1683 int
1684 establish()
1685 {
1686     char buf[MSG_SIZ];
1687
1688     if (*appData.icsCommPort != NULLCHAR) {
1689         /* Talk to the host through a serial comm port */
1690         return OpenCommPort(appData.icsCommPort, &icsPR);
1691
1692     } else if (*appData.gateway != NULLCHAR) {
1693         if (*appData.remoteShell == NULLCHAR) {
1694             /* Use the rcmd protocol to run telnet program on a gateway host */
1695             snprintf(buf, sizeof(buf), "%s %s %s",
1696                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1697             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1698
1699         } else {
1700             /* Use the rsh program to run telnet program on a gateway host */
1701             if (*appData.remoteUser == NULLCHAR) {
1702                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1703                         appData.gateway, appData.telnetProgram,
1704                         appData.icsHost, appData.icsPort);
1705             } else {
1706                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1707                         appData.remoteShell, appData.gateway,
1708                         appData.remoteUser, appData.telnetProgram,
1709                         appData.icsHost, appData.icsPort);
1710             }
1711             return StartChildProcess(buf, "", &icsPR);
1712
1713         }
1714     } else if (appData.useTelnet) {
1715         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1716
1717     } else {
1718         /* TCP socket interface differs somewhat between
1719            Unix and NT; handle details in the front end.
1720            */
1721         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1722     }
1723 }
1724
1725 void EscapeExpand(char *p, char *q)
1726 {       // [HGM] initstring: routine to shape up string arguments
1727         while(*p++ = *q++) if(p[-1] == '\\')
1728             switch(*q++) {
1729                 case 'n': p[-1] = '\n'; break;
1730                 case 'r': p[-1] = '\r'; break;
1731                 case 't': p[-1] = '\t'; break;
1732                 case '\\': p[-1] = '\\'; break;
1733                 case 0: *p = 0; return;
1734                 default: p[-1] = q[-1]; break;
1735             }
1736 }
1737
1738 void
1739 show_bytes(fp, buf, count)
1740      FILE *fp;
1741      char *buf;
1742      int count;
1743 {
1744     while (count--) {
1745         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1746             fprintf(fp, "\\%03o", *buf & 0xff);
1747         } else {
1748             putc(*buf, fp);
1749         }
1750         buf++;
1751     }
1752     fflush(fp);
1753 }
1754
1755 /* Returns an errno value */
1756 int
1757 OutputMaybeTelnet(pr, message, count, outError)
1758      ProcRef pr;
1759      char *message;
1760      int count;
1761      int *outError;
1762 {
1763     char buf[8192], *p, *q, *buflim;
1764     int left, newcount, outcount;
1765
1766     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1767         *appData.gateway != NULLCHAR) {
1768         if (appData.debugMode) {
1769             fprintf(debugFP, ">ICS: ");
1770             show_bytes(debugFP, message, count);
1771             fprintf(debugFP, "\n");
1772         }
1773         return OutputToProcess(pr, message, count, outError);
1774     }
1775
1776     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1777     p = message;
1778     q = buf;
1779     left = count;
1780     newcount = 0;
1781     while (left) {
1782         if (q >= buflim) {
1783             if (appData.debugMode) {
1784                 fprintf(debugFP, ">ICS: ");
1785                 show_bytes(debugFP, buf, newcount);
1786                 fprintf(debugFP, "\n");
1787             }
1788             outcount = OutputToProcess(pr, buf, newcount, outError);
1789             if (outcount < newcount) return -1; /* to be sure */
1790             q = buf;
1791             newcount = 0;
1792         }
1793         if (*p == '\n') {
1794             *q++ = '\r';
1795             newcount++;
1796         } else if (((unsigned char) *p) == TN_IAC) {
1797             *q++ = (char) TN_IAC;
1798             newcount ++;
1799         }
1800         *q++ = *p++;
1801         newcount++;
1802         left--;
1803     }
1804     if (appData.debugMode) {
1805         fprintf(debugFP, ">ICS: ");
1806         show_bytes(debugFP, buf, newcount);
1807         fprintf(debugFP, "\n");
1808     }
1809     outcount = OutputToProcess(pr, buf, newcount, outError);
1810     if (outcount < newcount) return -1; /* to be sure */
1811     return count;
1812 }
1813
1814 void
1815 read_from_player(isr, closure, message, count, error)
1816      InputSourceRef isr;
1817      VOIDSTAR closure;
1818      char *message;
1819      int count;
1820      int error;
1821 {
1822     int outError, outCount;
1823     static int gotEof = 0;
1824
1825     /* Pass data read from player on to ICS */
1826     if (count > 0) {
1827         gotEof = 0;
1828         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1829         if (outCount < count) {
1830             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1831         }
1832     } else if (count < 0) {
1833         RemoveInputSource(isr);
1834         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1835     } else if (gotEof++ > 0) {
1836         RemoveInputSource(isr);
1837         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1838     }
1839 }
1840
1841 void
1842 KeepAlive()
1843 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1844     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1845     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1846     SendToICS("date\n");
1847     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1848 }
1849
1850 /* added routine for printf style output to ics */
1851 void ics_printf(char *format, ...)
1852 {
1853     char buffer[MSG_SIZ];
1854     va_list args;
1855
1856     va_start(args, format);
1857     vsnprintf(buffer, sizeof(buffer), format, args);
1858     buffer[sizeof(buffer)-1] = '\0';
1859     SendToICS(buffer);
1860     va_end(args);
1861 }
1862
1863 void
1864 SendToICS(s)
1865      char *s;
1866 {
1867     int count, outCount, outError;
1868
1869     if (icsPR == NoProc) return;
1870
1871     count = strlen(s);
1872     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1873     if (outCount < count) {
1874         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1875     }
1876 }
1877
1878 /* This is used for sending logon scripts to the ICS. Sending
1879    without a delay causes problems when using timestamp on ICC
1880    (at least on my machine). */
1881 void
1882 SendToICSDelayed(s,msdelay)
1883      char *s;
1884      long msdelay;
1885 {
1886     int count, outCount, outError;
1887
1888     if (icsPR == NoProc) return;
1889
1890     count = strlen(s);
1891     if (appData.debugMode) {
1892         fprintf(debugFP, ">ICS: ");
1893         show_bytes(debugFP, s, count);
1894         fprintf(debugFP, "\n");
1895     }
1896     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1897                                       msdelay);
1898     if (outCount < count) {
1899         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900     }
1901 }
1902
1903
1904 /* Remove all highlighting escape sequences in s
1905    Also deletes any suffix starting with '('
1906    */
1907 char *
1908 StripHighlightAndTitle(s)
1909      char *s;
1910 {
1911     static char retbuf[MSG_SIZ];
1912     char *p = retbuf;
1913
1914     while (*s != NULLCHAR) {
1915         while (*s == '\033') {
1916             while (*s != NULLCHAR && !isalpha(*s)) s++;
1917             if (*s != NULLCHAR) s++;
1918         }
1919         while (*s != NULLCHAR && *s != '\033') {
1920             if (*s == '(' || *s == '[') {
1921                 *p = NULLCHAR;
1922                 return retbuf;
1923             }
1924             *p++ = *s++;
1925         }
1926     }
1927     *p = NULLCHAR;
1928     return retbuf;
1929 }
1930
1931 /* Remove all highlighting escape sequences in s */
1932 char *
1933 StripHighlight(s)
1934      char *s;
1935 {
1936     static char retbuf[MSG_SIZ];
1937     char *p = retbuf;
1938
1939     while (*s != NULLCHAR) {
1940         while (*s == '\033') {
1941             while (*s != NULLCHAR && !isalpha(*s)) s++;
1942             if (*s != NULLCHAR) s++;
1943         }
1944         while (*s != NULLCHAR && *s != '\033') {
1945             *p++ = *s++;
1946         }
1947     }
1948     *p = NULLCHAR;
1949     return retbuf;
1950 }
1951
1952 char *variantNames[] = VARIANT_NAMES;
1953 char *
1954 VariantName(v)
1955      VariantClass v;
1956 {
1957     return variantNames[v];
1958 }
1959
1960
1961 /* Identify a variant from the strings the chess servers use or the
1962    PGN Variant tag names we use. */
1963 VariantClass
1964 StringToVariant(e)
1965      char *e;
1966 {
1967     char *p;
1968     int wnum = -1;
1969     VariantClass v = VariantNormal;
1970     int i, found = FALSE;
1971     char buf[MSG_SIZ];
1972     int len;
1973
1974     if (!e) return v;
1975
1976     /* [HGM] skip over optional board-size prefixes */
1977     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1978         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1979         while( *e++ != '_');
1980     }
1981
1982     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1983         v = VariantNormal;
1984         found = TRUE;
1985     } else
1986     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1987       if (StrCaseStr(e, variantNames[i])) {
1988         v = (VariantClass) i;
1989         found = TRUE;
1990         break;
1991       }
1992     }
1993
1994     if (!found) {
1995       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1996           || StrCaseStr(e, "wild/fr")
1997           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1998         v = VariantFischeRandom;
1999       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2000                  (i = 1, p = StrCaseStr(e, "w"))) {
2001         p += i;
2002         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2003         if (isdigit(*p)) {
2004           wnum = atoi(p);
2005         } else {
2006           wnum = -1;
2007         }
2008         switch (wnum) {
2009         case 0: /* FICS only, actually */
2010         case 1:
2011           /* Castling legal even if K starts on d-file */
2012           v = VariantWildCastle;
2013           break;
2014         case 2:
2015         case 3:
2016         case 4:
2017           /* Castling illegal even if K & R happen to start in
2018              normal positions. */
2019           v = VariantNoCastle;
2020           break;
2021         case 5:
2022         case 7:
2023         case 8:
2024         case 10:
2025         case 11:
2026         case 12:
2027         case 13:
2028         case 14:
2029         case 15:
2030         case 18:
2031         case 19:
2032           /* Castling legal iff K & R start in normal positions */
2033           v = VariantNormal;
2034           break;
2035         case 6:
2036         case 20:
2037         case 21:
2038           /* Special wilds for position setup; unclear what to do here */
2039           v = VariantLoadable;
2040           break;
2041         case 9:
2042           /* Bizarre ICC game */
2043           v = VariantTwoKings;
2044           break;
2045         case 16:
2046           v = VariantKriegspiel;
2047           break;
2048         case 17:
2049           v = VariantLosers;
2050           break;
2051         case 22:
2052           v = VariantFischeRandom;
2053           break;
2054         case 23:
2055           v = VariantCrazyhouse;
2056           break;
2057         case 24:
2058           v = VariantBughouse;
2059           break;
2060         case 25:
2061           v = Variant3Check;
2062           break;
2063         case 26:
2064           /* Not quite the same as FICS suicide! */
2065           v = VariantGiveaway;
2066           break;
2067         case 27:
2068           v = VariantAtomic;
2069           break;
2070         case 28:
2071           v = VariantShatranj;
2072           break;
2073
2074         /* Temporary names for future ICC types.  The name *will* change in
2075            the next xboard/WinBoard release after ICC defines it. */
2076         case 29:
2077           v = Variant29;
2078           break;
2079         case 30:
2080           v = Variant30;
2081           break;
2082         case 31:
2083           v = Variant31;
2084           break;
2085         case 32:
2086           v = Variant32;
2087           break;
2088         case 33:
2089           v = Variant33;
2090           break;
2091         case 34:
2092           v = Variant34;
2093           break;
2094         case 35:
2095           v = Variant35;
2096           break;
2097         case 36:
2098           v = Variant36;
2099           break;
2100         case 37:
2101           v = VariantShogi;
2102           break;
2103         case 38:
2104           v = VariantXiangqi;
2105           break;
2106         case 39:
2107           v = VariantCourier;
2108           break;
2109         case 40:
2110           v = VariantGothic;
2111           break;
2112         case 41:
2113           v = VariantCapablanca;
2114           break;
2115         case 42:
2116           v = VariantKnightmate;
2117           break;
2118         case 43:
2119           v = VariantFairy;
2120           break;
2121         case 44:
2122           v = VariantCylinder;
2123           break;
2124         case 45:
2125           v = VariantFalcon;
2126           break;
2127         case 46:
2128           v = VariantCapaRandom;
2129           break;
2130         case 47:
2131           v = VariantBerolina;
2132           break;
2133         case 48:
2134           v = VariantJanus;
2135           break;
2136         case 49:
2137           v = VariantSuper;
2138           break;
2139         case 50:
2140           v = VariantGreat;
2141           break;
2142         case -1:
2143           /* Found "wild" or "w" in the string but no number;
2144              must assume it's normal chess. */
2145           v = VariantNormal;
2146           break;
2147         default:
2148           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2149           if( (len > MSG_SIZ) && appData.debugMode )
2150             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2151
2152           DisplayError(buf, 0);
2153           v = VariantUnknown;
2154           break;
2155         }
2156       }
2157     }
2158     if (appData.debugMode) {
2159       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2160               e, wnum, VariantName(v));
2161     }
2162     return v;
2163 }
2164
2165 static int leftover_start = 0, leftover_len = 0;
2166 char star_match[STAR_MATCH_N][MSG_SIZ];
2167
2168 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2169    advance *index beyond it, and set leftover_start to the new value of
2170    *index; else return FALSE.  If pattern contains the character '*', it
2171    matches any sequence of characters not containing '\r', '\n', or the
2172    character following the '*' (if any), and the matched sequence(s) are
2173    copied into star_match.
2174    */
2175 int
2176 looking_at(buf, index, pattern)
2177      char *buf;
2178      int *index;
2179      char *pattern;
2180 {
2181     char *bufp = &buf[*index], *patternp = pattern;
2182     int star_count = 0;
2183     char *matchp = star_match[0];
2184
2185     for (;;) {
2186         if (*patternp == NULLCHAR) {
2187             *index = leftover_start = bufp - buf;
2188             *matchp = NULLCHAR;
2189             return TRUE;
2190         }
2191         if (*bufp == NULLCHAR) return FALSE;
2192         if (*patternp == '*') {
2193             if (*bufp == *(patternp + 1)) {
2194                 *matchp = NULLCHAR;
2195                 matchp = star_match[++star_count];
2196                 patternp += 2;
2197                 bufp++;
2198                 continue;
2199             } else if (*bufp == '\n' || *bufp == '\r') {
2200                 patternp++;
2201                 if (*patternp == NULLCHAR)
2202                   continue;
2203                 else
2204                   return FALSE;
2205             } else {
2206                 *matchp++ = *bufp++;
2207                 continue;
2208             }
2209         }
2210         if (*patternp != *bufp) return FALSE;
2211         patternp++;
2212         bufp++;
2213     }
2214 }
2215
2216 void
2217 SendToPlayer(data, length)
2218      char *data;
2219      int length;
2220 {
2221     int error, outCount;
2222     outCount = OutputToProcess(NoProc, data, length, &error);
2223     if (outCount < length) {
2224         DisplayFatalError(_("Error writing to display"), error, 1);
2225     }
2226 }
2227
2228 void
2229 PackHolding(packed, holding)
2230      char packed[];
2231      char *holding;
2232 {
2233     char *p = holding;
2234     char *q = packed;
2235     int runlength = 0;
2236     int curr = 9999;
2237     do {
2238         if (*p == curr) {
2239             runlength++;
2240         } else {
2241             switch (runlength) {
2242               case 0:
2243                 break;
2244               case 1:
2245                 *q++ = curr;
2246                 break;
2247               case 2:
2248                 *q++ = curr;
2249                 *q++ = curr;
2250                 break;
2251               default:
2252                 sprintf(q, "%d", runlength);
2253                 while (*q) q++;
2254                 *q++ = curr;
2255                 break;
2256             }
2257             runlength = 1;
2258             curr = *p;
2259         }
2260     } while (*p++);
2261     *q = NULLCHAR;
2262 }
2263
2264 /* Telnet protocol requests from the front end */
2265 void
2266 TelnetRequest(ddww, option)
2267      unsigned char ddww, option;
2268 {
2269     unsigned char msg[3];
2270     int outCount, outError;
2271
2272     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2273
2274     if (appData.debugMode) {
2275         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2276         switch (ddww) {
2277           case TN_DO:
2278             ddwwStr = "DO";
2279             break;
2280           case TN_DONT:
2281             ddwwStr = "DONT";
2282             break;
2283           case TN_WILL:
2284             ddwwStr = "WILL";
2285             break;
2286           case TN_WONT:
2287             ddwwStr = "WONT";
2288             break;
2289           default:
2290             ddwwStr = buf1;
2291             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2292             break;
2293         }
2294         switch (option) {
2295           case TN_ECHO:
2296             optionStr = "ECHO";
2297             break;
2298           default:
2299             optionStr = buf2;
2300             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2301             break;
2302         }
2303         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2304     }
2305     msg[0] = TN_IAC;
2306     msg[1] = ddww;
2307     msg[2] = option;
2308     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2309     if (outCount < 3) {
2310         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2311     }
2312 }
2313
2314 void
2315 DoEcho()
2316 {
2317     if (!appData.icsActive) return;
2318     TelnetRequest(TN_DO, TN_ECHO);
2319 }
2320
2321 void
2322 DontEcho()
2323 {
2324     if (!appData.icsActive) return;
2325     TelnetRequest(TN_DONT, TN_ECHO);
2326 }
2327
2328 void
2329 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2330 {
2331     /* put the holdings sent to us by the server on the board holdings area */
2332     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2333     char p;
2334     ChessSquare piece;
2335
2336     if(gameInfo.holdingsWidth < 2)  return;
2337     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2338         return; // prevent overwriting by pre-board holdings
2339
2340     if( (int)lowestPiece >= BlackPawn ) {
2341         holdingsColumn = 0;
2342         countsColumn = 1;
2343         holdingsStartRow = BOARD_HEIGHT-1;
2344         direction = -1;
2345     } else {
2346         holdingsColumn = BOARD_WIDTH-1;
2347         countsColumn = BOARD_WIDTH-2;
2348         holdingsStartRow = 0;
2349         direction = 1;
2350     }
2351
2352     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2353         board[i][holdingsColumn] = EmptySquare;
2354         board[i][countsColumn]   = (ChessSquare) 0;
2355     }
2356     while( (p=*holdings++) != NULLCHAR ) {
2357         piece = CharToPiece( ToUpper(p) );
2358         if(piece == EmptySquare) continue;
2359         /*j = (int) piece - (int) WhitePawn;*/
2360         j = PieceToNumber(piece);
2361         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2362         if(j < 0) continue;               /* should not happen */
2363         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2364         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2365         board[holdingsStartRow+j*direction][countsColumn]++;
2366     }
2367 }
2368
2369
2370 void
2371 VariantSwitch(Board board, VariantClass newVariant)
2372 {
2373    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2374    static Board oldBoard;
2375
2376    startedFromPositionFile = FALSE;
2377    if(gameInfo.variant == newVariant) return;
2378
2379    /* [HGM] This routine is called each time an assignment is made to
2380     * gameInfo.variant during a game, to make sure the board sizes
2381     * are set to match the new variant. If that means adding or deleting
2382     * holdings, we shift the playing board accordingly
2383     * This kludge is needed because in ICS observe mode, we get boards
2384     * of an ongoing game without knowing the variant, and learn about the
2385     * latter only later. This can be because of the move list we requested,
2386     * in which case the game history is refilled from the beginning anyway,
2387     * but also when receiving holdings of a crazyhouse game. In the latter
2388     * case we want to add those holdings to the already received position.
2389     */
2390
2391
2392    if (appData.debugMode) {
2393      fprintf(debugFP, "Switch board from %s to %s\n",
2394              VariantName(gameInfo.variant), VariantName(newVariant));
2395      setbuf(debugFP, NULL);
2396    }
2397    shuffleOpenings = 0;       /* [HGM] shuffle */
2398    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2399    switch(newVariant)
2400      {
2401      case VariantShogi:
2402        newWidth = 9;  newHeight = 9;
2403        gameInfo.holdingsSize = 7;
2404      case VariantBughouse:
2405      case VariantCrazyhouse:
2406        newHoldingsWidth = 2; break;
2407      case VariantGreat:
2408        newWidth = 10;
2409      case VariantSuper:
2410        newHoldingsWidth = 2;
2411        gameInfo.holdingsSize = 8;
2412        break;
2413      case VariantGothic:
2414      case VariantCapablanca:
2415      case VariantCapaRandom:
2416        newWidth = 10;
2417      default:
2418        newHoldingsWidth = gameInfo.holdingsSize = 0;
2419      };
2420
2421    if(newWidth  != gameInfo.boardWidth  ||
2422       newHeight != gameInfo.boardHeight ||
2423       newHoldingsWidth != gameInfo.holdingsWidth ) {
2424
2425      /* shift position to new playing area, if needed */
2426      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2427        for(i=0; i<BOARD_HEIGHT; i++)
2428          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2429            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430              board[i][j];
2431        for(i=0; i<newHeight; i++) {
2432          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2433          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2434        }
2435      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2436        for(i=0; i<BOARD_HEIGHT; i++)
2437          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2438            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439              board[i][j];
2440      }
2441      gameInfo.boardWidth  = newWidth;
2442      gameInfo.boardHeight = newHeight;
2443      gameInfo.holdingsWidth = newHoldingsWidth;
2444      gameInfo.variant = newVariant;
2445      InitDrawingSizes(-2, 0);
2446    } else gameInfo.variant = newVariant;
2447    CopyBoard(oldBoard, board);   // remember correctly formatted board
2448      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2449    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2450 }
2451
2452 static int loggedOn = FALSE;
2453
2454 /*-- Game start info cache: --*/
2455 int gs_gamenum;
2456 char gs_kind[MSG_SIZ];
2457 static char player1Name[128] = "";
2458 static char player2Name[128] = "";
2459 static char cont_seq[] = "\n\\   ";
2460 static int player1Rating = -1;
2461 static int player2Rating = -1;
2462 /*----------------------------*/
2463
2464 ColorClass curColor = ColorNormal;
2465 int suppressKibitz = 0;
2466
2467 // [HGM] seekgraph
2468 Boolean soughtPending = FALSE;
2469 Boolean seekGraphUp;
2470 #define MAX_SEEK_ADS 200
2471 #define SQUARE 0x80
2472 char *seekAdList[MAX_SEEK_ADS];
2473 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2474 float tcList[MAX_SEEK_ADS];
2475 char colorList[MAX_SEEK_ADS];
2476 int nrOfSeekAds = 0;
2477 int minRating = 1010, maxRating = 2800;
2478 int hMargin = 10, vMargin = 20, h, w;
2479 extern int squareSize, lineGap;
2480
2481 void
2482 PlotSeekAd(int i)
2483 {
2484         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2485         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2486         if(r < minRating+100 && r >=0 ) r = minRating+100;
2487         if(r > maxRating) r = maxRating;
2488         if(tc < 1.) tc = 1.;
2489         if(tc > 95.) tc = 95.;
2490         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2491         y = ((double)r - minRating)/(maxRating - minRating)
2492             * (h-vMargin-squareSize/8-1) + vMargin;
2493         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2494         if(strstr(seekAdList[i], " u ")) color = 1;
2495         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2496            !strstr(seekAdList[i], "bullet") &&
2497            !strstr(seekAdList[i], "blitz") &&
2498            !strstr(seekAdList[i], "standard") ) color = 2;
2499         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2500         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2501 }
2502
2503 void
2504 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2505 {
2506         char buf[MSG_SIZ], *ext = "";
2507         VariantClass v = StringToVariant(type);
2508         if(strstr(type, "wild")) {
2509             ext = type + 4; // append wild number
2510             if(v == VariantFischeRandom) type = "chess960"; else
2511             if(v == VariantLoadable) type = "setup"; else
2512             type = VariantName(v);
2513         }
2514         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2515         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2516             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2517             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2518             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2519             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2520             seekNrList[nrOfSeekAds] = nr;
2521             zList[nrOfSeekAds] = 0;
2522             seekAdList[nrOfSeekAds++] = StrSave(buf);
2523             if(plot) PlotSeekAd(nrOfSeekAds-1);
2524         }
2525 }
2526
2527 void
2528 EraseSeekDot(int i)
2529 {
2530     int x = xList[i], y = yList[i], d=squareSize/4, k;
2531     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2532     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2533     // now replot every dot that overlapped
2534     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2535         int xx = xList[k], yy = yList[k];
2536         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2537             DrawSeekDot(xx, yy, colorList[k]);
2538     }
2539 }
2540
2541 void
2542 RemoveSeekAd(int nr)
2543 {
2544         int i;
2545         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2546             EraseSeekDot(i);
2547             if(seekAdList[i]) free(seekAdList[i]);
2548             seekAdList[i] = seekAdList[--nrOfSeekAds];
2549             seekNrList[i] = seekNrList[nrOfSeekAds];
2550             ratingList[i] = ratingList[nrOfSeekAds];
2551             colorList[i]  = colorList[nrOfSeekAds];
2552             tcList[i] = tcList[nrOfSeekAds];
2553             xList[i]  = xList[nrOfSeekAds];
2554             yList[i]  = yList[nrOfSeekAds];
2555             zList[i]  = zList[nrOfSeekAds];
2556             seekAdList[nrOfSeekAds] = NULL;
2557             break;
2558         }
2559 }
2560
2561 Boolean
2562 MatchSoughtLine(char *line)
2563 {
2564     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2565     int nr, base, inc, u=0; char dummy;
2566
2567     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2568        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2569        (u=1) &&
2570        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2571         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2572         // match: compact and save the line
2573         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2574         return TRUE;
2575     }
2576     return FALSE;
2577 }
2578
2579 int
2580 DrawSeekGraph()
2581 {
2582     int i;
2583     if(!seekGraphUp) return FALSE;
2584     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2585     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2586
2587     DrawSeekBackground(0, 0, w, h);
2588     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2589     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2590     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2591         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2592         yy = h-1-yy;
2593         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2594         if(i%500 == 0) {
2595             char buf[MSG_SIZ];
2596             snprintf(buf, MSG_SIZ, "%d", i);
2597             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2598         }
2599     }
2600     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2601     for(i=1; i<100; i+=(i<10?1:5)) {
2602         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2603         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2604         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2605             char buf[MSG_SIZ];
2606             snprintf(buf, MSG_SIZ, "%d", i);
2607             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2608         }
2609     }
2610     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2611     return TRUE;
2612 }
2613
2614 int SeekGraphClick(ClickType click, int x, int y, int moving)
2615 {
2616     static int lastDown = 0, displayed = 0, lastSecond;
2617     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2618         if(click == Release || moving) return FALSE;
2619         nrOfSeekAds = 0;
2620         soughtPending = TRUE;
2621         SendToICS(ics_prefix);
2622         SendToICS("sought\n"); // should this be "sought all"?
2623     } else { // issue challenge based on clicked ad
2624         int dist = 10000; int i, closest = 0, second = 0;
2625         for(i=0; i<nrOfSeekAds; i++) {
2626             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2627             if(d < dist) { dist = d; closest = i; }
2628             second += (d - zList[i] < 120); // count in-range ads
2629             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2630         }
2631         if(dist < 120) {
2632             char buf[MSG_SIZ];
2633             second = (second > 1);
2634             if(displayed != closest || second != lastSecond) {
2635                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2636                 lastSecond = second; displayed = closest;
2637             }
2638             if(click == Press) {
2639                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2640                 lastDown = closest;
2641                 return TRUE;
2642             } // on press 'hit', only show info
2643             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2644             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2645             SendToICS(ics_prefix);
2646             SendToICS(buf);
2647             return TRUE; // let incoming board of started game pop down the graph
2648         } else if(click == Release) { // release 'miss' is ignored
2649             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2650             if(moving == 2) { // right up-click
2651                 nrOfSeekAds = 0; // refresh graph
2652                 soughtPending = TRUE;
2653                 SendToICS(ics_prefix);
2654                 SendToICS("sought\n"); // should this be "sought all"?
2655             }
2656             return TRUE;
2657         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2658         // press miss or release hit 'pop down' seek graph
2659         seekGraphUp = FALSE;
2660         DrawPosition(TRUE, NULL);
2661     }
2662     return TRUE;
2663 }
2664
2665 void
2666 read_from_ics(isr, closure, data, count, error)
2667      InputSourceRef isr;
2668      VOIDSTAR closure;
2669      char *data;
2670      int count;
2671      int error;
2672 {
2673 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2674 #define STARTED_NONE 0
2675 #define STARTED_MOVES 1
2676 #define STARTED_BOARD 2
2677 #define STARTED_OBSERVE 3
2678 #define STARTED_HOLDINGS 4
2679 #define STARTED_CHATTER 5
2680 #define STARTED_COMMENT 6
2681 #define STARTED_MOVES_NOHIDE 7
2682
2683     static int started = STARTED_NONE;
2684     static char parse[20000];
2685     static int parse_pos = 0;
2686     static char buf[BUF_SIZE + 1];
2687     static int firstTime = TRUE, intfSet = FALSE;
2688     static ColorClass prevColor = ColorNormal;
2689     static int savingComment = FALSE;
2690     static int cmatch = 0; // continuation sequence match
2691     char *bp;
2692     char str[MSG_SIZ];
2693     int i, oldi;
2694     int buf_len;
2695     int next_out;
2696     int tkind;
2697     int backup;    /* [DM] For zippy color lines */
2698     char *p;
2699     char talker[MSG_SIZ]; // [HGM] chat
2700     int channel;
2701
2702     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2703
2704     if (appData.debugMode) {
2705       if (!error) {
2706         fprintf(debugFP, "<ICS: ");
2707         show_bytes(debugFP, data, count);
2708         fprintf(debugFP, "\n");
2709       }
2710     }
2711
2712     if (appData.debugMode) { int f = forwardMostMove;
2713         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2714                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2715                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2716     }
2717     if (count > 0) {
2718         /* If last read ended with a partial line that we couldn't parse,
2719            prepend it to the new read and try again. */
2720         if (leftover_len > 0) {
2721             for (i=0; i<leftover_len; i++)
2722               buf[i] = buf[leftover_start + i];
2723         }
2724
2725     /* copy new characters into the buffer */
2726     bp = buf + leftover_len;
2727     buf_len=leftover_len;
2728     for (i=0; i<count; i++)
2729     {
2730         // ignore these
2731         if (data[i] == '\r')
2732             continue;
2733
2734         // join lines split by ICS?
2735         if (!appData.noJoin)
2736         {
2737             /*
2738                 Joining just consists of finding matches against the
2739                 continuation sequence, and discarding that sequence
2740                 if found instead of copying it.  So, until a match
2741                 fails, there's nothing to do since it might be the
2742                 complete sequence, and thus, something we don't want
2743                 copied.
2744             */
2745             if (data[i] == cont_seq[cmatch])
2746             {
2747                 cmatch++;
2748                 if (cmatch == strlen(cont_seq))
2749                 {
2750                     cmatch = 0; // complete match.  just reset the counter
2751
2752                     /*
2753                         it's possible for the ICS to not include the space
2754                         at the end of the last word, making our [correct]
2755                         join operation fuse two separate words.  the server
2756                         does this when the space occurs at the width setting.
2757                     */
2758                     if (!buf_len || buf[buf_len-1] != ' ')
2759                     {
2760                         *bp++ = ' ';
2761                         buf_len++;
2762                     }
2763                 }
2764                 continue;
2765             }
2766             else if (cmatch)
2767             {
2768                 /*
2769                     match failed, so we have to copy what matched before
2770                     falling through and copying this character.  In reality,
2771                     this will only ever be just the newline character, but
2772                     it doesn't hurt to be precise.
2773                 */
2774                 strncpy(bp, cont_seq, cmatch);
2775                 bp += cmatch;
2776                 buf_len += cmatch;
2777                 cmatch = 0;
2778             }
2779         }
2780
2781         // copy this char
2782         *bp++ = data[i];
2783         buf_len++;
2784     }
2785
2786         buf[buf_len] = NULLCHAR;
2787 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2788         next_out = 0;
2789         leftover_start = 0;
2790
2791         i = 0;
2792         while (i < buf_len) {
2793             /* Deal with part of the TELNET option negotiation
2794                protocol.  We refuse to do anything beyond the
2795                defaults, except that we allow the WILL ECHO option,
2796                which ICS uses to turn off password echoing when we are
2797                directly connected to it.  We reject this option
2798                if localLineEditing mode is on (always on in xboard)
2799                and we are talking to port 23, which might be a real
2800                telnet server that will try to keep WILL ECHO on permanently.
2801              */
2802             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2803                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2804                 unsigned char option;
2805                 oldi = i;
2806                 switch ((unsigned char) buf[++i]) {
2807                   case TN_WILL:
2808                     if (appData.debugMode)
2809                       fprintf(debugFP, "\n<WILL ");
2810                     switch (option = (unsigned char) buf[++i]) {
2811                       case TN_ECHO:
2812                         if (appData.debugMode)
2813                           fprintf(debugFP, "ECHO ");
2814                         /* Reply only if this is a change, according
2815                            to the protocol rules. */
2816                         if (remoteEchoOption) break;
2817                         if (appData.localLineEditing &&
2818                             atoi(appData.icsPort) == TN_PORT) {
2819                             TelnetRequest(TN_DONT, TN_ECHO);
2820                         } else {
2821                             EchoOff();
2822                             TelnetRequest(TN_DO, TN_ECHO);
2823                             remoteEchoOption = TRUE;
2824                         }
2825                         break;
2826                       default:
2827                         if (appData.debugMode)
2828                           fprintf(debugFP, "%d ", option);
2829                         /* Whatever this is, we don't want it. */
2830                         TelnetRequest(TN_DONT, option);
2831                         break;
2832                     }
2833                     break;
2834                   case TN_WONT:
2835                     if (appData.debugMode)
2836                       fprintf(debugFP, "\n<WONT ");
2837                     switch (option = (unsigned char) buf[++i]) {
2838                       case TN_ECHO:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "ECHO ");
2841                         /* Reply only if this is a change, according
2842                            to the protocol rules. */
2843                         if (!remoteEchoOption) break;
2844                         EchoOn();
2845                         TelnetRequest(TN_DONT, TN_ECHO);
2846                         remoteEchoOption = FALSE;
2847                         break;
2848                       default:
2849                         if (appData.debugMode)
2850                           fprintf(debugFP, "%d ", (unsigned char) option);
2851                         /* Whatever this is, it must already be turned
2852                            off, because we never agree to turn on
2853                            anything non-default, so according to the
2854                            protocol rules, we don't reply. */
2855                         break;
2856                     }
2857                     break;
2858                   case TN_DO:
2859                     if (appData.debugMode)
2860                       fprintf(debugFP, "\n<DO ");
2861                     switch (option = (unsigned char) buf[++i]) {
2862                       default:
2863                         /* Whatever this is, we refuse to do it. */
2864                         if (appData.debugMode)
2865                           fprintf(debugFP, "%d ", option);
2866                         TelnetRequest(TN_WONT, option);
2867                         break;
2868                     }
2869                     break;
2870                   case TN_DONT:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<DONT ");
2873                     switch (option = (unsigned char) buf[++i]) {
2874                       default:
2875                         if (appData.debugMode)
2876                           fprintf(debugFP, "%d ", option);
2877                         /* Whatever this is, we are already not doing
2878                            it, because we never agree to do anything
2879                            non-default, so according to the protocol
2880                            rules, we don't reply. */
2881                         break;
2882                     }
2883                     break;
2884                   case TN_IAC:
2885                     if (appData.debugMode)
2886                       fprintf(debugFP, "\n<IAC ");
2887                     /* Doubled IAC; pass it through */
2888                     i--;
2889                     break;
2890                   default:
2891                     if (appData.debugMode)
2892                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2893                     /* Drop all other telnet commands on the floor */
2894                     break;
2895                 }
2896                 if (oldi > next_out)
2897                   SendToPlayer(&buf[next_out], oldi - next_out);
2898                 if (++i > next_out)
2899                   next_out = i;
2900                 continue;
2901             }
2902
2903             /* OK, this at least will *usually* work */
2904             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2905                 loggedOn = TRUE;
2906             }
2907
2908             if (loggedOn && !intfSet) {
2909                 if (ics_type == ICS_ICC) {
2910                   snprintf(str, MSG_SIZ,
2911                           "/set-quietly interface %s\n/set-quietly style 12\n",
2912                           programVersion);
2913                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2914                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2915                 } else if (ics_type == ICS_CHESSNET) {
2916                   snprintf(str, MSG_SIZ, "/style 12\n");
2917                 } else {
2918                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2919                   strcat(str, programVersion);
2920                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2921                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2922                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2923 #ifdef WIN32
2924                   strcat(str, "$iset nohighlight 1\n");
2925 #endif
2926                   strcat(str, "$iset lock 1\n$style 12\n");
2927                 }
2928                 SendToICS(str);
2929                 NotifyFrontendLogin();
2930                 intfSet = TRUE;
2931             }
2932
2933             if (started == STARTED_COMMENT) {
2934                 /* Accumulate characters in comment */
2935                 parse[parse_pos++] = buf[i];
2936                 if (buf[i] == '\n') {
2937                     parse[parse_pos] = NULLCHAR;
2938                     if(chattingPartner>=0) {
2939                         char mess[MSG_SIZ];
2940                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2941                         OutputChatMessage(chattingPartner, mess);
2942                         chattingPartner = -1;
2943                         next_out = i+1; // [HGM] suppress printing in ICS window
2944                     } else
2945                     if(!suppressKibitz) // [HGM] kibitz
2946                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2947                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2948                         int nrDigit = 0, nrAlph = 0, j;
2949                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2950                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2951                         parse[parse_pos] = NULLCHAR;
2952                         // try to be smart: if it does not look like search info, it should go to
2953                         // ICS interaction window after all, not to engine-output window.
2954                         for(j=0; j<parse_pos; j++) { // count letters and digits
2955                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2956                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2957                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2958                         }
2959                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2960                             int depth=0; float score;
2961                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2962                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2963                                 pvInfoList[forwardMostMove-1].depth = depth;
2964                                 pvInfoList[forwardMostMove-1].score = 100*score;
2965                             }
2966                             OutputKibitz(suppressKibitz, parse);
2967                         } else {
2968                             char tmp[MSG_SIZ];
2969                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2970                             SendToPlayer(tmp, strlen(tmp));
2971                         }
2972                         next_out = i+1; // [HGM] suppress printing in ICS window
2973                     }
2974                     started = STARTED_NONE;
2975                 } else {
2976                     /* Don't match patterns against characters in comment */
2977                     i++;
2978                     continue;
2979                 }
2980             }
2981             if (started == STARTED_CHATTER) {
2982                 if (buf[i] != '\n') {
2983                     /* Don't match patterns against characters in chatter */
2984                     i++;
2985                     continue;
2986                 }
2987                 started = STARTED_NONE;
2988                 if(suppressKibitz) next_out = i+1;
2989             }
2990
2991             /* Kludge to deal with rcmd protocol */
2992             if (firstTime && looking_at(buf, &i, "\001*")) {
2993                 DisplayFatalError(&buf[1], 0, 1);
2994                 continue;
2995             } else {
2996                 firstTime = FALSE;
2997             }
2998
2999             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3000                 ics_type = ICS_ICC;
3001                 ics_prefix = "/";
3002                 if (appData.debugMode)
3003                   fprintf(debugFP, "ics_type %d\n", ics_type);
3004                 continue;
3005             }
3006             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3007                 ics_type = ICS_FICS;
3008                 ics_prefix = "$";
3009                 if (appData.debugMode)
3010                   fprintf(debugFP, "ics_type %d\n", ics_type);
3011                 continue;
3012             }
3013             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3014                 ics_type = ICS_CHESSNET;
3015                 ics_prefix = "/";
3016                 if (appData.debugMode)
3017                   fprintf(debugFP, "ics_type %d\n", ics_type);
3018                 continue;
3019             }
3020
3021             if (!loggedOn &&
3022                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3023                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3024                  looking_at(buf, &i, "will be \"*\""))) {
3025               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3026               continue;
3027             }
3028
3029             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3030               char buf[MSG_SIZ];
3031               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3032               DisplayIcsInteractionTitle(buf);
3033               have_set_title = TRUE;
3034             }
3035
3036             /* skip finger notes */
3037             if (started == STARTED_NONE &&
3038                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3039                  (buf[i] == '1' && buf[i+1] == '0')) &&
3040                 buf[i+2] == ':' && buf[i+3] == ' ') {
3041               started = STARTED_CHATTER;
3042               i += 3;
3043               continue;
3044             }
3045
3046             oldi = i;
3047             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3048             if(appData.seekGraph) {
3049                 if(soughtPending && MatchSoughtLine(buf+i)) {
3050                     i = strstr(buf+i, "rated") - buf;
3051                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3052                     next_out = leftover_start = i;
3053                     started = STARTED_CHATTER;
3054                     suppressKibitz = TRUE;
3055                     continue;
3056                 }
3057                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3058                         && looking_at(buf, &i, "* ads displayed")) {
3059                     soughtPending = FALSE;
3060                     seekGraphUp = TRUE;
3061                     DrawSeekGraph();
3062                     continue;
3063                 }
3064                 if(appData.autoRefresh) {
3065                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3066                         int s = (ics_type == ICS_ICC); // ICC format differs
3067                         if(seekGraphUp)
3068                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3069                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3070                         looking_at(buf, &i, "*% "); // eat prompt
3071                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3072                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3073                         next_out = i; // suppress
3074                         continue;
3075                     }
3076                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3077                         char *p = star_match[0];
3078                         while(*p) {
3079                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3080                             while(*p && *p++ != ' '); // next
3081                         }
3082                         looking_at(buf, &i, "*% "); // eat prompt
3083                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3084                         next_out = i;
3085                         continue;
3086                     }
3087                 }
3088             }
3089
3090             /* skip formula vars */
3091             if (started == STARTED_NONE &&
3092                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3093               started = STARTED_CHATTER;
3094               i += 3;
3095               continue;
3096             }
3097
3098             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3099             if (appData.autoKibitz && started == STARTED_NONE &&
3100                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3101                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3102                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3103                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3104                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3105                         suppressKibitz = TRUE;
3106                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3107                         next_out = i;
3108                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3109                                 && (gameMode == IcsPlayingWhite)) ||
3110                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3111                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3112                             started = STARTED_CHATTER; // own kibitz we simply discard
3113                         else {
3114                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3115                             parse_pos = 0; parse[0] = NULLCHAR;
3116                             savingComment = TRUE;
3117                             suppressKibitz = gameMode != IcsObserving ? 2 :
3118                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3119                         }
3120                         continue;
3121                 } else
3122                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3123                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3124                          && atoi(star_match[0])) {
3125                     // suppress the acknowledgements of our own autoKibitz
3126                     char *p;
3127                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3128                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3129                     SendToPlayer(star_match[0], strlen(star_match[0]));
3130                     if(looking_at(buf, &i, "*% ")) // eat prompt
3131                         suppressKibitz = FALSE;
3132                     next_out = i;
3133                     continue;
3134                 }
3135             } // [HGM] kibitz: end of patch
3136
3137             // [HGM] chat: intercept tells by users for which we have an open chat window
3138             channel = -1;
3139             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3140                                            looking_at(buf, &i, "* whispers:") ||
3141                                            looking_at(buf, &i, "* kibitzes:") ||
3142                                            looking_at(buf, &i, "* shouts:") ||
3143                                            looking_at(buf, &i, "* c-shouts:") ||
3144                                            looking_at(buf, &i, "--> * ") ||
3145                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3146                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3148                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3149                 int p;
3150                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3151                 chattingPartner = -1;
3152
3153                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3154                 for(p=0; p<MAX_CHAT; p++) {
3155                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3156                     talker[0] = '['; strcat(talker, "] ");
3157                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3158                     chattingPartner = p; break;
3159                     }
3160                 } else
3161                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3162                 for(p=0; p<MAX_CHAT; p++) {
3163                     if(!strcmp("kibitzes", chatPartner[p])) {
3164                         talker[0] = '['; strcat(talker, "] ");
3165                         chattingPartner = p; break;
3166                     }
3167                 } else
3168                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3169                 for(p=0; p<MAX_CHAT; p++) {
3170                     if(!strcmp("whispers", chatPartner[p])) {
3171                         talker[0] = '['; strcat(talker, "] ");
3172                         chattingPartner = p; break;
3173                     }
3174                 } else
3175                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3176                   if(buf[i-8] == '-' && buf[i-3] == 't')
3177                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3178                     if(!strcmp("c-shouts", chatPartner[p])) {
3179                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3180                         chattingPartner = p; break;
3181                     }
3182                   }
3183                   if(chattingPartner < 0)
3184                   for(p=0; p<MAX_CHAT; p++) {
3185                     if(!strcmp("shouts", chatPartner[p])) {
3186                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3187                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3188                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3189                         chattingPartner = p; break;
3190                     }
3191                   }
3192                 }
3193                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3194                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3195                     talker[0] = 0; Colorize(ColorTell, FALSE);
3196                     chattingPartner = p; break;
3197                 }
3198                 if(chattingPartner<0) i = oldi; else {
3199                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3200                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3201                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3202                     started = STARTED_COMMENT;
3203                     parse_pos = 0; parse[0] = NULLCHAR;
3204                     savingComment = 3 + chattingPartner; // counts as TRUE
3205                     suppressKibitz = TRUE;
3206                     continue;
3207                 }
3208             } // [HGM] chat: end of patch
3209
3210           backup = i;
3211             if (appData.zippyTalk || appData.zippyPlay) {
3212                 /* [DM] Backup address for color zippy lines */
3213 #if ZIPPY
3214                if (loggedOn == TRUE)
3215                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3216                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3217 #endif
3218             } // [DM] 'else { ' deleted
3219                 if (
3220                     /* Regular tells and says */
3221                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3222                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3223                     looking_at(buf, &i, "* says: ") ||
3224                     /* Don't color "message" or "messages" output */
3225                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3226                     looking_at(buf, &i, "*. * at *:*: ") ||
3227                     looking_at(buf, &i, "--* (*:*): ") ||
3228                     /* Message notifications (same color as tells) */
3229                     looking_at(buf, &i, "* has left a message ") ||
3230                     looking_at(buf, &i, "* just sent you a message:\n") ||
3231                     /* Whispers and kibitzes */
3232                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3233                     looking_at(buf, &i, "* kibitzes: ") ||
3234                     /* Channel tells */
3235                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3236
3237                   if (tkind == 1 && strchr(star_match[0], ':')) {
3238                       /* Avoid "tells you:" spoofs in channels */
3239                      tkind = 3;
3240                   }
3241                   if (star_match[0][0] == NULLCHAR ||
3242                       strchr(star_match[0], ' ') ||
3243                       (tkind == 3 && strchr(star_match[1], ' '))) {
3244                     /* Reject bogus matches */
3245                     i = oldi;
3246                   } else {
3247                     if (appData.colorize) {
3248                       if (oldi > next_out) {
3249                         SendToPlayer(&buf[next_out], oldi - next_out);
3250                         next_out = oldi;
3251                       }
3252                       switch (tkind) {
3253                       case 1:
3254                         Colorize(ColorTell, FALSE);
3255                         curColor = ColorTell;
3256                         break;
3257                       case 2:
3258                         Colorize(ColorKibitz, FALSE);
3259                         curColor = ColorKibitz;
3260                         break;
3261                       case 3:
3262                         p = strrchr(star_match[1], '(');
3263                         if (p == NULL) {
3264                           p = star_match[1];
3265                         } else {
3266                           p++;
3267                         }
3268                         if (atoi(p) == 1) {
3269                           Colorize(ColorChannel1, FALSE);
3270                           curColor = ColorChannel1;
3271                         } else {
3272                           Colorize(ColorChannel, FALSE);
3273                           curColor = ColorChannel;
3274                         }
3275                         break;
3276                       case 5:
3277                         curColor = ColorNormal;
3278                         break;
3279                       }
3280                     }
3281                     if (started == STARTED_NONE && appData.autoComment &&
3282                         (gameMode == IcsObserving ||
3283                          gameMode == IcsPlayingWhite ||
3284                          gameMode == IcsPlayingBlack)) {
3285                       parse_pos = i - oldi;
3286                       memcpy(parse, &buf[oldi], parse_pos);
3287                       parse[parse_pos] = NULLCHAR;
3288                       started = STARTED_COMMENT;
3289                       savingComment = TRUE;
3290                     } else {
3291                       started = STARTED_CHATTER;
3292                       savingComment = FALSE;
3293                     }
3294                     loggedOn = TRUE;
3295                     continue;
3296                   }
3297                 }
3298
3299                 if (looking_at(buf, &i, "* s-shouts: ") ||
3300                     looking_at(buf, &i, "* c-shouts: ")) {
3301                     if (appData.colorize) {
3302                         if (oldi > next_out) {
3303                             SendToPlayer(&buf[next_out], oldi - next_out);
3304                             next_out = oldi;
3305                         }
3306                         Colorize(ColorSShout, FALSE);
3307                         curColor = ColorSShout;
3308                     }
3309                     loggedOn = TRUE;
3310                     started = STARTED_CHATTER;
3311                     continue;
3312                 }
3313
3314                 if (looking_at(buf, &i, "--->")) {
3315                     loggedOn = TRUE;
3316                     continue;
3317                 }
3318
3319                 if (looking_at(buf, &i, "* shouts: ") ||
3320                     looking_at(buf, &i, "--> ")) {
3321                     if (appData.colorize) {
3322                         if (oldi > next_out) {
3323                             SendToPlayer(&buf[next_out], oldi - next_out);
3324                             next_out = oldi;
3325                         }
3326                         Colorize(ColorShout, FALSE);
3327                         curColor = ColorShout;
3328                     }
3329                     loggedOn = TRUE;
3330                     started = STARTED_CHATTER;
3331                     continue;
3332                 }
3333
3334                 if (looking_at( buf, &i, "Challenge:")) {
3335                     if (appData.colorize) {
3336                         if (oldi > next_out) {
3337                             SendToPlayer(&buf[next_out], oldi - next_out);
3338                             next_out = oldi;
3339                         }
3340                         Colorize(ColorChallenge, FALSE);
3341                         curColor = ColorChallenge;
3342                     }
3343                     loggedOn = TRUE;
3344                     continue;
3345                 }
3346
3347                 if (looking_at(buf, &i, "* offers you") ||
3348                     looking_at(buf, &i, "* offers to be") ||
3349                     looking_at(buf, &i, "* would like to") ||
3350                     looking_at(buf, &i, "* requests to") ||
3351                     looking_at(buf, &i, "Your opponent offers") ||
3352                     looking_at(buf, &i, "Your opponent requests")) {
3353
3354                     if (appData.colorize) {
3355                         if (oldi > next_out) {
3356                             SendToPlayer(&buf[next_out], oldi - next_out);
3357                             next_out = oldi;
3358                         }
3359                         Colorize(ColorRequest, FALSE);
3360                         curColor = ColorRequest;
3361                     }
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "* (*) seeking")) {
3366                     if (appData.colorize) {
3367                         if (oldi > next_out) {
3368                             SendToPlayer(&buf[next_out], oldi - next_out);
3369                             next_out = oldi;
3370                         }
3371                         Colorize(ColorSeek, FALSE);
3372                         curColor = ColorSeek;
3373                     }
3374                     continue;
3375             }
3376
3377           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3378
3379             if (looking_at(buf, &i, "\\   ")) {
3380                 if (prevColor != ColorNormal) {
3381                     if (oldi > next_out) {
3382                         SendToPlayer(&buf[next_out], oldi - next_out);
3383                         next_out = oldi;
3384                     }
3385                     Colorize(prevColor, TRUE);
3386                     curColor = prevColor;
3387                 }
3388                 if (savingComment) {
3389                     parse_pos = i - oldi;
3390                     memcpy(parse, &buf[oldi], parse_pos);
3391                     parse[parse_pos] = NULLCHAR;
3392                     started = STARTED_COMMENT;
3393                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3394                         chattingPartner = savingComment - 3; // kludge to remember the box
3395                 } else {
3396                     started = STARTED_CHATTER;
3397                 }
3398                 continue;
3399             }
3400
3401             if (looking_at(buf, &i, "Black Strength :") ||
3402                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3403                 looking_at(buf, &i, "<10>") ||
3404                 looking_at(buf, &i, "#@#")) {
3405                 /* Wrong board style */
3406                 loggedOn = TRUE;
3407                 SendToICS(ics_prefix);
3408                 SendToICS("set style 12\n");
3409                 SendToICS(ics_prefix);
3410                 SendToICS("refresh\n");
3411                 continue;
3412             }
3413
3414             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3415                 ICSInitScript();
3416                 have_sent_ICS_logon = 1;
3417                 continue;
3418             }
3419
3420             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3421                 (looking_at(buf, &i, "\n<12> ") ||
3422                  looking_at(buf, &i, "<12> "))) {
3423                 loggedOn = TRUE;
3424                 if (oldi > next_out) {
3425                     SendToPlayer(&buf[next_out], oldi - next_out);
3426                 }
3427                 next_out = i;
3428                 started = STARTED_BOARD;
3429                 parse_pos = 0;
3430                 continue;
3431             }
3432
3433             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3434                 looking_at(buf, &i, "<b1> ")) {
3435                 if (oldi > next_out) {
3436                     SendToPlayer(&buf[next_out], oldi - next_out);
3437                 }
3438                 next_out = i;
3439                 started = STARTED_HOLDINGS;
3440                 parse_pos = 0;
3441                 continue;
3442             }
3443
3444             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3445                 loggedOn = TRUE;
3446                 /* Header for a move list -- first line */
3447
3448                 switch (ics_getting_history) {
3449                   case H_FALSE:
3450                     switch (gameMode) {
3451                       case IcsIdle:
3452                       case BeginningOfGame:
3453                         /* User typed "moves" or "oldmoves" while we
3454                            were idle.  Pretend we asked for these
3455                            moves and soak them up so user can step
3456                            through them and/or save them.
3457                            */
3458                         Reset(FALSE, TRUE);
3459                         gameMode = IcsObserving;
3460                         ModeHighlight();
3461                         ics_gamenum = -1;
3462                         ics_getting_history = H_GOT_UNREQ_HEADER;
3463                         break;
3464                       case EditGame: /*?*/
3465                       case EditPosition: /*?*/
3466                         /* Should above feature work in these modes too? */
3467                         /* For now it doesn't */
3468                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3469                         break;
3470                       default:
3471                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3472                         break;
3473                     }
3474                     break;
3475                   case H_REQUESTED:
3476                     /* Is this the right one? */
3477                     if (gameInfo.white && gameInfo.black &&
3478                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3479                         strcmp(gameInfo.black, star_match[2]) == 0) {
3480                         /* All is well */
3481                         ics_getting_history = H_GOT_REQ_HEADER;
3482                     }
3483                     break;
3484                   case H_GOT_REQ_HEADER:
3485                   case H_GOT_UNREQ_HEADER:
3486                   case H_GOT_UNWANTED_HEADER:
3487                   case H_GETTING_MOVES:
3488                     /* Should not happen */
3489                     DisplayError(_("Error gathering move list: two headers"), 0);
3490                     ics_getting_history = H_FALSE;
3491                     break;
3492                 }
3493
3494                 /* Save player ratings into gameInfo if needed */
3495                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3496                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3497                     (gameInfo.whiteRating == -1 ||
3498                      gameInfo.blackRating == -1)) {
3499
3500                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3501                     gameInfo.blackRating = string_to_rating(star_match[3]);
3502                     if (appData.debugMode)
3503                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3504                               gameInfo.whiteRating, gameInfo.blackRating);
3505                 }
3506                 continue;
3507             }
3508
3509             if (looking_at(buf, &i,
3510               "* * match, initial time: * minute*, increment: * second")) {
3511                 /* Header for a move list -- second line */
3512                 /* Initial board will follow if this is a wild game */
3513                 if (gameInfo.event != NULL) free(gameInfo.event);
3514                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3515                 gameInfo.event = StrSave(str);
3516                 /* [HGM] we switched variant. Translate boards if needed. */
3517                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3518                 continue;
3519             }
3520
3521             if (looking_at(buf, &i, "Move  ")) {
3522                 /* Beginning of a move list */
3523                 switch (ics_getting_history) {
3524                   case H_FALSE:
3525                     /* Normally should not happen */
3526                     /* Maybe user hit reset while we were parsing */
3527                     break;
3528                   case H_REQUESTED:
3529                     /* Happens if we are ignoring a move list that is not
3530                      * the one we just requested.  Common if the user
3531                      * tries to observe two games without turning off
3532                      * getMoveList */
3533                     break;
3534                   case H_GETTING_MOVES:
3535                     /* Should not happen */
3536                     DisplayError(_("Error gathering move list: nested"), 0);
3537                     ics_getting_history = H_FALSE;
3538                     break;
3539                   case H_GOT_REQ_HEADER:
3540                     ics_getting_history = H_GETTING_MOVES;
3541                     started = STARTED_MOVES;
3542                     parse_pos = 0;
3543                     if (oldi > next_out) {
3544                         SendToPlayer(&buf[next_out], oldi - next_out);
3545                     }
3546                     break;
3547                   case H_GOT_UNREQ_HEADER:
3548                     ics_getting_history = H_GETTING_MOVES;
3549                     started = STARTED_MOVES_NOHIDE;
3550                     parse_pos = 0;
3551                     break;
3552                   case H_GOT_UNWANTED_HEADER:
3553                     ics_getting_history = H_FALSE;
3554                     break;
3555                 }
3556                 continue;
3557             }
3558
3559             if (looking_at(buf, &i, "% ") ||
3560                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3561                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3562                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3563                     soughtPending = FALSE;
3564                     seekGraphUp = TRUE;
3565                     DrawSeekGraph();
3566                 }
3567                 if(suppressKibitz) next_out = i;
3568                 savingComment = FALSE;
3569                 suppressKibitz = 0;
3570                 switch (started) {
3571                   case STARTED_MOVES:
3572                   case STARTED_MOVES_NOHIDE:
3573                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3574                     parse[parse_pos + i - oldi] = NULLCHAR;
3575                     ParseGameHistory(parse);
3576 #if ZIPPY
3577                     if (appData.zippyPlay && first.initDone) {
3578                         FeedMovesToProgram(&first, forwardMostMove);
3579                         if (gameMode == IcsPlayingWhite) {
3580                             if (WhiteOnMove(forwardMostMove)) {
3581                                 if (first.sendTime) {
3582                                   if (first.useColors) {
3583                                     SendToProgram("black\n", &first);
3584                                   }
3585                                   SendTimeRemaining(&first, TRUE);
3586                                 }
3587                                 if (first.useColors) {
3588                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3589                                 }
3590                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3591                                 first.maybeThinking = TRUE;
3592                             } else {
3593                                 if (first.usePlayother) {
3594                                   if (first.sendTime) {
3595                                     SendTimeRemaining(&first, TRUE);
3596                                   }
3597                                   SendToProgram("playother\n", &first);
3598                                   firstMove = FALSE;
3599                                 } else {
3600                                   firstMove = TRUE;
3601                                 }
3602                             }
3603                         } else if (gameMode == IcsPlayingBlack) {
3604                             if (!WhiteOnMove(forwardMostMove)) {
3605                                 if (first.sendTime) {
3606                                   if (first.useColors) {
3607                                     SendToProgram("white\n", &first);
3608                                   }
3609                                   SendTimeRemaining(&first, FALSE);
3610                                 }
3611                                 if (first.useColors) {
3612                                   SendToProgram("black\n", &first);
3613                                 }
3614                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3615                                 first.maybeThinking = TRUE;
3616                             } else {
3617                                 if (first.usePlayother) {
3618                                   if (first.sendTime) {
3619                                     SendTimeRemaining(&first, FALSE);
3620                                   }
3621                                   SendToProgram("playother\n", &first);
3622                                   firstMove = FALSE;
3623                                 } else {
3624                                   firstMove = TRUE;
3625                                 }
3626                             }
3627                         }
3628                     }
3629 #endif
3630                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3631                         /* Moves came from oldmoves or moves command
3632                            while we weren't doing anything else.
3633                            */
3634                         currentMove = forwardMostMove;
3635                         ClearHighlights();/*!!could figure this out*/
3636                         flipView = appData.flipView;
3637                         DrawPosition(TRUE, boards[currentMove]);
3638                         DisplayBothClocks();
3639                         snprintf(str, MSG_SIZ, "%s vs. %s",
3640                                 gameInfo.white, gameInfo.black);
3641                         DisplayTitle(str);
3642                         gameMode = IcsIdle;
3643                     } else {
3644                         /* Moves were history of an active game */
3645                         if (gameInfo.resultDetails != NULL) {
3646                             free(gameInfo.resultDetails);
3647                             gameInfo.resultDetails = NULL;
3648                         }
3649                     }
3650                     HistorySet(parseList, backwardMostMove,
3651                                forwardMostMove, currentMove-1);
3652                     DisplayMove(currentMove - 1);
3653                     if (started == STARTED_MOVES) next_out = i;
3654                     started = STARTED_NONE;
3655                     ics_getting_history = H_FALSE;
3656                     break;
3657
3658                   case STARTED_OBSERVE:
3659                     started = STARTED_NONE;
3660                     SendToICS(ics_prefix);
3661                     SendToICS("refresh\n");
3662                     break;
3663
3664                   default:
3665                     break;
3666                 }
3667                 if(bookHit) { // [HGM] book: simulate book reply
3668                     static char bookMove[MSG_SIZ]; // a bit generous?
3669
3670                     programStats.nodes = programStats.depth = programStats.time =
3671                     programStats.score = programStats.got_only_move = 0;
3672                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3673
3674                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3675                     strcat(bookMove, bookHit);
3676                     HandleMachineMove(bookMove, &first);
3677                 }
3678                 continue;
3679             }
3680
3681             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3682                  started == STARTED_HOLDINGS ||
3683                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3684                 /* Accumulate characters in move list or board */
3685                 parse[parse_pos++] = buf[i];
3686             }
3687
3688             /* Start of game messages.  Mostly we detect start of game
3689                when the first board image arrives.  On some versions
3690                of the ICS, though, we need to do a "refresh" after starting
3691                to observe in order to get the current board right away. */
3692             if (looking_at(buf, &i, "Adding game * to observation list")) {
3693                 started = STARTED_OBSERVE;
3694                 continue;
3695             }
3696
3697             /* Handle auto-observe */
3698             if (appData.autoObserve &&
3699                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3700                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3701                 char *player;
3702                 /* Choose the player that was highlighted, if any. */
3703                 if (star_match[0][0] == '\033' ||
3704                     star_match[1][0] != '\033') {
3705                     player = star_match[0];
3706                 } else {
3707                     player = star_match[2];
3708                 }
3709                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3710                         ics_prefix, StripHighlightAndTitle(player));
3711                 SendToICS(str);
3712
3713                 /* Save ratings from notify string */
3714                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3715                 player1Rating = string_to_rating(star_match[1]);
3716                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3717                 player2Rating = string_to_rating(star_match[3]);
3718
3719                 if (appData.debugMode)
3720                   fprintf(debugFP,
3721                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3722                           player1Name, player1Rating,
3723                           player2Name, player2Rating);
3724
3725                 continue;
3726             }
3727
3728             /* Deal with automatic examine mode after a game,
3729                and with IcsObserving -> IcsExamining transition */
3730             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3731                 looking_at(buf, &i, "has made you an examiner of game *")) {
3732
3733                 int gamenum = atoi(star_match[0]);
3734                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3735                     gamenum == ics_gamenum) {
3736                     /* We were already playing or observing this game;
3737                        no need to refetch history */
3738                     gameMode = IcsExamining;
3739                     if (pausing) {
3740                         pauseExamForwardMostMove = forwardMostMove;
3741                     } else if (currentMove < forwardMostMove) {
3742                         ForwardInner(forwardMostMove);
3743                     }
3744                 } else {
3745                     /* I don't think this case really can happen */
3746                     SendToICS(ics_prefix);
3747                     SendToICS("refresh\n");
3748                 }
3749                 continue;
3750             }
3751
3752             /* Error messages */
3753 //          if (ics_user_moved) {
3754             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3755                 if (looking_at(buf, &i, "Illegal move") ||
3756                     looking_at(buf, &i, "Not a legal move") ||
3757                     looking_at(buf, &i, "Your king is in check") ||
3758                     looking_at(buf, &i, "It isn't your turn") ||
3759                     looking_at(buf, &i, "It is not your move")) {
3760                     /* Illegal move */
3761                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3762                         currentMove = forwardMostMove-1;
3763                         DisplayMove(currentMove - 1); /* before DMError */
3764                         DrawPosition(FALSE, boards[currentMove]);
3765                         SwitchClocks(forwardMostMove-1); // [HGM] race
3766                         DisplayBothClocks();
3767                     }
3768                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3769                     ics_user_moved = 0;
3770                     continue;
3771                 }
3772             }
3773
3774             if (looking_at(buf, &i, "still have time") ||
3775                 looking_at(buf, &i, "not out of time") ||
3776                 looking_at(buf, &i, "either player is out of time") ||
3777                 looking_at(buf, &i, "has timeseal; checking")) {
3778                 /* We must have called his flag a little too soon */
3779                 whiteFlag = blackFlag = FALSE;
3780                 continue;
3781             }
3782
3783             if (looking_at(buf, &i, "added * seconds to") ||
3784                 looking_at(buf, &i, "seconds were added to")) {
3785                 /* Update the clocks */
3786                 SendToICS(ics_prefix);
3787                 SendToICS("refresh\n");
3788                 continue;
3789             }
3790
3791             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3792                 ics_clock_paused = TRUE;
3793                 StopClocks();
3794                 continue;
3795             }
3796
3797             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3798                 ics_clock_paused = FALSE;
3799                 StartClocks();
3800                 continue;
3801             }
3802
3803             /* Grab player ratings from the Creating: message.
3804                Note we have to check for the special case when
3805                the ICS inserts things like [white] or [black]. */
3806             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3807                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3808                 /* star_matches:
3809                    0    player 1 name (not necessarily white)
3810                    1    player 1 rating
3811                    2    empty, white, or black (IGNORED)
3812                    3    player 2 name (not necessarily black)
3813                    4    player 2 rating
3814
3815                    The names/ratings are sorted out when the game
3816                    actually starts (below).
3817                 */
3818                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3819                 player1Rating = string_to_rating(star_match[1]);
3820                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3821                 player2Rating = string_to_rating(star_match[4]);
3822
3823                 if (appData.debugMode)
3824                   fprintf(debugFP,
3825                           "Ratings from 'Creating:' %s %d, %s %d\n",
3826                           player1Name, player1Rating,
3827                           player2Name, player2Rating);
3828
3829                 continue;
3830             }
3831
3832             /* Improved generic start/end-of-game messages */
3833             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3834                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3835                 /* If tkind == 0: */
3836                 /* star_match[0] is the game number */
3837                 /*           [1] is the white player's name */
3838                 /*           [2] is the black player's name */
3839                 /* For end-of-game: */
3840                 /*           [3] is the reason for the game end */
3841                 /*           [4] is a PGN end game-token, preceded by " " */
3842                 /* For start-of-game: */
3843                 /*           [3] begins with "Creating" or "Continuing" */
3844                 /*           [4] is " *" or empty (don't care). */
3845                 int gamenum = atoi(star_match[0]);
3846                 char *whitename, *blackname, *why, *endtoken;
3847                 ChessMove endtype = EndOfFile;
3848
3849                 if (tkind == 0) {
3850                   whitename = star_match[1];
3851                   blackname = star_match[2];
3852                   why = star_match[3];
3853                   endtoken = star_match[4];
3854                 } else {
3855                   whitename = star_match[1];
3856                   blackname = star_match[3];
3857                   why = star_match[5];
3858                   endtoken = star_match[6];
3859                 }
3860
3861                 /* Game start messages */
3862                 if (strncmp(why, "Creating ", 9) == 0 ||
3863                     strncmp(why, "Continuing ", 11) == 0) {
3864                     gs_gamenum = gamenum;
3865                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3866                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3867 #if ZIPPY
3868                     if (appData.zippyPlay) {
3869                         ZippyGameStart(whitename, blackname);
3870                     }
3871 #endif /*ZIPPY*/
3872                     partnerBoardValid = FALSE; // [HGM] bughouse
3873                     continue;
3874                 }
3875
3876                 /* Game end messages */
3877                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3878                     ics_gamenum != gamenum) {
3879                     continue;
3880                 }
3881                 while (endtoken[0] == ' ') endtoken++;
3882                 switch (endtoken[0]) {
3883                   case '*':
3884                   default:
3885                     endtype = GameUnfinished;
3886                     break;
3887                   case '0':
3888                     endtype = BlackWins;
3889                     break;
3890                   case '1':
3891                     if (endtoken[1] == '/')
3892                       endtype = GameIsDrawn;
3893                     else
3894                       endtype = WhiteWins;
3895                     break;
3896                 }
3897                 GameEnds(endtype, why, GE_ICS);
3898 #if ZIPPY
3899                 if (appData.zippyPlay && first.initDone) {
3900                     ZippyGameEnd(endtype, why);
3901                     if (first.pr == NoProc) {
3902                       /* Start the next process early so that we'll
3903                          be ready for the next challenge */
3904                       StartChessProgram(&first);
3905                     }
3906                     /* Send "new" early, in case this command takes
3907                        a long time to finish, so that we'll be ready
3908                        for the next challenge. */
3909                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3910                     Reset(TRUE, TRUE);
3911                 }
3912 #endif /*ZIPPY*/
3913                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3914                 continue;
3915             }
3916
3917             if (looking_at(buf, &i, "Removing game * from observation") ||
3918                 looking_at(buf, &i, "no longer observing game *") ||
3919                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3920                 if (gameMode == IcsObserving &&
3921                     atoi(star_match[0]) == ics_gamenum)
3922                   {
3923                       /* icsEngineAnalyze */
3924                       if (appData.icsEngineAnalyze) {
3925                             ExitAnalyzeMode();
3926                             ModeHighlight();
3927                       }
3928                       StopClocks();
3929                       gameMode = IcsIdle;
3930                       ics_gamenum = -1;
3931                       ics_user_moved = FALSE;
3932                   }
3933                 continue;
3934             }
3935
3936             if (looking_at(buf, &i, "no longer examining game *")) {
3937                 if (gameMode == IcsExamining &&
3938                     atoi(star_match[0]) == ics_gamenum)
3939                   {
3940                       gameMode = IcsIdle;
3941                       ics_gamenum = -1;
3942                       ics_user_moved = FALSE;
3943                   }
3944                 continue;
3945             }
3946
3947             /* Advance leftover_start past any newlines we find,
3948                so only partial lines can get reparsed */
3949             if (looking_at(buf, &i, "\n")) {
3950                 prevColor = curColor;
3951                 if (curColor != ColorNormal) {
3952                     if (oldi > next_out) {
3953                         SendToPlayer(&buf[next_out], oldi - next_out);
3954                         next_out = oldi;
3955                     }
3956                     Colorize(ColorNormal, FALSE);
3957                     curColor = ColorNormal;
3958                 }
3959                 if (started == STARTED_BOARD) {
3960                     started = STARTED_NONE;
3961                     parse[parse_pos] = NULLCHAR;
3962                     ParseBoard12(parse);
3963                     ics_user_moved = 0;
3964
3965                     /* Send premove here */
3966                     if (appData.premove) {
3967                       char str[MSG_SIZ];
3968                       if (currentMove == 0 &&
3969                           gameMode == IcsPlayingWhite &&
3970                           appData.premoveWhite) {
3971                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3972                         if (appData.debugMode)
3973                           fprintf(debugFP, "Sending premove:\n");
3974                         SendToICS(str);
3975                       } else if (currentMove == 1 &&
3976                                  gameMode == IcsPlayingBlack &&
3977                                  appData.premoveBlack) {
3978                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3979                         if (appData.debugMode)
3980                           fprintf(debugFP, "Sending premove:\n");
3981                         SendToICS(str);
3982                       } else if (gotPremove) {
3983                         gotPremove = 0;
3984                         ClearPremoveHighlights();
3985                         if (appData.debugMode)
3986                           fprintf(debugFP, "Sending premove:\n");
3987                           UserMoveEvent(premoveFromX, premoveFromY,
3988                                         premoveToX, premoveToY,
3989                                         premovePromoChar);
3990                       }
3991                     }
3992
3993                     /* Usually suppress following prompt */
3994                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3995                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3996                         if (looking_at(buf, &i, "*% ")) {
3997                             savingComment = FALSE;
3998                             suppressKibitz = 0;
3999                         }
4000                     }
4001                     next_out = i;
4002                 } else if (started == STARTED_HOLDINGS) {
4003                     int gamenum;
4004                     char new_piece[MSG_SIZ];
4005                     started = STARTED_NONE;
4006                     parse[parse_pos] = NULLCHAR;
4007                     if (appData.debugMode)
4008                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4009                                                         parse, currentMove);
4010                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4011                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4012                         if (gameInfo.variant == VariantNormal) {
4013                           /* [HGM] We seem to switch variant during a game!
4014                            * Presumably no holdings were displayed, so we have
4015                            * to move the position two files to the right to
4016                            * create room for them!
4017                            */
4018                           VariantClass newVariant;
4019                           switch(gameInfo.boardWidth) { // base guess on board width
4020                                 case 9:  newVariant = VariantShogi; break;
4021                                 case 10: newVariant = VariantGreat; break;
4022                                 default: newVariant = VariantCrazyhouse; break;
4023                           }
4024                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4025                           /* Get a move list just to see the header, which
4026                              will tell us whether this is really bug or zh */
4027                           if (ics_getting_history == H_FALSE) {
4028                             ics_getting_history = H_REQUESTED;
4029                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4030                             SendToICS(str);
4031                           }
4032                         }
4033                         new_piece[0] = NULLCHAR;
4034                         sscanf(parse, "game %d white [%s black [%s <- %s",
4035                                &gamenum, white_holding, black_holding,
4036                                new_piece);
4037                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4038                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4039                         /* [HGM] copy holdings to board holdings area */
4040                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4041                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4042                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4043 #if ZIPPY
4044                         if (appData.zippyPlay && first.initDone) {
4045                             ZippyHoldings(white_holding, black_holding,
4046                                           new_piece);
4047                         }
4048 #endif /*ZIPPY*/
4049                         if (tinyLayout || smallLayout) {
4050                             char wh[16], bh[16];
4051                             PackHolding(wh, white_holding);
4052                             PackHolding(bh, black_holding);
4053                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4054                                     gameInfo.white, gameInfo.black);
4055                         } else {
4056                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4057                                     gameInfo.white, white_holding,
4058                                     gameInfo.black, black_holding);
4059                         }
4060                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4061                         DrawPosition(FALSE, boards[currentMove]);
4062                         DisplayTitle(str);
4063                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4064                         sscanf(parse, "game %d white [%s black [%s <- %s",
4065                                &gamenum, white_holding, black_holding,
4066                                new_piece);
4067                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4068                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4069                         /* [HGM] copy holdings to partner-board holdings area */
4070                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4071                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4072                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4073                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4074                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4075                       }
4076                     }
4077                     /* Suppress following prompt */
4078                     if (looking_at(buf, &i, "*% ")) {
4079                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4080                         savingComment = FALSE;
4081                         suppressKibitz = 0;
4082                     }
4083                     next_out = i;
4084                 }
4085                 continue;
4086             }
4087
4088             i++;                /* skip unparsed character and loop back */
4089         }
4090
4091         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4092 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4093 //          SendToPlayer(&buf[next_out], i - next_out);
4094             started != STARTED_HOLDINGS && leftover_start > next_out) {
4095             SendToPlayer(&buf[next_out], leftover_start - next_out);
4096             next_out = i;
4097         }
4098
4099         leftover_len = buf_len - leftover_start;
4100         /* if buffer ends with something we couldn't parse,
4101            reparse it after appending the next read */
4102
4103     } else if (count == 0) {
4104         RemoveInputSource(isr);
4105         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4106     } else {
4107         DisplayFatalError(_("Error reading from ICS"), error, 1);
4108     }
4109 }
4110
4111
4112 /* Board style 12 looks like this:
4113
4114    <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
4115
4116  * The "<12> " is stripped before it gets to this routine.  The two
4117  * trailing 0's (flip state and clock ticking) are later addition, and
4118  * some chess servers may not have them, or may have only the first.
4119  * Additional trailing fields may be added in the future.
4120  */
4121
4122 #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"
4123
4124 #define RELATION_OBSERVING_PLAYED    0
4125 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4126 #define RELATION_PLAYING_MYMOVE      1
4127 #define RELATION_PLAYING_NOTMYMOVE  -1
4128 #define RELATION_EXAMINING           2
4129 #define RELATION_ISOLATED_BOARD     -3
4130 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4131
4132 void
4133 ParseBoard12(string)
4134      char *string;
4135 {
4136     GameMode newGameMode;
4137     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4138     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4139     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4140     char to_play, board_chars[200];
4141     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4142     char black[32], white[32];
4143     Board board;
4144     int prevMove = currentMove;
4145     int ticking = 2;
4146     ChessMove moveType;
4147     int fromX, fromY, toX, toY;
4148     char promoChar;
4149     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4150     char *bookHit = NULL; // [HGM] book
4151     Boolean weird = FALSE, reqFlag = FALSE;
4152
4153     fromX = fromY = toX = toY = -1;
4154
4155     newGame = FALSE;
4156
4157     if (appData.debugMode)
4158       fprintf(debugFP, _("Parsing board: %s\n"), string);
4159
4160     move_str[0] = NULLCHAR;
4161     elapsed_time[0] = NULLCHAR;
4162     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4163         int  i = 0, j;
4164         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4165             if(string[i] == ' ') { ranks++; files = 0; }
4166             else files++;
4167             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4168             i++;
4169         }
4170         for(j = 0; j <i; j++) board_chars[j] = string[j];
4171         board_chars[i] = '\0';
4172         string += i + 1;
4173     }
4174     n = sscanf(string, PATTERN, &to_play, &double_push,
4175                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4176                &gamenum, white, black, &relation, &basetime, &increment,
4177                &white_stren, &black_stren, &white_time, &black_time,
4178                &moveNum, str, elapsed_time, move_str, &ics_flip,
4179                &ticking);
4180
4181     if (n < 21) {
4182         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4183         DisplayError(str, 0);
4184         return;
4185     }
4186
4187     /* Convert the move number to internal form */
4188     moveNum = (moveNum - 1) * 2;
4189     if (to_play == 'B') moveNum++;
4190     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4191       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4192                         0, 1);
4193       return;
4194     }
4195
4196     switch (relation) {
4197       case RELATION_OBSERVING_PLAYED:
4198       case RELATION_OBSERVING_STATIC:
4199         if (gamenum == -1) {
4200             /* Old ICC buglet */
4201             relation = RELATION_OBSERVING_STATIC;
4202         }
4203         newGameMode = IcsObserving;
4204         break;
4205       case RELATION_PLAYING_MYMOVE:
4206       case RELATION_PLAYING_NOTMYMOVE:
4207         newGameMode =
4208           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4209             IcsPlayingWhite : IcsPlayingBlack;
4210         break;
4211       case RELATION_EXAMINING:
4212         newGameMode = IcsExamining;
4213         break;
4214       case RELATION_ISOLATED_BOARD:
4215       default:
4216         /* Just display this board.  If user was doing something else,
4217            we will forget about it until the next board comes. */
4218         newGameMode = IcsIdle;
4219         break;
4220       case RELATION_STARTING_POSITION:
4221         newGameMode = gameMode;
4222         break;
4223     }
4224
4225     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4226          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4227       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4228       char *toSqr;
4229       for (k = 0; k < ranks; k++) {
4230         for (j = 0; j < files; j++)
4231           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4232         if(gameInfo.holdingsWidth > 1) {
4233              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4234              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4235         }
4236       }
4237       CopyBoard(partnerBoard, board);
4238       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4239         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4240         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4241       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4242       if(toSqr = strchr(str, '-')) {
4243         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4244         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4245       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4246       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4247       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4248       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4249       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4250       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4251                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4252       DisplayMessage(partnerStatus, "");
4253         partnerBoardValid = TRUE;
4254       return;
4255     }
4256
4257     /* Modify behavior for initial board display on move listing
4258        of wild games.
4259        */
4260     switch (ics_getting_history) {
4261       case H_FALSE:
4262       case H_REQUESTED:
4263         break;
4264       case H_GOT_REQ_HEADER:
4265       case H_GOT_UNREQ_HEADER:
4266         /* This is the initial position of the current game */
4267         gamenum = ics_gamenum;
4268         moveNum = 0;            /* old ICS bug workaround */
4269         if (to_play == 'B') {
4270           startedFromSetupPosition = TRUE;
4271           blackPlaysFirst = TRUE;
4272           moveNum = 1;
4273           if (forwardMostMove == 0) forwardMostMove = 1;
4274           if (backwardMostMove == 0) backwardMostMove = 1;
4275           if (currentMove == 0) currentMove = 1;
4276         }
4277         newGameMode = gameMode;
4278         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4279         break;
4280       case H_GOT_UNWANTED_HEADER:
4281         /* This is an initial board that we don't want */
4282         return;
4283       case H_GETTING_MOVES:
4284         /* Should not happen */
4285         DisplayError(_("Error gathering move list: extra board"), 0);
4286         ics_getting_history = H_FALSE;
4287         return;
4288     }
4289
4290    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4291                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4292      /* [HGM] We seem to have switched variant unexpectedly
4293       * Try to guess new variant from board size
4294       */
4295           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4296           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4297           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4298           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4299           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4300           if(!weird) newVariant = VariantNormal;
4301           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4302           /* Get a move list just to see the header, which
4303              will tell us whether this is really bug or zh */
4304           if (ics_getting_history == H_FALSE) {
4305             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4306             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4307             SendToICS(str);
4308           }
4309     }
4310
4311     /* Take action if this is the first board of a new game, or of a
4312        different game than is currently being displayed.  */
4313     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4314         relation == RELATION_ISOLATED_BOARD) {
4315
4316         /* Forget the old game and get the history (if any) of the new one */
4317         if (gameMode != BeginningOfGame) {
4318           Reset(TRUE, TRUE);
4319         }
4320         newGame = TRUE;
4321         if (appData.autoRaiseBoard) BoardToTop();
4322         prevMove = -3;
4323         if (gamenum == -1) {
4324             newGameMode = IcsIdle;
4325         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4326                    appData.getMoveList && !reqFlag) {
4327             /* Need to get game history */
4328             ics_getting_history = H_REQUESTED;
4329             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4330             SendToICS(str);
4331         }
4332
4333         /* Initially flip the board to have black on the bottom if playing
4334            black or if the ICS flip flag is set, but let the user change
4335            it with the Flip View button. */
4336         flipView = appData.autoFlipView ?
4337           (newGameMode == IcsPlayingBlack) || ics_flip :
4338           appData.flipView;
4339
4340         /* Done with values from previous mode; copy in new ones */
4341         gameMode = newGameMode;
4342         ModeHighlight();
4343         ics_gamenum = gamenum;
4344         if (gamenum == gs_gamenum) {
4345             int klen = strlen(gs_kind);
4346             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4347             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4348             gameInfo.event = StrSave(str);
4349         } else {
4350             gameInfo.event = StrSave("ICS game");
4351         }
4352         gameInfo.site = StrSave(appData.icsHost);
4353         gameInfo.date = PGNDate();
4354         gameInfo.round = StrSave("-");
4355         gameInfo.white = StrSave(white);
4356         gameInfo.black = StrSave(black);
4357         timeControl = basetime * 60 * 1000;
4358         timeControl_2 = 0;
4359         timeIncrement = increment * 1000;
4360         movesPerSession = 0;
4361         gameInfo.timeControl = TimeControlTagValue();
4362         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4363   if (appData.debugMode) {
4364     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4365     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4366     setbuf(debugFP, NULL);
4367   }
4368
4369         gameInfo.outOfBook = NULL;
4370
4371         /* Do we have the ratings? */
4372         if (strcmp(player1Name, white) == 0 &&
4373             strcmp(player2Name, black) == 0) {
4374             if (appData.debugMode)
4375               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4376                       player1Rating, player2Rating);
4377             gameInfo.whiteRating = player1Rating;
4378             gameInfo.blackRating = player2Rating;
4379         } else if (strcmp(player2Name, white) == 0 &&
4380                    strcmp(player1Name, black) == 0) {
4381             if (appData.debugMode)
4382               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4383                       player2Rating, player1Rating);
4384             gameInfo.whiteRating = player2Rating;
4385             gameInfo.blackRating = player1Rating;
4386         }
4387         player1Name[0] = player2Name[0] = NULLCHAR;
4388
4389         /* Silence shouts if requested */
4390         if (appData.quietPlay &&
4391             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4392             SendToICS(ics_prefix);
4393             SendToICS("set shout 0\n");
4394         }
4395     }
4396
4397     /* Deal with midgame name changes */
4398     if (!newGame) {
4399         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4400             if (gameInfo.white) free(gameInfo.white);
4401             gameInfo.white = StrSave(white);
4402         }
4403         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4404             if (gameInfo.black) free(gameInfo.black);
4405             gameInfo.black = StrSave(black);
4406         }
4407     }
4408
4409     /* Throw away game result if anything actually changes in examine mode */
4410     if (gameMode == IcsExamining && !newGame) {
4411         gameInfo.result = GameUnfinished;
4412         if (gameInfo.resultDetails != NULL) {
4413             free(gameInfo.resultDetails);
4414             gameInfo.resultDetails = NULL;
4415         }
4416     }
4417
4418     /* In pausing && IcsExamining mode, we ignore boards coming
4419        in if they are in a different variation than we are. */
4420     if (pauseExamInvalid) return;
4421     if (pausing && gameMode == IcsExamining) {
4422         if (moveNum <= pauseExamForwardMostMove) {
4423             pauseExamInvalid = TRUE;
4424             forwardMostMove = pauseExamForwardMostMove;
4425             return;
4426         }
4427     }
4428
4429   if (appData.debugMode) {
4430     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4431   }
4432     /* Parse the board */
4433     for (k = 0; k < ranks; k++) {
4434       for (j = 0; j < files; j++)
4435         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4436       if(gameInfo.holdingsWidth > 1) {
4437            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4438            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4439       }
4440     }
4441     CopyBoard(boards[moveNum], board);
4442     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4443     if (moveNum == 0) {
4444         startedFromSetupPosition =
4445           !CompareBoards(board, initialPosition);
4446         if(startedFromSetupPosition)
4447             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4448     }
4449
4450     /* [HGM] Set castling rights. Take the outermost Rooks,
4451        to make it also work for FRC opening positions. Note that board12
4452        is really defective for later FRC positions, as it has no way to
4453        indicate which Rook can castle if they are on the same side of King.
4454        For the initial position we grant rights to the outermost Rooks,
4455        and remember thos rights, and we then copy them on positions
4456        later in an FRC game. This means WB might not recognize castlings with
4457        Rooks that have moved back to their original position as illegal,
4458        but in ICS mode that is not its job anyway.
4459     */
4460     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4461     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4462
4463         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4464             if(board[0][i] == WhiteRook) j = i;
4465         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4466         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4467             if(board[0][i] == WhiteRook) j = i;
4468         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4469         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4470             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4471         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4472         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4473             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4474         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4475
4476         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4477         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4478             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4479         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4480             if(board[BOARD_HEIGHT-1][k] == bKing)
4481                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4482         if(gameInfo.variant == VariantTwoKings) {
4483             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4484             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4485             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4486         }
4487     } else { int r;
4488         r = boards[moveNum][CASTLING][0] = initialRights[0];
4489         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4490         r = boards[moveNum][CASTLING][1] = initialRights[1];
4491         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4492         r = boards[moveNum][CASTLING][3] = initialRights[3];
4493         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4494         r = boards[moveNum][CASTLING][4] = initialRights[4];
4495         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4496         /* wildcastle kludge: always assume King has rights */
4497         r = boards[moveNum][CASTLING][2] = initialRights[2];
4498         r = boards[moveNum][CASTLING][5] = initialRights[5];
4499     }
4500     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4501     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4502
4503
4504     if (ics_getting_history == H_GOT_REQ_HEADER ||
4505         ics_getting_history == H_GOT_UNREQ_HEADER) {
4506         /* This was an initial position from a move list, not
4507            the current position */
4508         return;
4509     }
4510
4511     /* Update currentMove and known move number limits */
4512     newMove = newGame || moveNum > forwardMostMove;
4513
4514     if (newGame) {
4515         forwardMostMove = backwardMostMove = currentMove = moveNum;
4516         if (gameMode == IcsExamining && moveNum == 0) {
4517           /* Workaround for ICS limitation: we are not told the wild
4518              type when starting to examine a game.  But if we ask for
4519              the move list, the move list header will tell us */
4520             ics_getting_history = H_REQUESTED;
4521             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4522             SendToICS(str);
4523         }
4524     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4525                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4526 #if ZIPPY
4527         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4528         /* [HGM] applied this also to an engine that is silently watching        */
4529         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4530             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4531             gameInfo.variant == currentlyInitializedVariant) {
4532           takeback = forwardMostMove - moveNum;
4533           for (i = 0; i < takeback; i++) {
4534             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4535             SendToProgram("undo\n", &first);
4536           }
4537         }
4538 #endif
4539
4540         forwardMostMove = moveNum;
4541         if (!pausing || currentMove > forwardMostMove)
4542           currentMove = forwardMostMove;
4543     } else {
4544         /* New part of history that is not contiguous with old part */
4545         if (pausing && gameMode == IcsExamining) {
4546             pauseExamInvalid = TRUE;
4547             forwardMostMove = pauseExamForwardMostMove;
4548             return;
4549         }
4550         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4551 #if ZIPPY
4552             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4553                 // [HGM] when we will receive the move list we now request, it will be
4554                 // fed to the engine from the first move on. So if the engine is not
4555                 // in the initial position now, bring it there.
4556                 InitChessProgram(&first, 0);
4557             }
4558 #endif
4559             ics_getting_history = H_REQUESTED;
4560             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4561             SendToICS(str);
4562         }
4563         forwardMostMove = backwardMostMove = currentMove = moveNum;
4564     }
4565
4566     /* Update the clocks */
4567     if (strchr(elapsed_time, '.')) {
4568       /* Time is in ms */
4569       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4570       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4571     } else {
4572       /* Time is in seconds */
4573       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4574       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4575     }
4576
4577
4578 #if ZIPPY
4579     if (appData.zippyPlay && newGame &&
4580         gameMode != IcsObserving && gameMode != IcsIdle &&
4581         gameMode != IcsExamining)
4582       ZippyFirstBoard(moveNum, basetime, increment);
4583 #endif
4584
4585     /* Put the move on the move list, first converting
4586        to canonical algebraic form. */
4587     if (moveNum > 0) {
4588   if (appData.debugMode) {
4589     if (appData.debugMode) { int f = forwardMostMove;
4590         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4591                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4592                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4593     }
4594     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4595     fprintf(debugFP, "moveNum = %d\n", moveNum);
4596     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4597     setbuf(debugFP, NULL);
4598   }
4599         if (moveNum <= backwardMostMove) {
4600             /* We don't know what the board looked like before
4601                this move.  Punt. */
4602           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4603             strcat(parseList[moveNum - 1], " ");
4604             strcat(parseList[moveNum - 1], elapsed_time);
4605             moveList[moveNum - 1][0] = NULLCHAR;
4606         } else if (strcmp(move_str, "none") == 0) {
4607             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4608             /* Again, we don't know what the board looked like;
4609                this is really the start of the game. */
4610             parseList[moveNum - 1][0] = NULLCHAR;
4611             moveList[moveNum - 1][0] = NULLCHAR;
4612             backwardMostMove = moveNum;
4613             startedFromSetupPosition = TRUE;
4614             fromX = fromY = toX = toY = -1;
4615         } else {
4616           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4617           //                 So we parse the long-algebraic move string in stead of the SAN move
4618           int valid; char buf[MSG_SIZ], *prom;
4619
4620           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4621                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4622           // str looks something like "Q/a1-a2"; kill the slash
4623           if(str[1] == '/')
4624             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4625           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4626           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4627                 strcat(buf, prom); // long move lacks promo specification!
4628           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4629                 if(appData.debugMode)
4630                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4631                 safeStrCpy(move_str, buf, MSG_SIZ);
4632           }
4633           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4634                                 &fromX, &fromY, &toX, &toY, &promoChar)
4635                || ParseOneMove(buf, moveNum - 1, &moveType,
4636                                 &fromX, &fromY, &toX, &toY, &promoChar);
4637           // end of long SAN patch
4638           if (valid) {
4639             (void) CoordsToAlgebraic(boards[moveNum - 1],
4640                                      PosFlags(moveNum - 1),
4641                                      fromY, fromX, toY, toX, promoChar,
4642                                      parseList[moveNum-1]);
4643             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4644               case MT_NONE:
4645               case MT_STALEMATE:
4646               default:
4647                 break;
4648               case MT_CHECK:
4649                 if(gameInfo.variant != VariantShogi)
4650                     strcat(parseList[moveNum - 1], "+");
4651                 break;
4652               case MT_CHECKMATE:
4653               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4654                 strcat(parseList[moveNum - 1], "#");
4655                 break;
4656             }
4657             strcat(parseList[moveNum - 1], " ");
4658             strcat(parseList[moveNum - 1], elapsed_time);
4659             /* currentMoveString is set as a side-effect of ParseOneMove */
4660             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4661             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4662             strcat(moveList[moveNum - 1], "\n");
4663
4664             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4665                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4666               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4667                 ChessSquare old, new = boards[moveNum][k][j];
4668                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4669                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4670                   if(old == new) continue;
4671                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4672                   else if(new == WhiteWazir || new == BlackWazir) {
4673                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4674                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4675                       else boards[moveNum][k][j] = old; // preserve type of Gold
4676                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4677                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4678               }
4679           } else {
4680             /* Move from ICS was illegal!?  Punt. */
4681             if (appData.debugMode) {
4682               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4683               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4684             }
4685             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4686             strcat(parseList[moveNum - 1], " ");
4687             strcat(parseList[moveNum - 1], elapsed_time);
4688             moveList[moveNum - 1][0] = NULLCHAR;
4689             fromX = fromY = toX = toY = -1;
4690           }
4691         }
4692   if (appData.debugMode) {
4693     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4694     setbuf(debugFP, NULL);
4695   }
4696
4697 #if ZIPPY
4698         /* Send move to chess program (BEFORE animating it). */
4699         if (appData.zippyPlay && !newGame && newMove &&
4700            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4701
4702             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4703                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4704                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4705                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4706                             move_str);
4707                     DisplayError(str, 0);
4708                 } else {
4709                     if (first.sendTime) {
4710                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4711                     }
4712                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4713                     if (firstMove && !bookHit) {
4714                         firstMove = FALSE;
4715                         if (first.useColors) {
4716                           SendToProgram(gameMode == IcsPlayingWhite ?
4717                                         "white\ngo\n" :
4718                                         "black\ngo\n", &first);
4719                         } else {
4720                           SendToProgram("go\n", &first);
4721                         }
4722                         first.maybeThinking = TRUE;
4723                     }
4724                 }
4725             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4726               if (moveList[moveNum - 1][0] == NULLCHAR) {
4727                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4728                 DisplayError(str, 0);
4729               } else {
4730                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4731                 SendMoveToProgram(moveNum - 1, &first);
4732               }
4733             }
4734         }
4735 #endif
4736     }
4737
4738     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4739         /* If move comes from a remote source, animate it.  If it
4740            isn't remote, it will have already been animated. */
4741         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4742             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4743         }
4744         if (!pausing && appData.highlightLastMove) {
4745             SetHighlights(fromX, fromY, toX, toY);
4746         }
4747     }
4748
4749     /* Start the clocks */
4750     whiteFlag = blackFlag = FALSE;
4751     appData.clockMode = !(basetime == 0 && increment == 0);
4752     if (ticking == 0) {
4753       ics_clock_paused = TRUE;
4754       StopClocks();
4755     } else if (ticking == 1) {
4756       ics_clock_paused = FALSE;
4757     }
4758     if (gameMode == IcsIdle ||
4759         relation == RELATION_OBSERVING_STATIC ||
4760         relation == RELATION_EXAMINING ||
4761         ics_clock_paused)
4762       DisplayBothClocks();
4763     else
4764       StartClocks();
4765
4766     /* Display opponents and material strengths */
4767     if (gameInfo.variant != VariantBughouse &&
4768         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4769         if (tinyLayout || smallLayout) {
4770             if(gameInfo.variant == VariantNormal)
4771               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4772                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4773                     basetime, increment);
4774             else
4775               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4776                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4777                     basetime, increment, (int) gameInfo.variant);
4778         } else {
4779             if(gameInfo.variant == VariantNormal)
4780               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4781                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4782                     basetime, increment);
4783             else
4784               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4785                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4786                     basetime, increment, VariantName(gameInfo.variant));
4787         }
4788         DisplayTitle(str);
4789   if (appData.debugMode) {
4790     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4791   }
4792     }
4793
4794
4795     /* Display the board */
4796     if (!pausing && !appData.noGUI) {
4797
4798       if (appData.premove)
4799           if (!gotPremove ||
4800              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4801              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4802               ClearPremoveHighlights();
4803
4804       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4805         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4806       DrawPosition(j, boards[currentMove]);
4807
4808       DisplayMove(moveNum - 1);
4809       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4810             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4811               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4812         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4813       }
4814     }
4815
4816     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4817 #if ZIPPY
4818     if(bookHit) { // [HGM] book: simulate book reply
4819         static char bookMove[MSG_SIZ]; // a bit generous?
4820
4821         programStats.nodes = programStats.depth = programStats.time =
4822         programStats.score = programStats.got_only_move = 0;
4823         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4824
4825         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4826         strcat(bookMove, bookHit);
4827         HandleMachineMove(bookMove, &first);
4828     }
4829 #endif
4830 }
4831
4832 void
4833 GetMoveListEvent()
4834 {
4835     char buf[MSG_SIZ];
4836     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4837         ics_getting_history = H_REQUESTED;
4838         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4839         SendToICS(buf);
4840     }
4841 }
4842
4843 void
4844 AnalysisPeriodicEvent(force)
4845      int force;
4846 {
4847     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4848          && !force) || !appData.periodicUpdates)
4849       return;
4850
4851     /* Send . command to Crafty to collect stats */
4852     SendToProgram(".\n", &first);
4853
4854     /* Don't send another until we get a response (this makes
4855        us stop sending to old Crafty's which don't understand
4856        the "." command (sending illegal cmds resets node count & time,
4857        which looks bad)) */
4858     programStats.ok_to_send = 0;
4859 }
4860
4861 void ics_update_width(new_width)
4862         int new_width;
4863 {
4864         ics_printf("set width %d\n", new_width);
4865 }
4866
4867 void
4868 SendMoveToProgram(moveNum, cps)
4869      int moveNum;
4870      ChessProgramState *cps;
4871 {
4872     char buf[MSG_SIZ];
4873
4874     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4875         // null move in variant where engine does not understand it (for analysis purposes)
4876         SendBoard(cps, moveNum + 1); // send position after move in stead.
4877         return;
4878     }
4879     if (cps->useUsermove) {
4880       SendToProgram("usermove ", cps);
4881     }
4882     if (cps->useSAN) {
4883       char *space;
4884       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4885         int len = space - parseList[moveNum];
4886         memcpy(buf, parseList[moveNum], len);
4887         buf[len++] = '\n';
4888         buf[len] = NULLCHAR;
4889       } else {
4890         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4891       }
4892       SendToProgram(buf, cps);
4893     } else {
4894       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4895         AlphaRank(moveList[moveNum], 4);
4896         SendToProgram(moveList[moveNum], cps);
4897         AlphaRank(moveList[moveNum], 4); // and back
4898       } else
4899       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4900        * the engine. It would be nice to have a better way to identify castle
4901        * moves here. */
4902       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4903                                                                          && cps->useOOCastle) {
4904         int fromX = moveList[moveNum][0] - AAA;
4905         int fromY = moveList[moveNum][1] - ONE;
4906         int toX = moveList[moveNum][2] - AAA;
4907         int toY = moveList[moveNum][3] - ONE;
4908         if((boards[moveNum][fromY][fromX] == WhiteKing
4909             && boards[moveNum][toY][toX] == WhiteRook)
4910            || (boards[moveNum][fromY][fromX] == BlackKing
4911                && boards[moveNum][toY][toX] == BlackRook)) {
4912           if(toX > fromX) SendToProgram("O-O\n", cps);
4913           else SendToProgram("O-O-O\n", cps);
4914         }
4915         else SendToProgram(moveList[moveNum], cps);
4916       } else
4917       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4918         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4919           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4920           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4921                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4922         } else
4923           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4924                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4925         SendToProgram(buf, cps);
4926       }
4927       else SendToProgram(moveList[moveNum], cps);
4928       /* End of additions by Tord */
4929     }
4930
4931     /* [HGM] setting up the opening has brought engine in force mode! */
4932     /*       Send 'go' if we are in a mode where machine should play. */
4933     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4934         (gameMode == TwoMachinesPlay   ||
4935 #if ZIPPY
4936          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4937 #endif
4938          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4939         SendToProgram("go\n", cps);
4940   if (appData.debugMode) {
4941     fprintf(debugFP, "(extra)\n");
4942   }
4943     }
4944     setboardSpoiledMachineBlack = 0;
4945 }
4946
4947 void
4948 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4949      ChessMove moveType;
4950      int fromX, fromY, toX, toY;
4951      char promoChar;
4952 {
4953     char user_move[MSG_SIZ];
4954
4955     switch (moveType) {
4956       default:
4957         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4958                 (int)moveType, fromX, fromY, toX, toY);
4959         DisplayError(user_move + strlen("say "), 0);
4960         break;
4961       case WhiteKingSideCastle:
4962       case BlackKingSideCastle:
4963       case WhiteQueenSideCastleWild:
4964       case BlackQueenSideCastleWild:
4965       /* PUSH Fabien */
4966       case WhiteHSideCastleFR:
4967       case BlackHSideCastleFR:
4968       /* POP Fabien */
4969         snprintf(user_move, MSG_SIZ, "o-o\n");
4970         break;
4971       case WhiteQueenSideCastle:
4972       case BlackQueenSideCastle:
4973       case WhiteKingSideCastleWild:
4974       case BlackKingSideCastleWild:
4975       /* PUSH Fabien */
4976       case WhiteASideCastleFR:
4977       case BlackASideCastleFR:
4978       /* POP Fabien */
4979         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4980         break;
4981       case WhiteNonPromotion:
4982       case BlackNonPromotion:
4983         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4984         break;
4985       case WhitePromotion:
4986       case BlackPromotion:
4987         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4988           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4989                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4990                 PieceToChar(WhiteFerz));
4991         else if(gameInfo.variant == VariantGreat)
4992           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4993                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4994                 PieceToChar(WhiteMan));
4995         else
4996           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4997                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4998                 promoChar);
4999         break;
5000       case WhiteDrop:
5001       case BlackDrop:
5002       drop:
5003         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5004                  ToUpper(PieceToChar((ChessSquare) fromX)),
5005                  AAA + toX, ONE + toY);
5006         break;
5007       case IllegalMove:  /* could be a variant we don't quite understand */
5008         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5009       case NormalMove:
5010       case WhiteCapturesEnPassant:
5011       case BlackCapturesEnPassant:
5012         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5013                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5014         break;
5015     }
5016     SendToICS(user_move);
5017     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5018         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5019 }
5020
5021 void
5022 UploadGameEvent()
5023 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5024     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5025     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5026     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5027         DisplayError("You cannot do this while you are playing or observing", 0);
5028         return;
5029     }
5030     if(gameMode != IcsExamining) { // is this ever not the case?
5031         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5032
5033         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5034           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5035         } else { // on FICS we must first go to general examine mode
5036           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5037         }
5038         if(gameInfo.variant != VariantNormal) {
5039             // try figure out wild number, as xboard names are not always valid on ICS
5040             for(i=1; i<=36; i++) {
5041               snprintf(buf, MSG_SIZ, "wild/%d", i);
5042                 if(StringToVariant(buf) == gameInfo.variant) break;
5043             }
5044             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5045             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5046             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5047         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5048         SendToICS(ics_prefix);
5049         SendToICS(buf);
5050         if(startedFromSetupPosition || backwardMostMove != 0) {
5051           fen = PositionToFEN(backwardMostMove, NULL);
5052           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5053             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5054             SendToICS(buf);
5055           } else { // FICS: everything has to set by separate bsetup commands
5056             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5057             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5058             SendToICS(buf);
5059             if(!WhiteOnMove(backwardMostMove)) {
5060                 SendToICS("bsetup tomove black\n");
5061             }
5062             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5063             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5064             SendToICS(buf);
5065             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5066             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5067             SendToICS(buf);
5068             i = boards[backwardMostMove][EP_STATUS];
5069             if(i >= 0) { // set e.p.
5070               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5071                 SendToICS(buf);
5072             }
5073             bsetup++;
5074           }
5075         }
5076       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5077             SendToICS("bsetup done\n"); // switch to normal examining.
5078     }
5079     for(i = backwardMostMove; i<last; i++) {
5080         char buf[20];
5081         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5082         SendToICS(buf);
5083     }
5084     SendToICS(ics_prefix);
5085     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5086 }
5087
5088 void
5089 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5090      int rf, ff, rt, ft;
5091      char promoChar;
5092      char move[7];
5093 {
5094     if (rf == DROP_RANK) {
5095       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5096       sprintf(move, "%c@%c%c\n",
5097                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5098     } else {
5099         if (promoChar == 'x' || promoChar == NULLCHAR) {
5100           sprintf(move, "%c%c%c%c\n",
5101                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5102         } else {
5103             sprintf(move, "%c%c%c%c%c\n",
5104                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5105         }
5106     }
5107 }
5108
5109 void
5110 ProcessICSInitScript(f)
5111      FILE *f;
5112 {
5113     char buf[MSG_SIZ];
5114
5115     while (fgets(buf, MSG_SIZ, f)) {
5116         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5117     }
5118
5119     fclose(f);
5120 }
5121
5122
5123 static int lastX, lastY, selectFlag, dragging;
5124
5125 void
5126 Sweep(int step)
5127 {
5128     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5129     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5130     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5131     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5132     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5133     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5134     do {
5135         promoSweep -= step;
5136         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5137         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5138         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5139         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5140         if(!step) step = -1;
5141     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5142             appData.testLegality && (promoSweep == king ||
5143             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5144     ChangeDragPiece(promoSweep);
5145 }
5146
5147 int PromoScroll(int x, int y)
5148 {
5149   int step = 0;
5150
5151   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5152   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5153   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5154   if(!step) return FALSE;
5155   lastX = x; lastY = y;
5156   if((promoSweep < BlackPawn) == flipView) step = -step;
5157   if(step > 0) selectFlag = 1;
5158   if(!selectFlag) Sweep(step);
5159   return FALSE;
5160 }
5161
5162 void
5163 NextPiece(int step)
5164 {
5165     ChessSquare piece = boards[currentMove][toY][toX];
5166     do {
5167         pieceSweep -= step;
5168         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5169         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5170         if(!step) step = -1;
5171     } while(PieceToChar(pieceSweep) == '.');
5172     boards[currentMove][toY][toX] = pieceSweep;
5173     DrawPosition(FALSE, boards[currentMove]);
5174     boards[currentMove][toY][toX] = piece;
5175 }
5176 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5177 void
5178 AlphaRank(char *move, int n)
5179 {
5180 //    char *p = move, c; int x, y;
5181
5182     if (appData.debugMode) {
5183         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5184     }
5185
5186     if(move[1]=='*' &&
5187        move[2]>='0' && move[2]<='9' &&
5188        move[3]>='a' && move[3]<='x'    ) {
5189         move[1] = '@';
5190         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5191         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5192     } else
5193     if(move[0]>='0' && move[0]<='9' &&
5194        move[1]>='a' && move[1]<='x' &&
5195        move[2]>='0' && move[2]<='9' &&
5196        move[3]>='a' && move[3]<='x'    ) {
5197         /* input move, Shogi -> normal */
5198         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5199         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5200         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5201         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5202     } else
5203     if(move[1]=='@' &&
5204        move[3]>='0' && move[3]<='9' &&
5205        move[2]>='a' && move[2]<='x'    ) {
5206         move[1] = '*';
5207         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5208         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5209     } else
5210     if(
5211        move[0]>='a' && move[0]<='x' &&
5212        move[3]>='0' && move[3]<='9' &&
5213        move[2]>='a' && move[2]<='x'    ) {
5214          /* output move, normal -> Shogi */
5215         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5216         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5217         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5218         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5219         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5220     }
5221     if (appData.debugMode) {
5222         fprintf(debugFP, "   out = '%s'\n", move);
5223     }
5224 }
5225
5226 char yy_textstr[8000];
5227
5228 /* Parser for moves from gnuchess, ICS, or user typein box */
5229 Boolean
5230 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5231      char *move;
5232      int moveNum;
5233      ChessMove *moveType;
5234      int *fromX, *fromY, *toX, *toY;
5235      char *promoChar;
5236 {
5237     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5238
5239     switch (*moveType) {
5240       case WhitePromotion:
5241       case BlackPromotion:
5242       case WhiteNonPromotion:
5243       case BlackNonPromotion:
5244       case NormalMove:
5245       case WhiteCapturesEnPassant:
5246       case BlackCapturesEnPassant:
5247       case WhiteKingSideCastle:
5248       case WhiteQueenSideCastle:
5249       case BlackKingSideCastle:
5250       case BlackQueenSideCastle:
5251       case WhiteKingSideCastleWild:
5252       case WhiteQueenSideCastleWild:
5253       case BlackKingSideCastleWild:
5254       case BlackQueenSideCastleWild:
5255       /* Code added by Tord: */
5256       case WhiteHSideCastleFR:
5257       case WhiteASideCastleFR:
5258       case BlackHSideCastleFR:
5259       case BlackASideCastleFR:
5260       /* End of code added by Tord */
5261       case IllegalMove:         /* bug or odd chess variant */
5262         *fromX = currentMoveString[0] - AAA;
5263         *fromY = currentMoveString[1] - ONE;
5264         *toX = currentMoveString[2] - AAA;
5265         *toY = currentMoveString[3] - ONE;
5266         *promoChar = currentMoveString[4];
5267         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5268             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5269     if (appData.debugMode) {
5270         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5271     }
5272             *fromX = *fromY = *toX = *toY = 0;
5273             return FALSE;
5274         }
5275         if (appData.testLegality) {
5276           return (*moveType != IllegalMove);
5277         } else {
5278           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5279                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5280         }
5281
5282       case WhiteDrop:
5283       case BlackDrop:
5284         *fromX = *moveType == WhiteDrop ?
5285           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5286           (int) CharToPiece(ToLower(currentMoveString[0]));
5287         *fromY = DROP_RANK;
5288         *toX = currentMoveString[2] - AAA;
5289         *toY = currentMoveString[3] - ONE;
5290         *promoChar = NULLCHAR;
5291         return TRUE;
5292
5293       case AmbiguousMove:
5294       case ImpossibleMove:
5295       case EndOfFile:
5296       case ElapsedTime:
5297       case Comment:
5298       case PGNTag:
5299       case NAG:
5300       case WhiteWins:
5301       case BlackWins:
5302       case GameIsDrawn:
5303       default:
5304     if (appData.debugMode) {
5305         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5306     }
5307         /* bug? */
5308         *fromX = *fromY = *toX = *toY = 0;
5309         *promoChar = NULLCHAR;
5310         return FALSE;
5311     }
5312 }
5313
5314 Boolean pushed = FALSE;
5315 char *lastParseAttempt;
5316
5317 void
5318 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5319 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5320   int fromX, fromY, toX, toY; char promoChar;
5321   ChessMove moveType;
5322   Boolean valid;
5323   int nr = 0;
5324
5325   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5326     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5327     pushed = TRUE;
5328   }
5329   endPV = forwardMostMove;
5330   do {
5331     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5332     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5333     lastParseAttempt = pv;
5334     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5335 if(appData.debugMode){
5336 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);
5337 }
5338     if(!valid && nr == 0 &&
5339        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5340         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5341         // Hande case where played move is different from leading PV move
5342         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5343         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5344         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5345         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5346           endPV += 2; // if position different, keep this
5347           moveList[endPV-1][0] = fromX + AAA;
5348           moveList[endPV-1][1] = fromY + ONE;
5349           moveList[endPV-1][2] = toX + AAA;
5350           moveList[endPV-1][3] = toY + ONE;
5351           parseList[endPV-1][0] = NULLCHAR;
5352           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5353         }
5354       }
5355     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5356     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5357     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5358     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5359         valid++; // allow comments in PV
5360         continue;
5361     }
5362     nr++;
5363     if(endPV+1 > framePtr) break; // no space, truncate
5364     if(!valid) break;
5365     endPV++;
5366     CopyBoard(boards[endPV], boards[endPV-1]);
5367     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5368     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5369     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5370     CoordsToAlgebraic(boards[endPV - 1],
5371                              PosFlags(endPV - 1),
5372                              fromY, fromX, toY, toX, promoChar,
5373                              parseList[endPV - 1]);
5374   } while(valid);
5375   if(atEnd == 2) return; // used hidden, for PV conversion
5376   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5377   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5378   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5379                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5380   DrawPosition(TRUE, boards[currentMove]);
5381 }
5382
5383 int
5384 MultiPV(ChessProgramState *cps)
5385 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5386         int i;
5387         for(i=0; i<cps->nrOptions; i++)
5388             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5389                 return i;
5390         return -1;
5391 }
5392
5393 Boolean
5394 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5395 {
5396         int startPV, multi, lineStart, origIndex = index;
5397         char *p, buf2[MSG_SIZ];
5398
5399         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5400         lastX = x; lastY = y;
5401         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5402         lineStart = startPV = index;
5403         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5404         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5405         index = startPV;
5406         do{ while(buf[index] && buf[index] != '\n') index++;
5407         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5408         buf[index] = 0;
5409         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5410                 int n = first.option[multi].value;
5411                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5412                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5413                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5414                 first.option[multi].value = n;
5415                 *start = *end = 0;
5416                 return FALSE;
5417         }
5418         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5419         *start = startPV; *end = index-1;
5420         return TRUE;
5421 }
5422
5423 char *
5424 PvToSAN(char *pv)
5425 {
5426         static char buf[10*MSG_SIZ];
5427         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5428         *buf = NULLCHAR;
5429         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5430         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5431         for(i = forwardMostMove; i<endPV; i++){
5432             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5433             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5434             k += strlen(buf+k);
5435         }
5436         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5437         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5438         endPV = savedEnd;
5439         return buf;
5440 }
5441
5442 Boolean
5443 LoadPV(int x, int y)
5444 { // called on right mouse click to load PV
5445   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5446   lastX = x; lastY = y;
5447   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5448   return TRUE;
5449 }
5450
5451 void
5452 UnLoadPV()
5453 {
5454   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5455   if(endPV < 0) return;
5456   endPV = -1;
5457   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5458         Boolean saveAnimate = appData.animate;
5459         if(pushed) {
5460             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5461                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5462             } else storedGames--; // abandon shelved tail of original game
5463         }
5464         pushed = FALSE;
5465         forwardMostMove = currentMove;
5466         currentMove = oldFMM;
5467         appData.animate = FALSE;
5468         ToNrEvent(forwardMostMove);
5469         appData.animate = saveAnimate;
5470   }
5471   currentMove = forwardMostMove;
5472   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5473   ClearPremoveHighlights();
5474   DrawPosition(TRUE, boards[currentMove]);
5475 }
5476
5477 void
5478 MovePV(int x, int y, int h)
5479 { // step through PV based on mouse coordinates (called on mouse move)
5480   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5481
5482   // we must somehow check if right button is still down (might be released off board!)
5483   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5484   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5485   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5486   if(!step) return;
5487   lastX = x; lastY = y;
5488
5489   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5490   if(endPV < 0) return;
5491   if(y < margin) step = 1; else
5492   if(y > h - margin) step = -1;
5493   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5494   currentMove += step;
5495   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5496   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5497                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5498   DrawPosition(FALSE, boards[currentMove]);
5499 }
5500
5501
5502 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5503 // All positions will have equal probability, but the current method will not provide a unique
5504 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5505 #define DARK 1
5506 #define LITE 2
5507 #define ANY 3
5508
5509 int squaresLeft[4];
5510 int piecesLeft[(int)BlackPawn];
5511 int seed, nrOfShuffles;
5512
5513 void GetPositionNumber()
5514 {       // sets global variable seed
5515         int i;
5516
5517         seed = appData.defaultFrcPosition;
5518         if(seed < 0) { // randomize based on time for negative FRC position numbers
5519                 for(i=0; i<50; i++) seed += random();
5520                 seed = random() ^ random() >> 8 ^ random() << 8;
5521                 if(seed<0) seed = -seed;
5522         }
5523 }
5524
5525 int put(Board board, int pieceType, int rank, int n, int shade)
5526 // put the piece on the (n-1)-th empty squares of the given shade
5527 {
5528         int i;
5529
5530         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5531                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5532                         board[rank][i] = (ChessSquare) pieceType;
5533                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5534                         squaresLeft[ANY]--;
5535                         piecesLeft[pieceType]--;
5536                         return i;
5537                 }
5538         }
5539         return -1;
5540 }
5541
5542
5543 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5544 // calculate where the next piece goes, (any empty square), and put it there
5545 {
5546         int i;
5547
5548         i = seed % squaresLeft[shade];
5549         nrOfShuffles *= squaresLeft[shade];
5550         seed /= squaresLeft[shade];
5551         put(board, pieceType, rank, i, shade);
5552 }
5553
5554 void AddTwoPieces(Board board, int pieceType, int rank)
5555 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5556 {
5557         int i, n=squaresLeft[ANY], j=n-1, k;
5558
5559         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5560         i = seed % k;  // pick one
5561         nrOfShuffles *= k;
5562         seed /= k;
5563         while(i >= j) i -= j--;
5564         j = n - 1 - j; i += j;
5565         put(board, pieceType, rank, j, ANY);
5566         put(board, pieceType, rank, i, ANY);
5567 }
5568
5569 void SetUpShuffle(Board board, int number)
5570 {
5571         int i, p, first=1;
5572
5573         GetPositionNumber(); nrOfShuffles = 1;
5574
5575         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5576         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5577         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5578
5579         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5580
5581         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5582             p = (int) board[0][i];
5583             if(p < (int) BlackPawn) piecesLeft[p] ++;
5584             board[0][i] = EmptySquare;
5585         }
5586
5587         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5588             // shuffles restricted to allow normal castling put KRR first
5589             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5590                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5591             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5592                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5593             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5594                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5595             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5596                 put(board, WhiteRook, 0, 0, ANY);
5597             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5598         }
5599
5600         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5601             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5602             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5603                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5604                 while(piecesLeft[p] >= 2) {
5605                     AddOnePiece(board, p, 0, LITE);
5606                     AddOnePiece(board, p, 0, DARK);
5607                 }
5608                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5609             }
5610
5611         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5612             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5613             // but we leave King and Rooks for last, to possibly obey FRC restriction
5614             if(p == (int)WhiteRook) continue;
5615             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5616             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5617         }
5618
5619         // now everything is placed, except perhaps King (Unicorn) and Rooks
5620
5621         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5622             // Last King gets castling rights
5623             while(piecesLeft[(int)WhiteUnicorn]) {
5624                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5625                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5626             }
5627
5628             while(piecesLeft[(int)WhiteKing]) {
5629                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5630                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5631             }
5632
5633
5634         } else {
5635             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5636             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5637         }
5638
5639         // Only Rooks can be left; simply place them all
5640         while(piecesLeft[(int)WhiteRook]) {
5641                 i = put(board, WhiteRook, 0, 0, ANY);
5642                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5643                         if(first) {
5644                                 first=0;
5645                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5646                         }
5647                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5648                 }
5649         }
5650         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5651             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5652         }
5653
5654         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5655 }
5656
5657 int SetCharTable( char *table, const char * map )
5658 /* [HGM] moved here from winboard.c because of its general usefulness */
5659 /*       Basically a safe strcpy that uses the last character as King */
5660 {
5661     int result = FALSE; int NrPieces;
5662
5663     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5664                     && NrPieces >= 12 && !(NrPieces&1)) {
5665         int i; /* [HGM] Accept even length from 12 to 34 */
5666
5667         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5668         for( i=0; i<NrPieces/2-1; i++ ) {
5669             table[i] = map[i];
5670             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5671         }
5672         table[(int) WhiteKing]  = map[NrPieces/2-1];
5673         table[(int) BlackKing]  = map[NrPieces-1];
5674
5675         result = TRUE;
5676     }
5677
5678     return result;
5679 }
5680
5681 void Prelude(Board board)
5682 {       // [HGM] superchess: random selection of exo-pieces
5683         int i, j, k; ChessSquare p;
5684         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5685
5686         GetPositionNumber(); // use FRC position number
5687
5688         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5689             SetCharTable(pieceToChar, appData.pieceToCharTable);
5690             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5691                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5692         }
5693
5694         j = seed%4;                 seed /= 4;
5695         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5696         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5697         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5698         j = seed%3 + (seed%3 >= j); seed /= 3;
5699         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5700         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5701         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5702         j = seed%3;                 seed /= 3;
5703         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5704         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5705         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5706         j = seed%2 + (seed%2 >= j); seed /= 2;
5707         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5708         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5709         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5710         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5711         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5712         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5713         put(board, exoPieces[0],    0, 0, ANY);
5714         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5715 }
5716
5717 void
5718 InitPosition(redraw)
5719      int redraw;
5720 {
5721     ChessSquare (* pieces)[BOARD_FILES];
5722     int i, j, pawnRow, overrule,
5723     oldx = gameInfo.boardWidth,
5724     oldy = gameInfo.boardHeight,
5725     oldh = gameInfo.holdingsWidth;
5726     static int oldv;
5727
5728     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5729
5730     /* [AS] Initialize pv info list [HGM] and game status */
5731     {
5732         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5733             pvInfoList[i].depth = 0;
5734             boards[i][EP_STATUS] = EP_NONE;
5735             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5736         }
5737
5738         initialRulePlies = 0; /* 50-move counter start */
5739
5740         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5741         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5742     }
5743
5744
5745     /* [HGM] logic here is completely changed. In stead of full positions */
5746     /* the initialized data only consist of the two backranks. The switch */
5747     /* selects which one we will use, which is than copied to the Board   */
5748     /* initialPosition, which for the rest is initialized by Pawns and    */
5749     /* empty squares. This initial position is then copied to boards[0],  */
5750     /* possibly after shuffling, so that it remains available.            */
5751
5752     gameInfo.holdingsWidth = 0; /* default board sizes */
5753     gameInfo.boardWidth    = 8;
5754     gameInfo.boardHeight   = 8;
5755     gameInfo.holdingsSize  = 0;
5756     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5757     for(i=0; i<BOARD_FILES-2; i++)
5758       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5759     initialPosition[EP_STATUS] = EP_NONE;
5760     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5761     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5762          SetCharTable(pieceNickName, appData.pieceNickNames);
5763     else SetCharTable(pieceNickName, "............");
5764     pieces = FIDEArray;
5765
5766     switch (gameInfo.variant) {
5767     case VariantFischeRandom:
5768       shuffleOpenings = TRUE;
5769     default:
5770       break;
5771     case VariantShatranj:
5772       pieces = ShatranjArray;
5773       nrCastlingRights = 0;
5774       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5775       break;
5776     case VariantMakruk:
5777       pieces = makrukArray;
5778       nrCastlingRights = 0;
5779       startedFromSetupPosition = TRUE;
5780       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5781       break;
5782     case VariantTwoKings:
5783       pieces = twoKingsArray;
5784       break;
5785     case VariantGrand:
5786       pieces = GrandArray;
5787       nrCastlingRights = 0;
5788       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5789       gameInfo.boardWidth = 10;
5790       gameInfo.boardHeight = 10;
5791       gameInfo.holdingsSize = 7;
5792       break;
5793     case VariantCapaRandom:
5794       shuffleOpenings = TRUE;
5795     case VariantCapablanca:
5796       pieces = CapablancaArray;
5797       gameInfo.boardWidth = 10;
5798       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5799       break;
5800     case VariantGothic:
5801       pieces = GothicArray;
5802       gameInfo.boardWidth = 10;
5803       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5804       break;
5805     case VariantSChess:
5806       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5807       gameInfo.holdingsSize = 7;
5808       break;
5809     case VariantJanus:
5810       pieces = JanusArray;
5811       gameInfo.boardWidth = 10;
5812       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5813       nrCastlingRights = 6;
5814         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5815         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5816         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5817         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5818         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5819         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5820       break;
5821     case VariantFalcon:
5822       pieces = FalconArray;
5823       gameInfo.boardWidth = 10;
5824       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5825       break;
5826     case VariantXiangqi:
5827       pieces = XiangqiArray;
5828       gameInfo.boardWidth  = 9;
5829       gameInfo.boardHeight = 10;
5830       nrCastlingRights = 0;
5831       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5832       break;
5833     case VariantShogi:
5834       pieces = ShogiArray;
5835       gameInfo.boardWidth  = 9;
5836       gameInfo.boardHeight = 9;
5837       gameInfo.holdingsSize = 7;
5838       nrCastlingRights = 0;
5839       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5840       break;
5841     case VariantCourier:
5842       pieces = CourierArray;
5843       gameInfo.boardWidth  = 12;
5844       nrCastlingRights = 0;
5845       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5846       break;
5847     case VariantKnightmate:
5848       pieces = KnightmateArray;
5849       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5850       break;
5851     case VariantSpartan:
5852       pieces = SpartanArray;
5853       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5854       break;
5855     case VariantFairy:
5856       pieces = fairyArray;
5857       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5858       break;
5859     case VariantGreat:
5860       pieces = GreatArray;
5861       gameInfo.boardWidth = 10;
5862       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5863       gameInfo.holdingsSize = 8;
5864       break;
5865     case VariantSuper:
5866       pieces = FIDEArray;
5867       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5868       gameInfo.holdingsSize = 8;
5869       startedFromSetupPosition = TRUE;
5870       break;
5871     case VariantCrazyhouse:
5872     case VariantBughouse:
5873       pieces = FIDEArray;
5874       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5875       gameInfo.holdingsSize = 5;
5876       break;
5877     case VariantWildCastle:
5878       pieces = FIDEArray;
5879       /* !!?shuffle with kings guaranteed to be on d or e file */
5880       shuffleOpenings = 1;
5881       break;
5882     case VariantNoCastle:
5883       pieces = FIDEArray;
5884       nrCastlingRights = 0;
5885       /* !!?unconstrained back-rank shuffle */
5886       shuffleOpenings = 1;
5887       break;
5888     }
5889
5890     overrule = 0;
5891     if(appData.NrFiles >= 0) {
5892         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5893         gameInfo.boardWidth = appData.NrFiles;
5894     }
5895     if(appData.NrRanks >= 0) {
5896         gameInfo.boardHeight = appData.NrRanks;
5897     }
5898     if(appData.holdingsSize >= 0) {
5899         i = appData.holdingsSize;
5900         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5901         gameInfo.holdingsSize = i;
5902     }
5903     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5904     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5905         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5906
5907     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5908     if(pawnRow < 1) pawnRow = 1;
5909     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5910
5911     /* User pieceToChar list overrules defaults */
5912     if(appData.pieceToCharTable != NULL)
5913         SetCharTable(pieceToChar, appData.pieceToCharTable);
5914
5915     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5916
5917         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5918             s = (ChessSquare) 0; /* account holding counts in guard band */
5919         for( i=0; i<BOARD_HEIGHT; i++ )
5920             initialPosition[i][j] = s;
5921
5922         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5923         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5924         initialPosition[pawnRow][j] = WhitePawn;
5925         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5926         if(gameInfo.variant == VariantXiangqi) {
5927             if(j&1) {
5928                 initialPosition[pawnRow][j] =
5929                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5930                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5931                    initialPosition[2][j] = WhiteCannon;
5932                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5933                 }
5934             }
5935         }
5936         if(gameInfo.variant == VariantGrand) {
5937             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5938                initialPosition[0][j] = WhiteRook;
5939                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5940             }
5941         }
5942         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5943     }
5944     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5945
5946             j=BOARD_LEFT+1;
5947             initialPosition[1][j] = WhiteBishop;
5948             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5949             j=BOARD_RGHT-2;
5950             initialPosition[1][j] = WhiteRook;
5951             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5952     }
5953
5954     if( nrCastlingRights == -1) {
5955         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5956         /*       This sets default castling rights from none to normal corners   */
5957         /* Variants with other castling rights must set them themselves above    */
5958         nrCastlingRights = 6;
5959
5960         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5961         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5962         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5963         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5964         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5965         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5966      }
5967
5968      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5969      if(gameInfo.variant == VariantGreat) { // promotion commoners
5970         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5971         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5972         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5973         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5974      }
5975      if( gameInfo.variant == VariantSChess ) {
5976       initialPosition[1][0] = BlackMarshall;
5977       initialPosition[2][0] = BlackAngel;
5978       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5979       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5980       initialPosition[1][1] = initialPosition[2][1] = 
5981       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5982      }
5983   if (appData.debugMode) {
5984     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5985   }
5986     if(shuffleOpenings) {
5987         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5988         startedFromSetupPosition = TRUE;
5989     }
5990     if(startedFromPositionFile) {
5991       /* [HGM] loadPos: use PositionFile for every new game */
5992       CopyBoard(initialPosition, filePosition);
5993       for(i=0; i<nrCastlingRights; i++)
5994           initialRights[i] = filePosition[CASTLING][i];
5995       startedFromSetupPosition = TRUE;
5996     }
5997
5998     CopyBoard(boards[0], initialPosition);
5999
6000     if(oldx != gameInfo.boardWidth ||
6001        oldy != gameInfo.boardHeight ||
6002        oldv != gameInfo.variant ||
6003        oldh != gameInfo.holdingsWidth
6004                                          )
6005             InitDrawingSizes(-2 ,0);
6006
6007     oldv = gameInfo.variant;
6008     if (redraw)
6009       DrawPosition(TRUE, boards[currentMove]);
6010 }
6011
6012 void
6013 SendBoard(cps, moveNum)
6014      ChessProgramState *cps;
6015      int moveNum;
6016 {
6017     char message[MSG_SIZ];
6018
6019     if (cps->useSetboard) {
6020       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6021       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6022       SendToProgram(message, cps);
6023       free(fen);
6024
6025     } else {
6026       ChessSquare *bp;
6027       int i, j;
6028       /* Kludge to set black to move, avoiding the troublesome and now
6029        * deprecated "black" command.
6030        */
6031       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6032         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6033
6034       SendToProgram("edit\n", cps);
6035       SendToProgram("#\n", cps);
6036       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6037         bp = &boards[moveNum][i][BOARD_LEFT];
6038         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6039           if ((int) *bp < (int) BlackPawn) {
6040             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6041                     AAA + j, ONE + i);
6042             if(message[0] == '+' || message[0] == '~') {
6043               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6044                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6045                         AAA + j, ONE + i);
6046             }
6047             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6048                 message[1] = BOARD_RGHT   - 1 - j + '1';
6049                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6050             }
6051             SendToProgram(message, cps);
6052           }
6053         }
6054       }
6055
6056       SendToProgram("c\n", cps);
6057       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6058         bp = &boards[moveNum][i][BOARD_LEFT];
6059         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6060           if (((int) *bp != (int) EmptySquare)
6061               && ((int) *bp >= (int) BlackPawn)) {
6062             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6063                     AAA + j, ONE + i);
6064             if(message[0] == '+' || message[0] == '~') {
6065               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6066                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6067                         AAA + j, ONE + i);
6068             }
6069             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6070                 message[1] = BOARD_RGHT   - 1 - j + '1';
6071                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6072             }
6073             SendToProgram(message, cps);
6074           }
6075         }
6076       }
6077
6078       SendToProgram(".\n", cps);
6079     }
6080     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6081 }
6082
6083 ChessSquare
6084 DefaultPromoChoice(int white)
6085 {
6086     ChessSquare result;
6087     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6088         result = WhiteFerz; // no choice
6089     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6090         result= WhiteKing; // in Suicide Q is the last thing we want
6091     else if(gameInfo.variant == VariantSpartan)
6092         result = white ? WhiteQueen : WhiteAngel;
6093     else result = WhiteQueen;
6094     if(!white) result = WHITE_TO_BLACK result;
6095     return result;
6096 }
6097
6098 static int autoQueen; // [HGM] oneclick
6099
6100 int
6101 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6102 {
6103     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6104     /* [HGM] add Shogi promotions */
6105     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6106     ChessSquare piece;
6107     ChessMove moveType;
6108     Boolean premove;
6109
6110     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6111     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6112
6113     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6114       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6115         return FALSE;
6116
6117     piece = boards[currentMove][fromY][fromX];
6118     if(gameInfo.variant == VariantShogi) {
6119         promotionZoneSize = BOARD_HEIGHT/3;
6120         highestPromotingPiece = (int)WhiteFerz;
6121     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6122         promotionZoneSize = 3;
6123     }
6124
6125     // Treat Lance as Pawn when it is not representing Amazon
6126     if(gameInfo.variant != VariantSuper) {
6127         if(piece == WhiteLance) piece = WhitePawn; else
6128         if(piece == BlackLance) piece = BlackPawn;
6129     }
6130
6131     // next weed out all moves that do not touch the promotion zone at all
6132     if((int)piece >= BlackPawn) {
6133         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6134              return FALSE;
6135         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6136     } else {
6137         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6138            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6139     }
6140
6141     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6142
6143     // weed out mandatory Shogi promotions
6144     if(gameInfo.variant == VariantShogi) {
6145         if(piece >= BlackPawn) {
6146             if(toY == 0 && piece == BlackPawn ||
6147                toY == 0 && piece == BlackQueen ||
6148                toY <= 1 && piece == BlackKnight) {
6149                 *promoChoice = '+';
6150                 return FALSE;
6151             }
6152         } else {
6153             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6154                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6155                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6156                 *promoChoice = '+';
6157                 return FALSE;
6158             }
6159         }
6160     }
6161
6162     // weed out obviously illegal Pawn moves
6163     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6164         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6165         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6166         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6167         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6168         // note we are not allowed to test for valid (non-)capture, due to premove
6169     }
6170
6171     // we either have a choice what to promote to, or (in Shogi) whether to promote
6172     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6173         *promoChoice = PieceToChar(BlackFerz);  // no choice
6174         return FALSE;
6175     }
6176     // no sense asking what we must promote to if it is going to explode...
6177     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6178         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6179         return FALSE;
6180     }
6181     // give caller the default choice even if we will not make it
6182     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6183     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6184     if(        sweepSelect && gameInfo.variant != VariantGreat
6185                            && gameInfo.variant != VariantGrand
6186                            && gameInfo.variant != VariantSuper) return FALSE;
6187     if(autoQueen) return FALSE; // predetermined
6188
6189     // suppress promotion popup on illegal moves that are not premoves
6190     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6191               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6192     if(appData.testLegality && !premove) {
6193         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6194                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6195         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6196             return FALSE;
6197     }
6198
6199     return TRUE;
6200 }
6201
6202 int
6203 InPalace(row, column)
6204      int row, column;
6205 {   /* [HGM] for Xiangqi */
6206     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6207          column < (BOARD_WIDTH + 4)/2 &&
6208          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6209     return FALSE;
6210 }
6211
6212 int
6213 PieceForSquare (x, y)
6214      int x;
6215      int y;
6216 {
6217   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6218      return -1;
6219   else
6220      return boards[currentMove][y][x];
6221 }
6222
6223 int
6224 OKToStartUserMove(x, y)
6225      int x, y;
6226 {
6227     ChessSquare from_piece;
6228     int white_piece;
6229
6230     if (matchMode) return FALSE;
6231     if (gameMode == EditPosition) return TRUE;
6232
6233     if (x >= 0 && y >= 0)
6234       from_piece = boards[currentMove][y][x];
6235     else
6236       from_piece = EmptySquare;
6237
6238     if (from_piece == EmptySquare) return FALSE;
6239
6240     white_piece = (int)from_piece >= (int)WhitePawn &&
6241       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6242
6243     switch (gameMode) {
6244       case AnalyzeFile:
6245       case TwoMachinesPlay:
6246       case EndOfGame:
6247         return FALSE;
6248
6249       case IcsObserving:
6250       case IcsIdle:
6251         return FALSE;
6252
6253       case MachinePlaysWhite:
6254       case IcsPlayingBlack:
6255         if (appData.zippyPlay) return FALSE;
6256         if (white_piece) {
6257             DisplayMoveError(_("You are playing Black"));
6258             return FALSE;
6259         }
6260         break;
6261
6262       case MachinePlaysBlack:
6263       case IcsPlayingWhite:
6264         if (appData.zippyPlay) return FALSE;
6265         if (!white_piece) {
6266             DisplayMoveError(_("You are playing White"));
6267             return FALSE;
6268         }
6269         break;
6270
6271       case PlayFromGameFile:
6272             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6273       case EditGame:
6274         if (!white_piece && WhiteOnMove(currentMove)) {
6275             DisplayMoveError(_("It is White's turn"));
6276             return FALSE;
6277         }
6278         if (white_piece && !WhiteOnMove(currentMove)) {
6279             DisplayMoveError(_("It is Black's turn"));
6280             return FALSE;
6281         }
6282         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6283             /* Editing correspondence game history */
6284             /* Could disallow this or prompt for confirmation */
6285             cmailOldMove = -1;
6286         }
6287         break;
6288
6289       case BeginningOfGame:
6290         if (appData.icsActive) return FALSE;
6291         if (!appData.noChessProgram) {
6292             if (!white_piece) {
6293                 DisplayMoveError(_("You are playing White"));
6294                 return FALSE;
6295             }
6296         }
6297         break;
6298
6299       case Training:
6300         if (!white_piece && WhiteOnMove(currentMove)) {
6301             DisplayMoveError(_("It is White's turn"));
6302             return FALSE;
6303         }
6304         if (white_piece && !WhiteOnMove(currentMove)) {
6305             DisplayMoveError(_("It is Black's turn"));
6306             return FALSE;
6307         }
6308         break;
6309
6310       default:
6311       case IcsExamining:
6312         break;
6313     }
6314     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6315         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6316         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6317         && gameMode != AnalyzeFile && gameMode != Training) {
6318         DisplayMoveError(_("Displayed position is not current"));
6319         return FALSE;
6320     }
6321     return TRUE;
6322 }
6323
6324 Boolean
6325 OnlyMove(int *x, int *y, Boolean captures) {
6326     DisambiguateClosure cl;
6327     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6328     switch(gameMode) {
6329       case MachinePlaysBlack:
6330       case IcsPlayingWhite:
6331       case BeginningOfGame:
6332         if(!WhiteOnMove(currentMove)) return FALSE;
6333         break;
6334       case MachinePlaysWhite:
6335       case IcsPlayingBlack:
6336         if(WhiteOnMove(currentMove)) return FALSE;
6337         break;
6338       case EditGame:
6339         break;
6340       default:
6341         return FALSE;
6342     }
6343     cl.pieceIn = EmptySquare;
6344     cl.rfIn = *y;
6345     cl.ffIn = *x;
6346     cl.rtIn = -1;
6347     cl.ftIn = -1;
6348     cl.promoCharIn = NULLCHAR;
6349     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6350     if( cl.kind == NormalMove ||
6351         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6352         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6353         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6354       fromX = cl.ff;
6355       fromY = cl.rf;
6356       *x = cl.ft;
6357       *y = cl.rt;
6358       return TRUE;
6359     }
6360     if(cl.kind != ImpossibleMove) return FALSE;
6361     cl.pieceIn = EmptySquare;
6362     cl.rfIn = -1;
6363     cl.ffIn = -1;
6364     cl.rtIn = *y;
6365     cl.ftIn = *x;
6366     cl.promoCharIn = NULLCHAR;
6367     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6368     if( cl.kind == NormalMove ||
6369         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6370         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6371         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6372       fromX = cl.ff;
6373       fromY = cl.rf;
6374       *x = cl.ft;
6375       *y = cl.rt;
6376       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6377       return TRUE;
6378     }
6379     return FALSE;
6380 }
6381
6382 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6383 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6384 int lastLoadGameUseList = FALSE;
6385 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6386 ChessMove lastLoadGameStart = EndOfFile;
6387
6388 void
6389 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6390      int fromX, fromY, toX, toY;
6391      int promoChar;
6392 {
6393     ChessMove moveType;
6394     ChessSquare pdown, pup;
6395
6396     /* Check if the user is playing in turn.  This is complicated because we
6397        let the user "pick up" a piece before it is his turn.  So the piece he
6398        tried to pick up may have been captured by the time he puts it down!
6399        Therefore we use the color the user is supposed to be playing in this
6400        test, not the color of the piece that is currently on the starting
6401        square---except in EditGame mode, where the user is playing both
6402        sides; fortunately there the capture race can't happen.  (It can
6403        now happen in IcsExamining mode, but that's just too bad.  The user
6404        will get a somewhat confusing message in that case.)
6405        */
6406
6407     switch (gameMode) {
6408       case AnalyzeFile:
6409       case TwoMachinesPlay:
6410       case EndOfGame:
6411       case IcsObserving:
6412       case IcsIdle:
6413         /* We switched into a game mode where moves are not accepted,
6414            perhaps while the mouse button was down. */
6415         return;
6416
6417       case MachinePlaysWhite:
6418         /* User is moving for Black */
6419         if (WhiteOnMove(currentMove)) {
6420             DisplayMoveError(_("It is White's turn"));
6421             return;
6422         }
6423         break;
6424
6425       case MachinePlaysBlack:
6426         /* User is moving for White */
6427         if (!WhiteOnMove(currentMove)) {
6428             DisplayMoveError(_("It is Black's turn"));
6429             return;
6430         }
6431         break;
6432
6433       case PlayFromGameFile:
6434             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6435       case EditGame:
6436       case IcsExamining:
6437       case BeginningOfGame:
6438       case AnalyzeMode:
6439       case Training:
6440         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6441         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6442             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6443             /* User is moving for Black */
6444             if (WhiteOnMove(currentMove)) {
6445                 DisplayMoveError(_("It is White's turn"));
6446                 return;
6447             }
6448         } else {
6449             /* User is moving for White */
6450             if (!WhiteOnMove(currentMove)) {
6451                 DisplayMoveError(_("It is Black's turn"));
6452                 return;
6453             }
6454         }
6455         break;
6456
6457       case IcsPlayingBlack:
6458         /* User is moving for Black */
6459         if (WhiteOnMove(currentMove)) {
6460             if (!appData.premove) {
6461                 DisplayMoveError(_("It is White's turn"));
6462             } else if (toX >= 0 && toY >= 0) {
6463                 premoveToX = toX;
6464                 premoveToY = toY;
6465                 premoveFromX = fromX;
6466                 premoveFromY = fromY;
6467                 premovePromoChar = promoChar;
6468                 gotPremove = 1;
6469                 if (appData.debugMode)
6470                     fprintf(debugFP, "Got premove: fromX %d,"
6471                             "fromY %d, toX %d, toY %d\n",
6472                             fromX, fromY, toX, toY);
6473             }
6474             return;
6475         }
6476         break;
6477
6478       case IcsPlayingWhite:
6479         /* User is moving for White */
6480         if (!WhiteOnMove(currentMove)) {
6481             if (!appData.premove) {
6482                 DisplayMoveError(_("It is Black's turn"));
6483             } else if (toX >= 0 && toY >= 0) {
6484                 premoveToX = toX;
6485                 premoveToY = toY;
6486                 premoveFromX = fromX;
6487                 premoveFromY = fromY;
6488                 premovePromoChar = promoChar;
6489                 gotPremove = 1;
6490                 if (appData.debugMode)
6491                     fprintf(debugFP, "Got premove: fromX %d,"
6492                             "fromY %d, toX %d, toY %d\n",
6493                             fromX, fromY, toX, toY);
6494             }
6495             return;
6496         }
6497         break;
6498
6499       default:
6500         break;
6501
6502       case EditPosition:
6503         /* EditPosition, empty square, or different color piece;
6504            click-click move is possible */
6505         if (toX == -2 || toY == -2) {
6506             boards[0][fromY][fromX] = EmptySquare;
6507             DrawPosition(FALSE, boards[currentMove]);
6508             return;
6509         } else if (toX >= 0 && toY >= 0) {
6510             boards[0][toY][toX] = boards[0][fromY][fromX];
6511             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6512                 if(boards[0][fromY][0] != EmptySquare) {
6513                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6514                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6515                 }
6516             } else
6517             if(fromX == BOARD_RGHT+1) {
6518                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6519                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6520                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6521                 }
6522             } else
6523             boards[0][fromY][fromX] = EmptySquare;
6524             DrawPosition(FALSE, boards[currentMove]);
6525             return;
6526         }
6527         return;
6528     }
6529
6530     if(toX < 0 || toY < 0) return;
6531     pdown = boards[currentMove][fromY][fromX];
6532     pup = boards[currentMove][toY][toX];
6533
6534     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6535     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6536          if( pup != EmptySquare ) return;
6537          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6538            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6539                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6540            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6541            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6542            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6543            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6544          fromY = DROP_RANK;
6545     }
6546
6547     /* [HGM] always test for legality, to get promotion info */
6548     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6549                                          fromY, fromX, toY, toX, promoChar);
6550
6551     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6552
6553     /* [HGM] but possibly ignore an IllegalMove result */
6554     if (appData.testLegality) {
6555         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6556             DisplayMoveError(_("Illegal move"));
6557             return;
6558         }
6559     }
6560
6561     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6562 }
6563
6564 /* Common tail of UserMoveEvent and DropMenuEvent */
6565 int
6566 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6567      ChessMove moveType;
6568      int fromX, fromY, toX, toY;
6569      /*char*/int promoChar;
6570 {
6571     char *bookHit = 0;
6572
6573     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6574         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6575         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6576         if(WhiteOnMove(currentMove)) {
6577             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6578         } else {
6579             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6580         }
6581     }
6582
6583     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6584        move type in caller when we know the move is a legal promotion */
6585     if(moveType == NormalMove && promoChar)
6586         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6587
6588     /* [HGM] <popupFix> The following if has been moved here from
6589        UserMoveEvent(). Because it seemed to belong here (why not allow
6590        piece drops in training games?), and because it can only be
6591        performed after it is known to what we promote. */
6592     if (gameMode == Training) {
6593       /* compare the move played on the board to the next move in the
6594        * game. If they match, display the move and the opponent's response.
6595        * If they don't match, display an error message.
6596        */
6597       int saveAnimate;
6598       Board testBoard;
6599       CopyBoard(testBoard, boards[currentMove]);
6600       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6601
6602       if (CompareBoards(testBoard, boards[currentMove+1])) {
6603         ForwardInner(currentMove+1);
6604
6605         /* Autoplay the opponent's response.
6606          * if appData.animate was TRUE when Training mode was entered,
6607          * the response will be animated.
6608          */
6609         saveAnimate = appData.animate;
6610         appData.animate = animateTraining;
6611         ForwardInner(currentMove+1);
6612         appData.animate = saveAnimate;
6613
6614         /* check for the end of the game */
6615         if (currentMove >= forwardMostMove) {
6616           gameMode = PlayFromGameFile;
6617           ModeHighlight();
6618           SetTrainingModeOff();
6619           DisplayInformation(_("End of game"));
6620         }
6621       } else {
6622         DisplayError(_("Incorrect move"), 0);
6623       }
6624       return 1;
6625     }
6626
6627   /* Ok, now we know that the move is good, so we can kill
6628      the previous line in Analysis Mode */
6629   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6630                                 && currentMove < forwardMostMove) {
6631     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6632     else forwardMostMove = currentMove;
6633   }
6634
6635   /* If we need the chess program but it's dead, restart it */
6636   ResurrectChessProgram();
6637
6638   /* A user move restarts a paused game*/
6639   if (pausing)
6640     PauseEvent();
6641
6642   thinkOutput[0] = NULLCHAR;
6643
6644   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6645
6646   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6647     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6648     return 1;
6649   }
6650
6651   if (gameMode == BeginningOfGame) {
6652     if (appData.noChessProgram) {
6653       gameMode = EditGame;
6654       SetGameInfo();
6655     } else {
6656       char buf[MSG_SIZ];
6657       gameMode = MachinePlaysBlack;
6658       StartClocks();
6659       SetGameInfo();
6660       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6661       DisplayTitle(buf);
6662       if (first.sendName) {
6663         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6664         SendToProgram(buf, &first);
6665       }
6666       StartClocks();
6667     }
6668     ModeHighlight();
6669   }
6670
6671   /* Relay move to ICS or chess engine */
6672   if (appData.icsActive) {
6673     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6674         gameMode == IcsExamining) {
6675       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6676         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6677         SendToICS("draw ");
6678         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6679       }
6680       // also send plain move, in case ICS does not understand atomic claims
6681       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6682       ics_user_moved = 1;
6683     }
6684   } else {
6685     if (first.sendTime && (gameMode == BeginningOfGame ||
6686                            gameMode == MachinePlaysWhite ||
6687                            gameMode == MachinePlaysBlack)) {
6688       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6689     }
6690     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6691          // [HGM] book: if program might be playing, let it use book
6692         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6693         first.maybeThinking = TRUE;
6694     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6695         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6696         SendBoard(&first, currentMove+1);
6697     } else SendMoveToProgram(forwardMostMove-1, &first);
6698     if (currentMove == cmailOldMove + 1) {
6699       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6700     }
6701   }
6702
6703   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6704
6705   switch (gameMode) {
6706   case EditGame:
6707     if(appData.testLegality)
6708     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6709     case MT_NONE:
6710     case MT_CHECK:
6711       break;
6712     case MT_CHECKMATE:
6713     case MT_STAINMATE:
6714       if (WhiteOnMove(currentMove)) {
6715         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6716       } else {
6717         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6718       }
6719       break;
6720     case MT_STALEMATE:
6721       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6722       break;
6723     }
6724     break;
6725
6726   case MachinePlaysBlack:
6727   case MachinePlaysWhite:
6728     /* disable certain menu options while machine is thinking */
6729     SetMachineThinkingEnables();
6730     break;
6731
6732   default:
6733     break;
6734   }
6735
6736   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6737   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6738
6739   if(bookHit) { // [HGM] book: simulate book reply
6740         static char bookMove[MSG_SIZ]; // a bit generous?
6741
6742         programStats.nodes = programStats.depth = programStats.time =
6743         programStats.score = programStats.got_only_move = 0;
6744         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6745
6746         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6747         strcat(bookMove, bookHit);
6748         HandleMachineMove(bookMove, &first);
6749   }
6750   return 1;
6751 }
6752
6753 void
6754 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6755      Board board;
6756      int flags;
6757      ChessMove kind;
6758      int rf, ff, rt, ft;
6759      VOIDSTAR closure;
6760 {
6761     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6762     Markers *m = (Markers *) closure;
6763     if(rf == fromY && ff == fromX)
6764         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6765                          || kind == WhiteCapturesEnPassant
6766                          || kind == BlackCapturesEnPassant);
6767     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6768 }
6769
6770 void
6771 MarkTargetSquares(int clear)
6772 {
6773   int x, y;
6774   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6775      !appData.testLegality || gameMode == EditPosition) return;
6776   if(clear) {
6777     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6778   } else {
6779     int capt = 0;
6780     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6781     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6782       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6783       if(capt)
6784       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6785     }
6786   }
6787   DrawPosition(TRUE, NULL);
6788 }
6789
6790 int
6791 Explode(Board board, int fromX, int fromY, int toX, int toY)
6792 {
6793     if(gameInfo.variant == VariantAtomic &&
6794        (board[toY][toX] != EmptySquare ||                     // capture?
6795         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6796                          board[fromY][fromX] == BlackPawn   )
6797       )) {
6798         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6799         return TRUE;
6800     }
6801     return FALSE;
6802 }
6803
6804 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6805
6806 int CanPromote(ChessSquare piece, int y)
6807 {
6808         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6809         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6810         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6811            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6812            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6813                                                   gameInfo.variant == VariantMakruk) return FALSE;
6814         return (piece == BlackPawn && y == 1 ||
6815                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6816                 piece == BlackLance && y == 1 ||
6817                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6818 }
6819
6820 void LeftClick(ClickType clickType, int xPix, int yPix)
6821 {
6822     int x, y;
6823     Boolean saveAnimate;
6824     static int second = 0, promotionChoice = 0, clearFlag = 0;
6825     char promoChoice = NULLCHAR;
6826     ChessSquare piece;
6827
6828     if(appData.seekGraph && appData.icsActive && loggedOn &&
6829         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6830         SeekGraphClick(clickType, xPix, yPix, 0);
6831         return;
6832     }
6833
6834     if (clickType == Press) ErrorPopDown();
6835
6836     x = EventToSquare(xPix, BOARD_WIDTH);
6837     y = EventToSquare(yPix, BOARD_HEIGHT);
6838     if (!flipView && y >= 0) {
6839         y = BOARD_HEIGHT - 1 - y;
6840     }
6841     if (flipView && x >= 0) {
6842         x = BOARD_WIDTH - 1 - x;
6843     }
6844
6845     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6846         defaultPromoChoice = promoSweep;
6847         promoSweep = EmptySquare;   // terminate sweep
6848         promoDefaultAltered = TRUE;
6849         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6850     }
6851
6852     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6853         if(clickType == Release) return; // ignore upclick of click-click destination
6854         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6855         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6856         if(gameInfo.holdingsWidth &&
6857                 (WhiteOnMove(currentMove)
6858                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6859                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6860             // click in right holdings, for determining promotion piece
6861             ChessSquare p = boards[currentMove][y][x];
6862             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6863             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6864             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6865                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6866                 fromX = fromY = -1;
6867                 return;
6868             }
6869         }
6870         DrawPosition(FALSE, boards[currentMove]);
6871         return;
6872     }
6873
6874     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6875     if(clickType == Press
6876             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6877               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6878               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6879         return;
6880
6881     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6882         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6883
6884     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6885         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6886                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6887         defaultPromoChoice = DefaultPromoChoice(side);
6888     }
6889
6890     autoQueen = appData.alwaysPromoteToQueen;
6891
6892     if (fromX == -1) {
6893       int originalY = y;
6894       gatingPiece = EmptySquare;
6895       if (clickType != Press) {
6896         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6897             DragPieceEnd(xPix, yPix); dragging = 0;
6898             DrawPosition(FALSE, NULL);
6899         }
6900         return;
6901       }
6902       fromX = x; fromY = y; toX = toY = -1;
6903       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6904          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6905          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6906             /* First square */
6907             if (OKToStartUserMove(fromX, fromY)) {
6908                 second = 0;
6909                 MarkTargetSquares(0);
6910                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6911                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6912                     promoSweep = defaultPromoChoice;
6913                     selectFlag = 0; lastX = xPix; lastY = yPix;
6914                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6915                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6916                 }
6917                 if (appData.highlightDragging) {
6918                     SetHighlights(fromX, fromY, -1, -1);
6919                 }
6920             } else fromX = fromY = -1;
6921             return;
6922         }
6923     }
6924
6925     /* fromX != -1 */
6926     if (clickType == Press && gameMode != EditPosition) {
6927         ChessSquare fromP;
6928         ChessSquare toP;
6929         int frc;
6930
6931         // ignore off-board to clicks
6932         if(y < 0 || x < 0) return;
6933
6934         /* Check if clicking again on the same color piece */
6935         fromP = boards[currentMove][fromY][fromX];
6936         toP = boards[currentMove][y][x];
6937         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6938         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6939              WhitePawn <= toP && toP <= WhiteKing &&
6940              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6941              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6942             (BlackPawn <= fromP && fromP <= BlackKing &&
6943              BlackPawn <= toP && toP <= BlackKing &&
6944              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6945              !(fromP == BlackKing && toP == BlackRook && frc))) {
6946             /* Clicked again on same color piece -- changed his mind */
6947             second = (x == fromX && y == fromY);
6948             promoDefaultAltered = FALSE;
6949             MarkTargetSquares(1);
6950            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6951             if (appData.highlightDragging) {
6952                 SetHighlights(x, y, -1, -1);
6953             } else {
6954                 ClearHighlights();
6955             }
6956             if (OKToStartUserMove(x, y)) {
6957                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6958                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6959                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6960                  gatingPiece = boards[currentMove][fromY][fromX];
6961                 else gatingPiece = EmptySquare;
6962                 fromX = x;
6963                 fromY = y; dragging = 1;
6964                 MarkTargetSquares(0);
6965                 DragPieceBegin(xPix, yPix, FALSE);
6966                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6967                     promoSweep = defaultPromoChoice;
6968                     selectFlag = 0; lastX = xPix; lastY = yPix;
6969                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6970                 }
6971             }
6972            }
6973            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6974            second = FALSE; 
6975         }
6976         // ignore clicks on holdings
6977         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6978     }
6979
6980     if (clickType == Release && x == fromX && y == fromY) {
6981         DragPieceEnd(xPix, yPix); dragging = 0;
6982         if(clearFlag) {
6983             // a deferred attempt to click-click move an empty square on top of a piece
6984             boards[currentMove][y][x] = EmptySquare;
6985             ClearHighlights();
6986             DrawPosition(FALSE, boards[currentMove]);
6987             fromX = fromY = -1; clearFlag = 0;
6988             return;
6989         }
6990         if (appData.animateDragging) {
6991             /* Undo animation damage if any */
6992             DrawPosition(FALSE, NULL);
6993         }
6994         if (second) {
6995             /* Second up/down in same square; just abort move */
6996             second = 0;
6997             fromX = fromY = -1;
6998             gatingPiece = EmptySquare;
6999             ClearHighlights();
7000             gotPremove = 0;
7001             ClearPremoveHighlights();
7002         } else {
7003             /* First upclick in same square; start click-click mode */
7004             SetHighlights(x, y, -1, -1);
7005         }
7006         return;
7007     }
7008
7009     clearFlag = 0;
7010
7011     /* we now have a different from- and (possibly off-board) to-square */
7012     /* Completed move */
7013     toX = x;
7014     toY = y;
7015     saveAnimate = appData.animate;
7016     MarkTargetSquares(1);
7017     if (clickType == Press) {
7018         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7019             // must be Edit Position mode with empty-square selected
7020             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7021             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7022             return;
7023         }
7024         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7025             ChessSquare piece = boards[currentMove][fromY][fromX];
7026             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7027             promoSweep = defaultPromoChoice;
7028             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7029             selectFlag = 0; lastX = xPix; lastY = yPix;
7030             Sweep(0); // Pawn that is going to promote: preview promotion piece
7031             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7032             DrawPosition(FALSE, boards[currentMove]);
7033             return;
7034         }
7035         /* Finish clickclick move */
7036         if (appData.animate || appData.highlightLastMove) {
7037             SetHighlights(fromX, fromY, toX, toY);
7038         } else {
7039             ClearHighlights();
7040         }
7041     } else {
7042         /* Finish drag move */
7043         if (appData.highlightLastMove) {
7044             SetHighlights(fromX, fromY, toX, toY);
7045         } else {
7046             ClearHighlights();
7047         }
7048         DragPieceEnd(xPix, yPix); dragging = 0;
7049         /* Don't animate move and drag both */
7050         appData.animate = FALSE;
7051     }
7052
7053     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7054     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7055         ChessSquare piece = boards[currentMove][fromY][fromX];
7056         if(gameMode == EditPosition && piece != EmptySquare &&
7057            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7058             int n;
7059
7060             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7061                 n = PieceToNumber(piece - (int)BlackPawn);
7062                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7063                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7064                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7065             } else
7066             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7067                 n = PieceToNumber(piece);
7068                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7069                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7070                 boards[currentMove][n][BOARD_WIDTH-2]++;
7071             }
7072             boards[currentMove][fromY][fromX] = EmptySquare;
7073         }
7074         ClearHighlights();
7075         fromX = fromY = -1;
7076         DrawPosition(TRUE, boards[currentMove]);
7077         return;
7078     }
7079
7080     // off-board moves should not be highlighted
7081     if(x < 0 || y < 0) ClearHighlights();
7082
7083     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7084
7085     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7086         SetHighlights(fromX, fromY, toX, toY);
7087         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7088             // [HGM] super: promotion to captured piece selected from holdings
7089             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7090             promotionChoice = TRUE;
7091             // kludge follows to temporarily execute move on display, without promoting yet
7092             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7093             boards[currentMove][toY][toX] = p;
7094             DrawPosition(FALSE, boards[currentMove]);
7095             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7096             boards[currentMove][toY][toX] = q;
7097             DisplayMessage("Click in holdings to choose piece", "");
7098             return;
7099         }
7100         PromotionPopUp();
7101     } else {
7102         int oldMove = currentMove;
7103         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7104         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7105         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7106         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7107            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7108             DrawPosition(TRUE, boards[currentMove]);
7109         fromX = fromY = -1;
7110     }
7111     appData.animate = saveAnimate;
7112     if (appData.animate || appData.animateDragging) {
7113         /* Undo animation damage if needed */
7114         DrawPosition(FALSE, NULL);
7115     }
7116 }
7117
7118 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7119 {   // front-end-free part taken out of PieceMenuPopup
7120     int whichMenu; int xSqr, ySqr;
7121
7122     if(seekGraphUp) { // [HGM] seekgraph
7123         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7124         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7125         return -2;
7126     }
7127
7128     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7129          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7130         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7131         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7132         if(action == Press)   {
7133             originalFlip = flipView;
7134             flipView = !flipView; // temporarily flip board to see game from partners perspective
7135             DrawPosition(TRUE, partnerBoard);
7136             DisplayMessage(partnerStatus, "");
7137             partnerUp = TRUE;
7138         } else if(action == Release) {
7139             flipView = originalFlip;
7140             DrawPosition(TRUE, boards[currentMove]);
7141             partnerUp = FALSE;
7142         }
7143         return -2;
7144     }
7145
7146     xSqr = EventToSquare(x, BOARD_WIDTH);
7147     ySqr = EventToSquare(y, BOARD_HEIGHT);
7148     if (action == Release) {
7149         if(pieceSweep != EmptySquare) {
7150             EditPositionMenuEvent(pieceSweep, toX, toY);
7151             pieceSweep = EmptySquare;
7152         } else UnLoadPV(); // [HGM] pv
7153     }
7154     if (action != Press) return -2; // return code to be ignored
7155     switch (gameMode) {
7156       case IcsExamining:
7157         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7158       case EditPosition:
7159         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7160         if (xSqr < 0 || ySqr < 0) return -1;
7161         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7162         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7163         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7164         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7165         NextPiece(0);
7166         return 2; // grab
7167       case IcsObserving:
7168         if(!appData.icsEngineAnalyze) return -1;
7169       case IcsPlayingWhite:
7170       case IcsPlayingBlack:
7171         if(!appData.zippyPlay) goto noZip;
7172       case AnalyzeMode:
7173       case AnalyzeFile:
7174       case MachinePlaysWhite:
7175       case MachinePlaysBlack:
7176       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7177         if (!appData.dropMenu) {
7178           LoadPV(x, y);
7179           return 2; // flag front-end to grab mouse events
7180         }
7181         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7182            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7183       case EditGame:
7184       noZip:
7185         if (xSqr < 0 || ySqr < 0) return -1;
7186         if (!appData.dropMenu || appData.testLegality &&
7187             gameInfo.variant != VariantBughouse &&
7188             gameInfo.variant != VariantCrazyhouse) return -1;
7189         whichMenu = 1; // drop menu
7190         break;
7191       default:
7192         return -1;
7193     }
7194
7195     if (((*fromX = xSqr) < 0) ||
7196         ((*fromY = ySqr) < 0)) {
7197         *fromX = *fromY = -1;
7198         return -1;
7199     }
7200     if (flipView)
7201       *fromX = BOARD_WIDTH - 1 - *fromX;
7202     else
7203       *fromY = BOARD_HEIGHT - 1 - *fromY;
7204
7205     return whichMenu;
7206 }
7207
7208 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7209 {
7210 //    char * hint = lastHint;
7211     FrontEndProgramStats stats;
7212
7213     stats.which = cps == &first ? 0 : 1;
7214     stats.depth = cpstats->depth;
7215     stats.nodes = cpstats->nodes;
7216     stats.score = cpstats->score;
7217     stats.time = cpstats->time;
7218     stats.pv = cpstats->movelist;
7219     stats.hint = lastHint;
7220     stats.an_move_index = 0;
7221     stats.an_move_count = 0;
7222
7223     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7224         stats.hint = cpstats->move_name;
7225         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7226         stats.an_move_count = cpstats->nr_moves;
7227     }
7228
7229     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
7230
7231     SetProgramStats( &stats );
7232 }
7233
7234 void
7235 ClearEngineOutputPane(int which)
7236 {
7237     static FrontEndProgramStats dummyStats;
7238     dummyStats.which = which;
7239     dummyStats.pv = "#";
7240     SetProgramStats( &dummyStats );
7241 }
7242
7243 #define MAXPLAYERS 500
7244
7245 char *
7246 TourneyStandings(int display)
7247 {
7248     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7249     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7250     char result, *p, *names[MAXPLAYERS];
7251
7252     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7253         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7254     names[0] = p = strdup(appData.participants);
7255     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7256
7257     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7258
7259     while(result = appData.results[nr]) {
7260         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7261         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7262         wScore = bScore = 0;
7263         switch(result) {
7264           case '+': wScore = 2; break;
7265           case '-': bScore = 2; break;
7266           case '=': wScore = bScore = 1; break;
7267           case ' ':
7268           case '*': return strdup("busy"); // tourney not finished
7269         }
7270         score[w] += wScore;
7271         score[b] += bScore;
7272         games[w]++;
7273         games[b]++;
7274         nr++;
7275     }
7276     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7277     for(w=0; w<nPlayers; w++) {
7278         bScore = -1;
7279         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7280         ranking[w] = b; points[w] = bScore; score[b] = -2;
7281     }
7282     p = malloc(nPlayers*34+1);
7283     for(w=0; w<nPlayers && w<display; w++)
7284         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7285     free(names[0]);
7286     return p;
7287 }
7288
7289 void
7290 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7291 {       // count all piece types
7292         int p, f, r;
7293         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7294         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7295         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7296                 p = board[r][f];
7297                 pCnt[p]++;
7298                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7299                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7300                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7301                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7302                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7303                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7304         }
7305 }
7306
7307 int
7308 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7309 {
7310         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7311         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7312
7313         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7314         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7315         if(myPawns == 2 && nMine == 3) // KPP
7316             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7317         if(myPawns == 1 && nMine == 2) // KP
7318             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7319         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7320             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7321         if(myPawns) return FALSE;
7322         if(pCnt[WhiteRook+side])
7323             return pCnt[BlackRook-side] ||
7324                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7325                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7326                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7327         if(pCnt[WhiteCannon+side]) {
7328             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7329             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7330         }
7331         if(pCnt[WhiteKnight+side])
7332             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7333         return FALSE;
7334 }
7335
7336 int
7337 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7338 {
7339         VariantClass v = gameInfo.variant;
7340
7341         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7342         if(v == VariantShatranj) return TRUE; // always winnable through baring
7343         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7344         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7345
7346         if(v == VariantXiangqi) {
7347                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7348
7349                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7350                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7351                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7352                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7353                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7354                 if(stale) // we have at least one last-rank P plus perhaps C
7355                     return majors // KPKX
7356                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7357                 else // KCA*E*
7358                     return pCnt[WhiteFerz+side] // KCAK
7359                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7360                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7361                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7362
7363         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7364                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7365
7366                 if(nMine == 1) return FALSE; // bare King
7367                 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
7368                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7369                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7370                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7371                 if(pCnt[WhiteKnight+side])
7372                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7373                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7374                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7375                 if(nBishops)
7376                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7377                 if(pCnt[WhiteAlfil+side])
7378                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7379                 if(pCnt[WhiteWazir+side])
7380                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7381         }
7382
7383         return TRUE;
7384 }
7385
7386 int
7387 CompareWithRights(Board b1, Board b2)
7388 {
7389     int rights = 0;
7390     if(!CompareBoards(b1, b2)) return FALSE;
7391     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7392     /* compare castling rights */
7393     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7394            rights++; /* King lost rights, while rook still had them */
7395     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7396         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7397            rights++; /* but at least one rook lost them */
7398     }
7399     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7400            rights++;
7401     if( b1[CASTLING][5] != NoRights ) {
7402         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7403            rights++;
7404     }
7405     return rights == 0;
7406 }
7407
7408 int
7409 Adjudicate(ChessProgramState *cps)
7410 {       // [HGM] some adjudications useful with buggy engines
7411         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7412         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7413         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7414         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7415         int k, count = 0; static int bare = 1;
7416         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7417         Boolean canAdjudicate = !appData.icsActive;
7418
7419         // most tests only when we understand the game, i.e. legality-checking on
7420             if( appData.testLegality )
7421             {   /* [HGM] Some more adjudications for obstinate engines */
7422                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7423                 static int moveCount = 6;
7424                 ChessMove result;
7425                 char *reason = NULL;
7426
7427                 /* Count what is on board. */
7428                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7429
7430                 /* Some material-based adjudications that have to be made before stalemate test */
7431                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7432                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7433                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7434                      if(canAdjudicate && appData.checkMates) {
7435                          if(engineOpponent)
7436                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7437                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7438                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7439                          return 1;
7440                      }
7441                 }
7442
7443                 /* Bare King in Shatranj (loses) or Losers (wins) */
7444                 if( nrW == 1 || nrB == 1) {
7445                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7446                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7447                      if(canAdjudicate && appData.checkMates) {
7448                          if(engineOpponent)
7449                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7450                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7451                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7452                          return 1;
7453                      }
7454                   } else
7455                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7456                   {    /* bare King */
7457                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7458                         if(canAdjudicate && appData.checkMates) {
7459                             /* but only adjudicate if adjudication enabled */
7460                             if(engineOpponent)
7461                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7462                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7463                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7464                             return 1;
7465                         }
7466                   }
7467                 } else bare = 1;
7468
7469
7470             // don't wait for engine to announce game end if we can judge ourselves
7471             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7472               case MT_CHECK:
7473                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7474                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7475                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7476                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7477                             checkCnt++;
7478                         if(checkCnt >= 2) {
7479                             reason = "Xboard adjudication: 3rd check";
7480                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7481                             break;
7482                         }
7483                     }
7484                 }
7485               case MT_NONE:
7486               default:
7487                 break;
7488               case MT_STALEMATE:
7489               case MT_STAINMATE:
7490                 reason = "Xboard adjudication: Stalemate";
7491                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7492                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7493                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7494                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7495                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7496                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7497                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7498                                                                         EP_CHECKMATE : EP_WINS);
7499                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7500                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7501                 }
7502                 break;
7503               case MT_CHECKMATE:
7504                 reason = "Xboard adjudication: Checkmate";
7505                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7506                 break;
7507             }
7508
7509                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7510                     case EP_STALEMATE:
7511                         result = GameIsDrawn; break;
7512                     case EP_CHECKMATE:
7513                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7514                     case EP_WINS:
7515                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7516                     default:
7517                         result = EndOfFile;
7518                 }
7519                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7520                     if(engineOpponent)
7521                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7522                     GameEnds( result, reason, GE_XBOARD );
7523                     return 1;
7524                 }
7525
7526                 /* Next absolutely insufficient mating material. */
7527                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7528                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7529                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7530
7531                      /* always flag draws, for judging claims */
7532                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7533
7534                      if(canAdjudicate && appData.materialDraws) {
7535                          /* but only adjudicate them if adjudication enabled */
7536                          if(engineOpponent) {
7537                            SendToProgram("force\n", engineOpponent); // suppress reply
7538                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7539                          }
7540                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7541                          return 1;
7542                      }
7543                 }
7544
7545                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7546                 if(gameInfo.variant == VariantXiangqi ?
7547                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7548                  : nrW + nrB == 4 &&
7549                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7550                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7551                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7552                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7553                    ) ) {
7554                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7555                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7556                           if(engineOpponent) {
7557                             SendToProgram("force\n", engineOpponent); // suppress reply
7558                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7559                           }
7560                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7561                           return 1;
7562                      }
7563                 } else moveCount = 6;
7564             }
7565         if (appData.debugMode) { int i;
7566             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7567                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7568                     appData.drawRepeats);
7569             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7570               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7571
7572         }
7573
7574         // Repetition draws and 50-move rule can be applied independently of legality testing
7575
7576                 /* Check for rep-draws */
7577                 count = 0;
7578                 for(k = forwardMostMove-2;
7579                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7580                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7581                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7582                     k-=2)
7583                 {   int rights=0;
7584                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7585                         /* compare castling rights */
7586                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7587                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7588                                 rights++; /* King lost rights, while rook still had them */
7589                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7590                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7591                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7592                                    rights++; /* but at least one rook lost them */
7593                         }
7594                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7595                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7596                                 rights++;
7597                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7598                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7599                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7600                                    rights++;
7601                         }
7602                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7603                             && appData.drawRepeats > 1) {
7604                              /* adjudicate after user-specified nr of repeats */
7605                              int result = GameIsDrawn;
7606                              char *details = "XBoard adjudication: repetition draw";
7607                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7608                                 // [HGM] xiangqi: check for forbidden perpetuals
7609                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7610                                 for(m=forwardMostMove; m>k; m-=2) {
7611                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7612                                         ourPerpetual = 0; // the current mover did not always check
7613                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7614                                         hisPerpetual = 0; // the opponent did not always check
7615                                 }
7616                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7617                                                                         ourPerpetual, hisPerpetual);
7618                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7619                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7620                                     details = "Xboard adjudication: perpetual checking";
7621                                 } else
7622                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7623                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7624                                 } else
7625                                 // Now check for perpetual chases
7626                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7627                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7628                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7629                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7630                                         static char resdet[MSG_SIZ];
7631                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7632                                         details = resdet;
7633                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7634                                     } else
7635                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7636                                         break; // Abort repetition-checking loop.
7637                                 }
7638                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7639                              }
7640                              if(engineOpponent) {
7641                                SendToProgram("force\n", engineOpponent); // suppress reply
7642                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7643                              }
7644                              GameEnds( result, details, GE_XBOARD );
7645                              return 1;
7646                         }
7647                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7648                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7649                     }
7650                 }
7651
7652                 /* Now we test for 50-move draws. Determine ply count */
7653                 count = forwardMostMove;
7654                 /* look for last irreversble move */
7655                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7656                     count--;
7657                 /* if we hit starting position, add initial plies */
7658                 if( count == backwardMostMove )
7659                     count -= initialRulePlies;
7660                 count = forwardMostMove - count;
7661                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7662                         // adjust reversible move counter for checks in Xiangqi
7663                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7664                         if(i < backwardMostMove) i = backwardMostMove;
7665                         while(i <= forwardMostMove) {
7666                                 lastCheck = inCheck; // check evasion does not count
7667                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7668                                 if(inCheck || lastCheck) count--; // check does not count
7669                                 i++;
7670                         }
7671                 }
7672                 if( count >= 100)
7673                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7674                          /* this is used to judge if draw claims are legal */
7675                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7676                          if(engineOpponent) {
7677                            SendToProgram("force\n", engineOpponent); // suppress reply
7678                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7679                          }
7680                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7681                          return 1;
7682                 }
7683
7684                 /* if draw offer is pending, treat it as a draw claim
7685                  * when draw condition present, to allow engines a way to
7686                  * claim draws before making their move to avoid a race
7687                  * condition occurring after their move
7688                  */
7689                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7690                          char *p = NULL;
7691                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7692                              p = "Draw claim: 50-move rule";
7693                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7694                              p = "Draw claim: 3-fold repetition";
7695                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7696                              p = "Draw claim: insufficient mating material";
7697                          if( p != NULL && canAdjudicate) {
7698                              if(engineOpponent) {
7699                                SendToProgram("force\n", engineOpponent); // suppress reply
7700                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7701                              }
7702                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7703                              return 1;
7704                          }
7705                 }
7706
7707                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7708                     if(engineOpponent) {
7709                       SendToProgram("force\n", engineOpponent); // suppress reply
7710                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7711                     }
7712                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7713                     return 1;
7714                 }
7715         return 0;
7716 }
7717
7718 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7719 {   // [HGM] book: this routine intercepts moves to simulate book replies
7720     char *bookHit = NULL;
7721
7722     //first determine if the incoming move brings opponent into his book
7723     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7724         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7725     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7726     if(bookHit != NULL && !cps->bookSuspend) {
7727         // make sure opponent is not going to reply after receiving move to book position
7728         SendToProgram("force\n", cps);
7729         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7730     }
7731     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7732     // now arrange restart after book miss
7733     if(bookHit) {
7734         // after a book hit we never send 'go', and the code after the call to this routine
7735         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7736         char buf[MSG_SIZ], *move = bookHit;
7737         if(cps->useSAN) {
7738             int fromX, fromY, toX, toY;
7739             char promoChar;
7740             ChessMove moveType;
7741             move = buf + 30;
7742             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7743                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7744                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7745                                     PosFlags(forwardMostMove),
7746                                     fromY, fromX, toY, toX, promoChar, move);
7747             } else {
7748                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7749                 bookHit = NULL;
7750             }
7751         }
7752         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7753         SendToProgram(buf, cps);
7754         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7755     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7756         SendToProgram("go\n", cps);
7757         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7758     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7759         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7760             SendToProgram("go\n", cps);
7761         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7762     }
7763     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7764 }
7765
7766 char *savedMessage;
7767 ChessProgramState *savedState;
7768 void DeferredBookMove(void)
7769 {
7770         if(savedState->lastPing != savedState->lastPong)
7771                     ScheduleDelayedEvent(DeferredBookMove, 10);
7772         else
7773         HandleMachineMove(savedMessage, savedState);
7774 }
7775
7776 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7777
7778 void
7779 HandleMachineMove(message, cps)
7780      char *message;
7781      ChessProgramState *cps;
7782 {
7783     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7784     char realname[MSG_SIZ];
7785     int fromX, fromY, toX, toY;
7786     ChessMove moveType;
7787     char promoChar;
7788     char *p, *pv=buf1;
7789     int machineWhite;
7790     char *bookHit;
7791
7792     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7793         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7794         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7795             DisplayError(_("Invalid pairing from pairing engine"), 0);
7796             return;
7797         }
7798         pairingReceived = 1;
7799         NextMatchGame();
7800         return; // Skim the pairing messages here.
7801     }
7802
7803     cps->userError = 0;
7804
7805 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7806     /*
7807      * Kludge to ignore BEL characters
7808      */
7809     while (*message == '\007') message++;
7810
7811     /*
7812      * [HGM] engine debug message: ignore lines starting with '#' character
7813      */
7814     if(cps->debug && *message == '#') return;
7815
7816     /*
7817      * Look for book output
7818      */
7819     if (cps == &first && bookRequested) {
7820         if (message[0] == '\t' || message[0] == ' ') {
7821             /* Part of the book output is here; append it */
7822             strcat(bookOutput, message);
7823             strcat(bookOutput, "  \n");
7824             return;
7825         } else if (bookOutput[0] != NULLCHAR) {
7826             /* All of book output has arrived; display it */
7827             char *p = bookOutput;
7828             while (*p != NULLCHAR) {
7829                 if (*p == '\t') *p = ' ';
7830                 p++;
7831             }
7832             DisplayInformation(bookOutput);
7833             bookRequested = FALSE;
7834             /* Fall through to parse the current output */
7835         }
7836     }
7837
7838     /*
7839      * Look for machine move.
7840      */
7841     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7842         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7843     {
7844         /* This method is only useful on engines that support ping */
7845         if (cps->lastPing != cps->lastPong) {
7846           if (gameMode == BeginningOfGame) {
7847             /* Extra move from before last new; ignore */
7848             if (appData.debugMode) {
7849                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7850             }
7851           } else {
7852             if (appData.debugMode) {
7853                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7854                         cps->which, gameMode);
7855             }
7856
7857             SendToProgram("undo\n", cps);
7858           }
7859           return;
7860         }
7861
7862         switch (gameMode) {
7863           case BeginningOfGame:
7864             /* Extra move from before last reset; ignore */
7865             if (appData.debugMode) {
7866                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7867             }
7868             return;
7869
7870           case EndOfGame:
7871           case IcsIdle:
7872           default:
7873             /* Extra move after we tried to stop.  The mode test is
7874                not a reliable way of detecting this problem, but it's
7875                the best we can do on engines that don't support ping.
7876             */
7877             if (appData.debugMode) {
7878                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7879                         cps->which, gameMode);
7880             }
7881             SendToProgram("undo\n", cps);
7882             return;
7883
7884           case MachinePlaysWhite:
7885           case IcsPlayingWhite:
7886             machineWhite = TRUE;
7887             break;
7888
7889           case MachinePlaysBlack:
7890           case IcsPlayingBlack:
7891             machineWhite = FALSE;
7892             break;
7893
7894           case TwoMachinesPlay:
7895             machineWhite = (cps->twoMachinesColor[0] == 'w');
7896             break;
7897         }
7898         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7899             if (appData.debugMode) {
7900                 fprintf(debugFP,
7901                         "Ignoring move out of turn by %s, gameMode %d"
7902                         ", forwardMost %d\n",
7903                         cps->which, gameMode, forwardMostMove);
7904             }
7905             return;
7906         }
7907
7908     if (appData.debugMode) { int f = forwardMostMove;
7909         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7910                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7911                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7912     }
7913         if(cps->alphaRank) AlphaRank(machineMove, 4);
7914         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7915                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7916             /* Machine move could not be parsed; ignore it. */
7917           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7918                     machineMove, _(cps->which));
7919             DisplayError(buf1, 0);
7920             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7921                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7922             if (gameMode == TwoMachinesPlay) {
7923               GameEnds(machineWhite ? BlackWins : WhiteWins,
7924                        buf1, GE_XBOARD);
7925             }
7926             return;
7927         }
7928
7929         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7930         /* So we have to redo legality test with true e.p. status here,  */
7931         /* to make sure an illegal e.p. capture does not slip through,   */
7932         /* to cause a forfeit on a justified illegal-move complaint      */
7933         /* of the opponent.                                              */
7934         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7935            ChessMove moveType;
7936            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7937                              fromY, fromX, toY, toX, promoChar);
7938             if (appData.debugMode) {
7939                 int i;
7940                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7941                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7942                 fprintf(debugFP, "castling rights\n");
7943             }
7944             if(moveType == IllegalMove) {
7945               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7946                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7947                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7948                            buf1, GE_XBOARD);
7949                 return;
7950            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7951            /* [HGM] Kludge to handle engines that send FRC-style castling
7952               when they shouldn't (like TSCP-Gothic) */
7953            switch(moveType) {
7954              case WhiteASideCastleFR:
7955              case BlackASideCastleFR:
7956                toX+=2;
7957                currentMoveString[2]++;
7958                break;
7959              case WhiteHSideCastleFR:
7960              case BlackHSideCastleFR:
7961                toX--;
7962                currentMoveString[2]--;
7963                break;
7964              default: ; // nothing to do, but suppresses warning of pedantic compilers
7965            }
7966         }
7967         hintRequested = FALSE;
7968         lastHint[0] = NULLCHAR;
7969         bookRequested = FALSE;
7970         /* Program may be pondering now */
7971         cps->maybeThinking = TRUE;
7972         if (cps->sendTime == 2) cps->sendTime = 1;
7973         if (cps->offeredDraw) cps->offeredDraw--;
7974
7975         /* [AS] Save move info*/
7976         pvInfoList[ forwardMostMove ].score = programStats.score;
7977         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7978         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7979
7980         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7981
7982         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7983         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7984             int count = 0;
7985
7986             while( count < adjudicateLossPlies ) {
7987                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7988
7989                 if( count & 1 ) {
7990                     score = -score; /* Flip score for winning side */
7991                 }
7992
7993                 if( score > adjudicateLossThreshold ) {
7994                     break;
7995                 }
7996
7997                 count++;
7998             }
7999
8000             if( count >= adjudicateLossPlies ) {
8001                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8002
8003                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8004                     "Xboard adjudication",
8005                     GE_XBOARD );
8006
8007                 return;
8008             }
8009         }
8010
8011         if(Adjudicate(cps)) {
8012             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8013             return; // [HGM] adjudicate: for all automatic game ends
8014         }
8015
8016 #if ZIPPY
8017         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8018             first.initDone) {
8019           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8020                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8021                 SendToICS("draw ");
8022                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8023           }
8024           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8025           ics_user_moved = 1;
8026           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8027                 char buf[3*MSG_SIZ];
8028
8029                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8030                         programStats.score / 100.,
8031                         programStats.depth,
8032                         programStats.time / 100.,
8033                         (unsigned int)programStats.nodes,
8034                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8035                         programStats.movelist);
8036                 SendToICS(buf);
8037 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8038           }
8039         }
8040 #endif
8041
8042         /* [AS] Clear stats for next move */
8043         ClearProgramStats();
8044         thinkOutput[0] = NULLCHAR;
8045         hiddenThinkOutputState = 0;
8046
8047         bookHit = NULL;
8048         if (gameMode == TwoMachinesPlay) {
8049             /* [HGM] relaying draw offers moved to after reception of move */
8050             /* and interpreting offer as claim if it brings draw condition */
8051             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8052                 SendToProgram("draw\n", cps->other);
8053             }
8054             if (cps->other->sendTime) {
8055                 SendTimeRemaining(cps->other,
8056                                   cps->other->twoMachinesColor[0] == 'w');
8057             }
8058             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8059             if (firstMove && !bookHit) {
8060                 firstMove = FALSE;
8061                 if (cps->other->useColors) {
8062                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8063                 }
8064                 SendToProgram("go\n", cps->other);
8065             }
8066             cps->other->maybeThinking = TRUE;
8067         }
8068
8069         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8070
8071         if (!pausing && appData.ringBellAfterMoves) {
8072             RingBell();
8073         }
8074
8075         /*
8076          * Reenable menu items that were disabled while
8077          * machine was thinking
8078          */
8079         if (gameMode != TwoMachinesPlay)
8080             SetUserThinkingEnables();
8081
8082         // [HGM] book: after book hit opponent has received move and is now in force mode
8083         // force the book reply into it, and then fake that it outputted this move by jumping
8084         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8085         if(bookHit) {
8086                 static char bookMove[MSG_SIZ]; // a bit generous?
8087
8088                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8089                 strcat(bookMove, bookHit);
8090                 message = bookMove;
8091                 cps = cps->other;
8092                 programStats.nodes = programStats.depth = programStats.time =
8093                 programStats.score = programStats.got_only_move = 0;
8094                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8095
8096                 if(cps->lastPing != cps->lastPong) {
8097                     savedMessage = message; // args for deferred call
8098                     savedState = cps;
8099                     ScheduleDelayedEvent(DeferredBookMove, 10);
8100                     return;
8101                 }
8102                 goto FakeBookMove;
8103         }
8104
8105         return;
8106     }
8107
8108     /* Set special modes for chess engines.  Later something general
8109      *  could be added here; for now there is just one kludge feature,
8110      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8111      *  when "xboard" is given as an interactive command.
8112      */
8113     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8114         cps->useSigint = FALSE;
8115         cps->useSigterm = FALSE;
8116     }
8117     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8118       ParseFeatures(message+8, cps);
8119       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8120     }
8121
8122     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8123                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8124       int dummy, s=6; char buf[MSG_SIZ];
8125       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8126       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8127       if(startedFromSetupPosition) return;
8128       ParseFEN(boards[0], &dummy, message+s);
8129       DrawPosition(TRUE, boards[0]);
8130       startedFromSetupPosition = TRUE;
8131       return;
8132     }
8133     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8134      * want this, I was asked to put it in, and obliged.
8135      */
8136     if (!strncmp(message, "setboard ", 9)) {
8137         Board initial_position;
8138
8139         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8140
8141         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8142             DisplayError(_("Bad FEN received from engine"), 0);
8143             return ;
8144         } else {
8145            Reset(TRUE, FALSE);
8146            CopyBoard(boards[0], initial_position);
8147            initialRulePlies = FENrulePlies;
8148            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8149            else gameMode = MachinePlaysBlack;
8150            DrawPosition(FALSE, boards[currentMove]);
8151         }
8152         return;
8153     }
8154
8155     /*
8156      * Look for communication commands
8157      */
8158     if (!strncmp(message, "telluser ", 9)) {
8159         if(message[9] == '\\' && message[10] == '\\')
8160             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8161         PlayTellSound();
8162         DisplayNote(message + 9);
8163         return;
8164     }
8165     if (!strncmp(message, "tellusererror ", 14)) {
8166         cps->userError = 1;
8167         if(message[14] == '\\' && message[15] == '\\')
8168             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8169         PlayTellSound();
8170         DisplayError(message + 14, 0);
8171         return;
8172     }
8173     if (!strncmp(message, "tellopponent ", 13)) {
8174       if (appData.icsActive) {
8175         if (loggedOn) {
8176           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8177           SendToICS(buf1);
8178         }
8179       } else {
8180         DisplayNote(message + 13);
8181       }
8182       return;
8183     }
8184     if (!strncmp(message, "tellothers ", 11)) {
8185       if (appData.icsActive) {
8186         if (loggedOn) {
8187           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8188           SendToICS(buf1);
8189         }
8190       }
8191       return;
8192     }
8193     if (!strncmp(message, "tellall ", 8)) {
8194       if (appData.icsActive) {
8195         if (loggedOn) {
8196           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8197           SendToICS(buf1);
8198         }
8199       } else {
8200         DisplayNote(message + 8);
8201       }
8202       return;
8203     }
8204     if (strncmp(message, "warning", 7) == 0) {
8205         /* Undocumented feature, use tellusererror in new code */
8206         DisplayError(message, 0);
8207         return;
8208     }
8209     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8210         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8211         strcat(realname, " query");
8212         AskQuestion(realname, buf2, buf1, cps->pr);
8213         return;
8214     }
8215     /* Commands from the engine directly to ICS.  We don't allow these to be
8216      *  sent until we are logged on. Crafty kibitzes have been known to
8217      *  interfere with the login process.
8218      */
8219     if (loggedOn) {
8220         if (!strncmp(message, "tellics ", 8)) {
8221             SendToICS(message + 8);
8222             SendToICS("\n");
8223             return;
8224         }
8225         if (!strncmp(message, "tellicsnoalias ", 15)) {
8226             SendToICS(ics_prefix);
8227             SendToICS(message + 15);
8228             SendToICS("\n");
8229             return;
8230         }
8231         /* The following are for backward compatibility only */
8232         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8233             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8234             SendToICS(ics_prefix);
8235             SendToICS(message);
8236             SendToICS("\n");
8237             return;
8238         }
8239     }
8240     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8241         return;
8242     }
8243     /*
8244      * If the move is illegal, cancel it and redraw the board.
8245      * Also deal with other error cases.  Matching is rather loose
8246      * here to accommodate engines written before the spec.
8247      */
8248     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8249         strncmp(message, "Error", 5) == 0) {
8250         if (StrStr(message, "name") ||
8251             StrStr(message, "rating") || StrStr(message, "?") ||
8252             StrStr(message, "result") || StrStr(message, "board") ||
8253             StrStr(message, "bk") || StrStr(message, "computer") ||
8254             StrStr(message, "variant") || StrStr(message, "hint") ||
8255             StrStr(message, "random") || StrStr(message, "depth") ||
8256             StrStr(message, "accepted")) {
8257             return;
8258         }
8259         if (StrStr(message, "protover")) {
8260           /* Program is responding to input, so it's apparently done
8261              initializing, and this error message indicates it is
8262              protocol version 1.  So we don't need to wait any longer
8263              for it to initialize and send feature commands. */
8264           FeatureDone(cps, 1);
8265           cps->protocolVersion = 1;
8266           return;
8267         }
8268         cps->maybeThinking = FALSE;
8269
8270         if (StrStr(message, "draw")) {
8271             /* Program doesn't have "draw" command */
8272             cps->sendDrawOffers = 0;
8273             return;
8274         }
8275         if (cps->sendTime != 1 &&
8276             (StrStr(message, "time") || StrStr(message, "otim"))) {
8277           /* Program apparently doesn't have "time" or "otim" command */
8278           cps->sendTime = 0;
8279           return;
8280         }
8281         if (StrStr(message, "analyze")) {
8282             cps->analysisSupport = FALSE;
8283             cps->analyzing = FALSE;
8284 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8285             EditGameEvent(); // [HGM] try to preserve loaded game
8286             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8287             DisplayError(buf2, 0);
8288             return;
8289         }
8290         if (StrStr(message, "(no matching move)st")) {
8291           /* Special kludge for GNU Chess 4 only */
8292           cps->stKludge = TRUE;
8293           SendTimeControl(cps, movesPerSession, timeControl,
8294                           timeIncrement, appData.searchDepth,
8295                           searchTime);
8296           return;
8297         }
8298         if (StrStr(message, "(no matching move)sd")) {
8299           /* Special kludge for GNU Chess 4 only */
8300           cps->sdKludge = TRUE;
8301           SendTimeControl(cps, movesPerSession, timeControl,
8302                           timeIncrement, appData.searchDepth,
8303                           searchTime);
8304           return;
8305         }
8306         if (!StrStr(message, "llegal")) {
8307             return;
8308         }
8309         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8310             gameMode == IcsIdle) return;
8311         if (forwardMostMove <= backwardMostMove) return;
8312         if (pausing) PauseEvent();
8313       if(appData.forceIllegal) {
8314             // [HGM] illegal: machine refused move; force position after move into it
8315           SendToProgram("force\n", cps);
8316           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8317                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8318                 // when black is to move, while there might be nothing on a2 or black
8319                 // might already have the move. So send the board as if white has the move.
8320                 // But first we must change the stm of the engine, as it refused the last move
8321                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8322                 if(WhiteOnMove(forwardMostMove)) {
8323                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8324                     SendBoard(cps, forwardMostMove); // kludgeless board
8325                 } else {
8326                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8327                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8328                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8329                 }
8330           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8331             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8332                  gameMode == TwoMachinesPlay)
8333               SendToProgram("go\n", cps);
8334             return;
8335       } else
8336         if (gameMode == PlayFromGameFile) {
8337             /* Stop reading this game file */
8338             gameMode = EditGame;
8339             ModeHighlight();
8340         }
8341         /* [HGM] illegal-move claim should forfeit game when Xboard */
8342         /* only passes fully legal moves                            */
8343         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8344             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8345                                 "False illegal-move claim", GE_XBOARD );
8346             return; // do not take back move we tested as valid
8347         }
8348         currentMove = forwardMostMove-1;
8349         DisplayMove(currentMove-1); /* before DisplayMoveError */
8350         SwitchClocks(forwardMostMove-1); // [HGM] race
8351         DisplayBothClocks();
8352         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8353                 parseList[currentMove], _(cps->which));
8354         DisplayMoveError(buf1);
8355         DrawPosition(FALSE, boards[currentMove]);
8356         return;
8357     }
8358     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8359         /* Program has a broken "time" command that
8360            outputs a string not ending in newline.
8361            Don't use it. */
8362         cps->sendTime = 0;
8363     }
8364
8365     /*
8366      * If chess program startup fails, exit with an error message.
8367      * Attempts to recover here are futile.
8368      */
8369     if ((StrStr(message, "unknown host") != NULL)
8370         || (StrStr(message, "No remote directory") != NULL)
8371         || (StrStr(message, "not found") != NULL)
8372         || (StrStr(message, "No such file") != NULL)
8373         || (StrStr(message, "can't alloc") != NULL)
8374         || (StrStr(message, "Permission denied") != NULL)) {
8375
8376         cps->maybeThinking = FALSE;
8377         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8378                 _(cps->which), cps->program, cps->host, message);
8379         RemoveInputSource(cps->isr);
8380         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8381             if(cps == &first) appData.noChessProgram = TRUE;
8382             DisplayError(buf1, 0);
8383         }
8384         return;
8385     }
8386
8387     /*
8388      * Look for hint output
8389      */
8390     if (sscanf(message, "Hint: %s", buf1) == 1) {
8391         if (cps == &first && hintRequested) {
8392             hintRequested = FALSE;
8393             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8394                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8395                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8396                                     PosFlags(forwardMostMove),
8397                                     fromY, fromX, toY, toX, promoChar, buf1);
8398                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8399                 DisplayInformation(buf2);
8400             } else {
8401                 /* Hint move could not be parsed!? */
8402               snprintf(buf2, sizeof(buf2),
8403                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8404                         buf1, _(cps->which));
8405                 DisplayError(buf2, 0);
8406             }
8407         } else {
8408           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8409         }
8410         return;
8411     }
8412
8413     /*
8414      * Ignore other messages if game is not in progress
8415      */
8416     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8417         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8418
8419     /*
8420      * look for win, lose, draw, or draw offer
8421      */
8422     if (strncmp(message, "1-0", 3) == 0) {
8423         char *p, *q, *r = "";
8424         p = strchr(message, '{');
8425         if (p) {
8426             q = strchr(p, '}');
8427             if (q) {
8428                 *q = NULLCHAR;
8429                 r = p + 1;
8430             }
8431         }
8432         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8433         return;
8434     } else if (strncmp(message, "0-1", 3) == 0) {
8435         char *p, *q, *r = "";
8436         p = strchr(message, '{');
8437         if (p) {
8438             q = strchr(p, '}');
8439             if (q) {
8440                 *q = NULLCHAR;
8441                 r = p + 1;
8442             }
8443         }
8444         /* Kludge for Arasan 4.1 bug */
8445         if (strcmp(r, "Black resigns") == 0) {
8446             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8447             return;
8448         }
8449         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8450         return;
8451     } else if (strncmp(message, "1/2", 3) == 0) {
8452         char *p, *q, *r = "";
8453         p = strchr(message, '{');
8454         if (p) {
8455             q = strchr(p, '}');
8456             if (q) {
8457                 *q = NULLCHAR;
8458                 r = p + 1;
8459             }
8460         }
8461
8462         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8463         return;
8464
8465     } else if (strncmp(message, "White resign", 12) == 0) {
8466         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8467         return;
8468     } else if (strncmp(message, "Black resign", 12) == 0) {
8469         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8470         return;
8471     } else if (strncmp(message, "White matches", 13) == 0 ||
8472                strncmp(message, "Black matches", 13) == 0   ) {
8473         /* [HGM] ignore GNUShogi noises */
8474         return;
8475     } else if (strncmp(message, "White", 5) == 0 &&
8476                message[5] != '(' &&
8477                StrStr(message, "Black") == NULL) {
8478         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8479         return;
8480     } else if (strncmp(message, "Black", 5) == 0 &&
8481                message[5] != '(') {
8482         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8483         return;
8484     } else if (strcmp(message, "resign") == 0 ||
8485                strcmp(message, "computer resigns") == 0) {
8486         switch (gameMode) {
8487           case MachinePlaysBlack:
8488           case IcsPlayingBlack:
8489             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8490             break;
8491           case MachinePlaysWhite:
8492           case IcsPlayingWhite:
8493             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8494             break;
8495           case TwoMachinesPlay:
8496             if (cps->twoMachinesColor[0] == 'w')
8497               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8498             else
8499               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8500             break;
8501           default:
8502             /* can't happen */
8503             break;
8504         }
8505         return;
8506     } else if (strncmp(message, "opponent mates", 14) == 0) {
8507         switch (gameMode) {
8508           case MachinePlaysBlack:
8509           case IcsPlayingBlack:
8510             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8511             break;
8512           case MachinePlaysWhite:
8513           case IcsPlayingWhite:
8514             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8515             break;
8516           case TwoMachinesPlay:
8517             if (cps->twoMachinesColor[0] == 'w')
8518               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8519             else
8520               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8521             break;
8522           default:
8523             /* can't happen */
8524             break;
8525         }
8526         return;
8527     } else if (strncmp(message, "computer mates", 14) == 0) {
8528         switch (gameMode) {
8529           case MachinePlaysBlack:
8530           case IcsPlayingBlack:
8531             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8532             break;
8533           case MachinePlaysWhite:
8534           case IcsPlayingWhite:
8535             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8536             break;
8537           case TwoMachinesPlay:
8538             if (cps->twoMachinesColor[0] == 'w')
8539               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8540             else
8541               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8542             break;
8543           default:
8544             /* can't happen */
8545             break;
8546         }
8547         return;
8548     } else if (strncmp(message, "checkmate", 9) == 0) {
8549         if (WhiteOnMove(forwardMostMove)) {
8550             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8551         } else {
8552             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8553         }
8554         return;
8555     } else if (strstr(message, "Draw") != NULL ||
8556                strstr(message, "game is a draw") != NULL) {
8557         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8558         return;
8559     } else if (strstr(message, "offer") != NULL &&
8560                strstr(message, "draw") != NULL) {
8561 #if ZIPPY
8562         if (appData.zippyPlay && first.initDone) {
8563             /* Relay offer to ICS */
8564             SendToICS(ics_prefix);
8565             SendToICS("draw\n");
8566         }
8567 #endif
8568         cps->offeredDraw = 2; /* valid until this engine moves twice */
8569         if (gameMode == TwoMachinesPlay) {
8570             if (cps->other->offeredDraw) {
8571                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8572             /* [HGM] in two-machine mode we delay relaying draw offer      */
8573             /* until after we also have move, to see if it is really claim */
8574             }
8575         } else if (gameMode == MachinePlaysWhite ||
8576                    gameMode == MachinePlaysBlack) {
8577           if (userOfferedDraw) {
8578             DisplayInformation(_("Machine accepts your draw offer"));
8579             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8580           } else {
8581             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8582           }
8583         }
8584     }
8585
8586
8587     /*
8588      * Look for thinking output
8589      */
8590     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8591           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8592                                 ) {
8593         int plylev, mvleft, mvtot, curscore, time;
8594         char mvname[MOVE_LEN];
8595         u64 nodes; // [DM]
8596         char plyext;
8597         int ignore = FALSE;
8598         int prefixHint = FALSE;
8599         mvname[0] = NULLCHAR;
8600
8601         switch (gameMode) {
8602           case MachinePlaysBlack:
8603           case IcsPlayingBlack:
8604             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8605             break;
8606           case MachinePlaysWhite:
8607           case IcsPlayingWhite:
8608             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8609             break;
8610           case AnalyzeMode:
8611           case AnalyzeFile:
8612             break;
8613           case IcsObserving: /* [DM] icsEngineAnalyze */
8614             if (!appData.icsEngineAnalyze) ignore = TRUE;
8615             break;
8616           case TwoMachinesPlay:
8617             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8618                 ignore = TRUE;
8619             }
8620             break;
8621           default:
8622             ignore = TRUE;
8623             break;
8624         }
8625
8626         if (!ignore) {
8627             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8628             buf1[0] = NULLCHAR;
8629             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8630                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8631
8632                 if (plyext != ' ' && plyext != '\t') {
8633                     time *= 100;
8634                 }
8635
8636                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8637                 if( cps->scoreIsAbsolute &&
8638                     ( gameMode == MachinePlaysBlack ||
8639                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8640                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8641                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8642                      !WhiteOnMove(currentMove)
8643                     ) )
8644                 {
8645                     curscore = -curscore;
8646                 }
8647
8648                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8649
8650                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8651                         char buf[MSG_SIZ];
8652                         FILE *f;
8653                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8654                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8655                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8656                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8657                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8658                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8659                                 fclose(f);
8660                         } else DisplayError("failed writing PV", 0);
8661                 }
8662
8663                 tempStats.depth = plylev;
8664                 tempStats.nodes = nodes;
8665                 tempStats.time = time;
8666                 tempStats.score = curscore;
8667                 tempStats.got_only_move = 0;
8668
8669                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8670                         int ticklen;
8671
8672                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8673                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8674                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8675                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8676                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8677                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8678                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8679                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8680                 }
8681
8682                 /* Buffer overflow protection */
8683                 if (pv[0] != NULLCHAR) {
8684                     if (strlen(pv) >= sizeof(tempStats.movelist)
8685                         && appData.debugMode) {
8686                         fprintf(debugFP,
8687                                 "PV is too long; using the first %u bytes.\n",
8688                                 (unsigned) sizeof(tempStats.movelist) - 1);
8689                     }
8690
8691                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8692                 } else {
8693                     sprintf(tempStats.movelist, " no PV\n");
8694                 }
8695
8696                 if (tempStats.seen_stat) {
8697                     tempStats.ok_to_send = 1;
8698                 }
8699
8700                 if (strchr(tempStats.movelist, '(') != NULL) {
8701                     tempStats.line_is_book = 1;
8702                     tempStats.nr_moves = 0;
8703                     tempStats.moves_left = 0;
8704                 } else {
8705                     tempStats.line_is_book = 0;
8706                 }
8707
8708                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8709                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8710
8711                 SendProgramStatsToFrontend( cps, &tempStats );
8712
8713                 /*
8714                     [AS] Protect the thinkOutput buffer from overflow... this
8715                     is only useful if buf1 hasn't overflowed first!
8716                 */
8717                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8718                          plylev,
8719                          (gameMode == TwoMachinesPlay ?
8720                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8721                          ((double) curscore) / 100.0,
8722                          prefixHint ? lastHint : "",
8723                          prefixHint ? " " : "" );
8724
8725                 if( buf1[0] != NULLCHAR ) {
8726                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8727
8728                     if( strlen(pv) > max_len ) {
8729                         if( appData.debugMode) {
8730                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8731                         }
8732                         pv[max_len+1] = '\0';
8733                     }
8734
8735                     strcat( thinkOutput, pv);
8736                 }
8737
8738                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8739                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8740                     DisplayMove(currentMove - 1);
8741                 }
8742                 return;
8743
8744             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8745                 /* crafty (9.25+) says "(only move) <move>"
8746                  * if there is only 1 legal move
8747                  */
8748                 sscanf(p, "(only move) %s", buf1);
8749                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8750                 sprintf(programStats.movelist, "%s (only move)", buf1);
8751                 programStats.depth = 1;
8752                 programStats.nr_moves = 1;
8753                 programStats.moves_left = 1;
8754                 programStats.nodes = 1;
8755                 programStats.time = 1;
8756                 programStats.got_only_move = 1;
8757
8758                 /* Not really, but we also use this member to
8759                    mean "line isn't going to change" (Crafty
8760                    isn't searching, so stats won't change) */
8761                 programStats.line_is_book = 1;
8762
8763                 SendProgramStatsToFrontend( cps, &programStats );
8764
8765                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8766                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8767                     DisplayMove(currentMove - 1);
8768                 }
8769                 return;
8770             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8771                               &time, &nodes, &plylev, &mvleft,
8772                               &mvtot, mvname) >= 5) {
8773                 /* The stat01: line is from Crafty (9.29+) in response
8774                    to the "." command */
8775                 programStats.seen_stat = 1;
8776                 cps->maybeThinking = TRUE;
8777
8778                 if (programStats.got_only_move || !appData.periodicUpdates)
8779                   return;
8780
8781                 programStats.depth = plylev;
8782                 programStats.time = time;
8783                 programStats.nodes = nodes;
8784                 programStats.moves_left = mvleft;
8785                 programStats.nr_moves = mvtot;
8786                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8787                 programStats.ok_to_send = 1;
8788                 programStats.movelist[0] = '\0';
8789
8790                 SendProgramStatsToFrontend( cps, &programStats );
8791
8792                 return;
8793
8794             } else if (strncmp(message,"++",2) == 0) {
8795                 /* Crafty 9.29+ outputs this */
8796                 programStats.got_fail = 2;
8797                 return;
8798
8799             } else if (strncmp(message,"--",2) == 0) {
8800                 /* Crafty 9.29+ outputs this */
8801                 programStats.got_fail = 1;
8802                 return;
8803
8804             } else if (thinkOutput[0] != NULLCHAR &&
8805                        strncmp(message, "    ", 4) == 0) {
8806                 unsigned message_len;
8807
8808                 p = message;
8809                 while (*p && *p == ' ') p++;
8810
8811                 message_len = strlen( p );
8812
8813                 /* [AS] Avoid buffer overflow */
8814                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8815                     strcat(thinkOutput, " ");
8816                     strcat(thinkOutput, p);
8817                 }
8818
8819                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8820                     strcat(programStats.movelist, " ");
8821                     strcat(programStats.movelist, p);
8822                 }
8823
8824                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8825                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8826                     DisplayMove(currentMove - 1);
8827                 }
8828                 return;
8829             }
8830         }
8831         else {
8832             buf1[0] = NULLCHAR;
8833
8834             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8835                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8836             {
8837                 ChessProgramStats cpstats;
8838
8839                 if (plyext != ' ' && plyext != '\t') {
8840                     time *= 100;
8841                 }
8842
8843                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8844                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8845                     curscore = -curscore;
8846                 }
8847
8848                 cpstats.depth = plylev;
8849                 cpstats.nodes = nodes;
8850                 cpstats.time = time;
8851                 cpstats.score = curscore;
8852                 cpstats.got_only_move = 0;
8853                 cpstats.movelist[0] = '\0';
8854
8855                 if (buf1[0] != NULLCHAR) {
8856                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8857                 }
8858
8859                 cpstats.ok_to_send = 0;
8860                 cpstats.line_is_book = 0;
8861                 cpstats.nr_moves = 0;
8862                 cpstats.moves_left = 0;
8863
8864                 SendProgramStatsToFrontend( cps, &cpstats );
8865             }
8866         }
8867     }
8868 }
8869
8870
8871 /* Parse a game score from the character string "game", and
8872    record it as the history of the current game.  The game
8873    score is NOT assumed to start from the standard position.
8874    The display is not updated in any way.
8875    */
8876 void
8877 ParseGameHistory(game)
8878      char *game;
8879 {
8880     ChessMove moveType;
8881     int fromX, fromY, toX, toY, boardIndex;
8882     char promoChar;
8883     char *p, *q;
8884     char buf[MSG_SIZ];
8885
8886     if (appData.debugMode)
8887       fprintf(debugFP, "Parsing game history: %s\n", game);
8888
8889     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8890     gameInfo.site = StrSave(appData.icsHost);
8891     gameInfo.date = PGNDate();
8892     gameInfo.round = StrSave("-");
8893
8894     /* Parse out names of players */
8895     while (*game == ' ') game++;
8896     p = buf;
8897     while (*game != ' ') *p++ = *game++;
8898     *p = NULLCHAR;
8899     gameInfo.white = StrSave(buf);
8900     while (*game == ' ') game++;
8901     p = buf;
8902     while (*game != ' ' && *game != '\n') *p++ = *game++;
8903     *p = NULLCHAR;
8904     gameInfo.black = StrSave(buf);
8905
8906     /* Parse moves */
8907     boardIndex = blackPlaysFirst ? 1 : 0;
8908     yynewstr(game);
8909     for (;;) {
8910         yyboardindex = boardIndex;
8911         moveType = (ChessMove) Myylex();
8912         switch (moveType) {
8913           case IllegalMove:             /* maybe suicide chess, etc. */
8914   if (appData.debugMode) {
8915     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8916     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8917     setbuf(debugFP, NULL);
8918   }
8919           case WhitePromotion:
8920           case BlackPromotion:
8921           case WhiteNonPromotion:
8922           case BlackNonPromotion:
8923           case NormalMove:
8924           case WhiteCapturesEnPassant:
8925           case BlackCapturesEnPassant:
8926           case WhiteKingSideCastle:
8927           case WhiteQueenSideCastle:
8928           case BlackKingSideCastle:
8929           case BlackQueenSideCastle:
8930           case WhiteKingSideCastleWild:
8931           case WhiteQueenSideCastleWild:
8932           case BlackKingSideCastleWild:
8933           case BlackQueenSideCastleWild:
8934           /* PUSH Fabien */
8935           case WhiteHSideCastleFR:
8936           case WhiteASideCastleFR:
8937           case BlackHSideCastleFR:
8938           case BlackASideCastleFR:
8939           /* POP Fabien */
8940             fromX = currentMoveString[0] - AAA;
8941             fromY = currentMoveString[1] - ONE;
8942             toX = currentMoveString[2] - AAA;
8943             toY = currentMoveString[3] - ONE;
8944             promoChar = currentMoveString[4];
8945             break;
8946           case WhiteDrop:
8947           case BlackDrop:
8948             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8949             fromX = moveType == WhiteDrop ?
8950               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8951             (int) CharToPiece(ToLower(currentMoveString[0]));
8952             fromY = DROP_RANK;
8953             toX = currentMoveString[2] - AAA;
8954             toY = currentMoveString[3] - ONE;
8955             promoChar = NULLCHAR;
8956             break;
8957           case AmbiguousMove:
8958             /* bug? */
8959             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8960   if (appData.debugMode) {
8961     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8962     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8963     setbuf(debugFP, NULL);
8964   }
8965             DisplayError(buf, 0);
8966             return;
8967           case ImpossibleMove:
8968             /* bug? */
8969             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8970   if (appData.debugMode) {
8971     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8972     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8973     setbuf(debugFP, NULL);
8974   }
8975             DisplayError(buf, 0);
8976             return;
8977           case EndOfFile:
8978             if (boardIndex < backwardMostMove) {
8979                 /* Oops, gap.  How did that happen? */
8980                 DisplayError(_("Gap in move list"), 0);
8981                 return;
8982             }
8983             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8984             if (boardIndex > forwardMostMove) {
8985                 forwardMostMove = boardIndex;
8986             }
8987             return;
8988           case ElapsedTime:
8989             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8990                 strcat(parseList[boardIndex-1], " ");
8991                 strcat(parseList[boardIndex-1], yy_text);
8992             }
8993             continue;
8994           case Comment:
8995           case PGNTag:
8996           case NAG:
8997           default:
8998             /* ignore */
8999             continue;
9000           case WhiteWins:
9001           case BlackWins:
9002           case GameIsDrawn:
9003           case GameUnfinished:
9004             if (gameMode == IcsExamining) {
9005                 if (boardIndex < backwardMostMove) {
9006                     /* Oops, gap.  How did that happen? */
9007                     return;
9008                 }
9009                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9010                 return;
9011             }
9012             gameInfo.result = moveType;
9013             p = strchr(yy_text, '{');
9014             if (p == NULL) p = strchr(yy_text, '(');
9015             if (p == NULL) {
9016                 p = yy_text;
9017                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9018             } else {
9019                 q = strchr(p, *p == '{' ? '}' : ')');
9020                 if (q != NULL) *q = NULLCHAR;
9021                 p++;
9022             }
9023             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9024             gameInfo.resultDetails = StrSave(p);
9025             continue;
9026         }
9027         if (boardIndex >= forwardMostMove &&
9028             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9029             backwardMostMove = blackPlaysFirst ? 1 : 0;
9030             return;
9031         }
9032         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9033                                  fromY, fromX, toY, toX, promoChar,
9034                                  parseList[boardIndex]);
9035         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9036         /* currentMoveString is set as a side-effect of yylex */
9037         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9038         strcat(moveList[boardIndex], "\n");
9039         boardIndex++;
9040         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9041         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9042           case MT_NONE:
9043           case MT_STALEMATE:
9044           default:
9045             break;
9046           case MT_CHECK:
9047             if(gameInfo.variant != VariantShogi)
9048                 strcat(parseList[boardIndex - 1], "+");
9049             break;
9050           case MT_CHECKMATE:
9051           case MT_STAINMATE:
9052             strcat(parseList[boardIndex - 1], "#");
9053             break;
9054         }
9055     }
9056 }
9057
9058
9059 /* Apply a move to the given board  */
9060 void
9061 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9062      int fromX, fromY, toX, toY;
9063      int promoChar;
9064      Board board;
9065 {
9066   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9067   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9068
9069     /* [HGM] compute & store e.p. status and castling rights for new position */
9070     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9071
9072       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9073       oldEP = (signed char)board[EP_STATUS];
9074       board[EP_STATUS] = EP_NONE;
9075
9076   if (fromY == DROP_RANK) {
9077         /* must be first */
9078         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9079             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9080             return;
9081         }
9082         piece = board[toY][toX] = (ChessSquare) fromX;
9083   } else {
9084       int i;
9085
9086       if( board[toY][toX] != EmptySquare )
9087            board[EP_STATUS] = EP_CAPTURE;
9088
9089       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9090            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9091                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9092       } else
9093       if( board[fromY][fromX] == WhitePawn ) {
9094            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9095                board[EP_STATUS] = EP_PAWN_MOVE;
9096            if( toY-fromY==2) {
9097                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9098                         gameInfo.variant != VariantBerolina || toX < fromX)
9099                       board[EP_STATUS] = toX | berolina;
9100                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9101                         gameInfo.variant != VariantBerolina || toX > fromX)
9102                       board[EP_STATUS] = toX;
9103            }
9104       } else
9105       if( board[fromY][fromX] == BlackPawn ) {
9106            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9107                board[EP_STATUS] = EP_PAWN_MOVE;
9108            if( toY-fromY== -2) {
9109                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9110                         gameInfo.variant != VariantBerolina || toX < fromX)
9111                       board[EP_STATUS] = toX | berolina;
9112                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9113                         gameInfo.variant != VariantBerolina || toX > fromX)
9114                       board[EP_STATUS] = toX;
9115            }
9116        }
9117
9118        for(i=0; i<nrCastlingRights; i++) {
9119            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9120               board[CASTLING][i] == toX   && castlingRank[i] == toY
9121              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9122        }
9123
9124      if (fromX == toX && fromY == toY) return;
9125
9126      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9127      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9128      if(gameInfo.variant == VariantKnightmate)
9129          king += (int) WhiteUnicorn - (int) WhiteKing;
9130
9131     /* Code added by Tord: */
9132     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9133     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9134         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9135       board[fromY][fromX] = EmptySquare;
9136       board[toY][toX] = EmptySquare;
9137       if((toX > fromX) != (piece == WhiteRook)) {
9138         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9139       } else {
9140         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9141       }
9142     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9143                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9144       board[fromY][fromX] = EmptySquare;
9145       board[toY][toX] = EmptySquare;
9146       if((toX > fromX) != (piece == BlackRook)) {
9147         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9148       } else {
9149         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9150       }
9151     /* End of code added by Tord */
9152
9153     } else if (board[fromY][fromX] == king
9154         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9155         && toY == fromY && toX > fromX+1) {
9156         board[fromY][fromX] = EmptySquare;
9157         board[toY][toX] = king;
9158         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9159         board[fromY][BOARD_RGHT-1] = EmptySquare;
9160     } else if (board[fromY][fromX] == king
9161         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9162                && toY == fromY && toX < fromX-1) {
9163         board[fromY][fromX] = EmptySquare;
9164         board[toY][toX] = king;
9165         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9166         board[fromY][BOARD_LEFT] = EmptySquare;
9167     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9168                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9169                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9170                ) {
9171         /* white pawn promotion */
9172         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9173         if(gameInfo.variant==VariantBughouse ||
9174            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9175             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9176         board[fromY][fromX] = EmptySquare;
9177     } else if ((fromY >= BOARD_HEIGHT>>1)
9178                && (toX != fromX)
9179                && gameInfo.variant != VariantXiangqi
9180                && gameInfo.variant != VariantBerolina
9181                && (board[fromY][fromX] == WhitePawn)
9182                && (board[toY][toX] == EmptySquare)) {
9183         board[fromY][fromX] = EmptySquare;
9184         board[toY][toX] = WhitePawn;
9185         captured = board[toY - 1][toX];
9186         board[toY - 1][toX] = EmptySquare;
9187     } else if ((fromY == BOARD_HEIGHT-4)
9188                && (toX == fromX)
9189                && gameInfo.variant == VariantBerolina
9190                && (board[fromY][fromX] == WhitePawn)
9191                && (board[toY][toX] == EmptySquare)) {
9192         board[fromY][fromX] = EmptySquare;
9193         board[toY][toX] = WhitePawn;
9194         if(oldEP & EP_BEROLIN_A) {
9195                 captured = board[fromY][fromX-1];
9196                 board[fromY][fromX-1] = EmptySquare;
9197         }else{  captured = board[fromY][fromX+1];
9198                 board[fromY][fromX+1] = EmptySquare;
9199         }
9200     } else if (board[fromY][fromX] == king
9201         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9202                && toY == fromY && toX > fromX+1) {
9203         board[fromY][fromX] = EmptySquare;
9204         board[toY][toX] = king;
9205         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9206         board[fromY][BOARD_RGHT-1] = EmptySquare;
9207     } else if (board[fromY][fromX] == king
9208         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9209                && toY == fromY && toX < fromX-1) {
9210         board[fromY][fromX] = EmptySquare;
9211         board[toY][toX] = king;
9212         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9213         board[fromY][BOARD_LEFT] = EmptySquare;
9214     } else if (fromY == 7 && fromX == 3
9215                && board[fromY][fromX] == BlackKing
9216                && toY == 7 && toX == 5) {
9217         board[fromY][fromX] = EmptySquare;
9218         board[toY][toX] = BlackKing;
9219         board[fromY][7] = EmptySquare;
9220         board[toY][4] = BlackRook;
9221     } else if (fromY == 7 && fromX == 3
9222                && board[fromY][fromX] == BlackKing
9223                && toY == 7 && toX == 1) {
9224         board[fromY][fromX] = EmptySquare;
9225         board[toY][toX] = BlackKing;
9226         board[fromY][0] = EmptySquare;
9227         board[toY][2] = BlackRook;
9228     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9229                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9230                && toY < promoRank && promoChar
9231                ) {
9232         /* black pawn promotion */
9233         board[toY][toX] = CharToPiece(ToLower(promoChar));
9234         if(gameInfo.variant==VariantBughouse ||
9235            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9236             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9237         board[fromY][fromX] = EmptySquare;
9238     } else if ((fromY < BOARD_HEIGHT>>1)
9239                && (toX != fromX)
9240                && gameInfo.variant != VariantXiangqi
9241                && gameInfo.variant != VariantBerolina
9242                && (board[fromY][fromX] == BlackPawn)
9243                && (board[toY][toX] == EmptySquare)) {
9244         board[fromY][fromX] = EmptySquare;
9245         board[toY][toX] = BlackPawn;
9246         captured = board[toY + 1][toX];
9247         board[toY + 1][toX] = EmptySquare;
9248     } else if ((fromY == 3)
9249                && (toX == fromX)
9250                && gameInfo.variant == VariantBerolina
9251                && (board[fromY][fromX] == BlackPawn)
9252                && (board[toY][toX] == EmptySquare)) {
9253         board[fromY][fromX] = EmptySquare;
9254         board[toY][toX] = BlackPawn;
9255         if(oldEP & EP_BEROLIN_A) {
9256                 captured = board[fromY][fromX-1];
9257                 board[fromY][fromX-1] = EmptySquare;
9258         }else{  captured = board[fromY][fromX+1];
9259                 board[fromY][fromX+1] = EmptySquare;
9260         }
9261     } else {
9262         board[toY][toX] = board[fromY][fromX];
9263         board[fromY][fromX] = EmptySquare;
9264     }
9265   }
9266
9267     if (gameInfo.holdingsWidth != 0) {
9268
9269       /* !!A lot more code needs to be written to support holdings  */
9270       /* [HGM] OK, so I have written it. Holdings are stored in the */
9271       /* penultimate board files, so they are automaticlly stored   */
9272       /* in the game history.                                       */
9273       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9274                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9275         /* Delete from holdings, by decreasing count */
9276         /* and erasing image if necessary            */
9277         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9278         if(p < (int) BlackPawn) { /* white drop */
9279              p -= (int)WhitePawn;
9280                  p = PieceToNumber((ChessSquare)p);
9281              if(p >= gameInfo.holdingsSize) p = 0;
9282              if(--board[p][BOARD_WIDTH-2] <= 0)
9283                   board[p][BOARD_WIDTH-1] = EmptySquare;
9284              if((int)board[p][BOARD_WIDTH-2] < 0)
9285                         board[p][BOARD_WIDTH-2] = 0;
9286         } else {                  /* black drop */
9287              p -= (int)BlackPawn;
9288                  p = PieceToNumber((ChessSquare)p);
9289              if(p >= gameInfo.holdingsSize) p = 0;
9290              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9291                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9292              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9293                         board[BOARD_HEIGHT-1-p][1] = 0;
9294         }
9295       }
9296       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9297           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9298         /* [HGM] holdings: Add to holdings, if holdings exist */
9299         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9300                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9301                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9302         }
9303         p = (int) captured;
9304         if (p >= (int) BlackPawn) {
9305           p -= (int)BlackPawn;
9306           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9307                   /* in Shogi restore piece to its original  first */
9308                   captured = (ChessSquare) (DEMOTED captured);
9309                   p = DEMOTED p;
9310           }
9311           p = PieceToNumber((ChessSquare)p);
9312           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9313           board[p][BOARD_WIDTH-2]++;
9314           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9315         } else {
9316           p -= (int)WhitePawn;
9317           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9318                   captured = (ChessSquare) (DEMOTED captured);
9319                   p = DEMOTED p;
9320           }
9321           p = PieceToNumber((ChessSquare)p);
9322           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9323           board[BOARD_HEIGHT-1-p][1]++;
9324           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9325         }
9326       }
9327     } else if (gameInfo.variant == VariantAtomic) {
9328       if (captured != EmptySquare) {
9329         int y, x;
9330         for (y = toY-1; y <= toY+1; y++) {
9331           for (x = toX-1; x <= toX+1; x++) {
9332             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9333                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9334               board[y][x] = EmptySquare;
9335             }
9336           }
9337         }
9338         board[toY][toX] = EmptySquare;
9339       }
9340     }
9341     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9342         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9343     } else
9344     if(promoChar == '+') {
9345         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9346         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9347     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9348         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9349     }
9350     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9351                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9352         // [HGM] superchess: take promotion piece out of holdings
9353         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9354         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9355             if(!--board[k][BOARD_WIDTH-2])
9356                 board[k][BOARD_WIDTH-1] = EmptySquare;
9357         } else {
9358             if(!--board[BOARD_HEIGHT-1-k][1])
9359                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9360         }
9361     }
9362
9363 }
9364
9365 /* Updates forwardMostMove */
9366 void
9367 MakeMove(fromX, fromY, toX, toY, promoChar)
9368      int fromX, fromY, toX, toY;
9369      int promoChar;
9370 {
9371 //    forwardMostMove++; // [HGM] bare: moved downstream
9372
9373     (void) CoordsToAlgebraic(boards[forwardMostMove],
9374                              PosFlags(forwardMostMove),
9375                              fromY, fromX, toY, toX, promoChar,
9376                              parseList[forwardMostMove]);
9377
9378     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9379         int timeLeft; static int lastLoadFlag=0; int king, piece;
9380         piece = boards[forwardMostMove][fromY][fromX];
9381         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9382         if(gameInfo.variant == VariantKnightmate)
9383             king += (int) WhiteUnicorn - (int) WhiteKing;
9384         if(forwardMostMove == 0) {
9385             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9386                 fprintf(serverMoves, "%s;", UserName());
9387             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9388                 fprintf(serverMoves, "%s;", second.tidy);
9389             fprintf(serverMoves, "%s;", first.tidy);
9390             if(gameMode == MachinePlaysWhite)
9391                 fprintf(serverMoves, "%s;", UserName());
9392             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9393                 fprintf(serverMoves, "%s;", second.tidy);
9394         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9395         lastLoadFlag = loadFlag;
9396         // print base move
9397         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9398         // print castling suffix
9399         if( toY == fromY && piece == king ) {
9400             if(toX-fromX > 1)
9401                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9402             if(fromX-toX >1)
9403                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9404         }
9405         // e.p. suffix
9406         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9407              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9408              boards[forwardMostMove][toY][toX] == EmptySquare
9409              && fromX != toX && fromY != toY)
9410                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9411         // promotion suffix
9412         if(promoChar != NULLCHAR)
9413                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9414         if(!loadFlag) {
9415                 char buf[MOVE_LEN*2], *p; int len;
9416             fprintf(serverMoves, "/%d/%d",
9417                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9418             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9419             else                      timeLeft = blackTimeRemaining/1000;
9420             fprintf(serverMoves, "/%d", timeLeft);
9421                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9422                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9423                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9424             fprintf(serverMoves, "/%s", buf);
9425         }
9426         fflush(serverMoves);
9427     }
9428
9429     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9430         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9431       return;
9432     }
9433     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9434     if (commentList[forwardMostMove+1] != NULL) {
9435         free(commentList[forwardMostMove+1]);
9436         commentList[forwardMostMove+1] = NULL;
9437     }
9438     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9439     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9440     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9441     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9442     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9443     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9444     adjustedClock = FALSE;
9445     gameInfo.result = GameUnfinished;
9446     if (gameInfo.resultDetails != NULL) {
9447         free(gameInfo.resultDetails);
9448         gameInfo.resultDetails = NULL;
9449     }
9450     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9451                               moveList[forwardMostMove - 1]);
9452     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9453       case MT_NONE:
9454       case MT_STALEMATE:
9455       default:
9456         break;
9457       case MT_CHECK:
9458         if(gameInfo.variant != VariantShogi)
9459             strcat(parseList[forwardMostMove - 1], "+");
9460         break;
9461       case MT_CHECKMATE:
9462       case MT_STAINMATE:
9463         strcat(parseList[forwardMostMove - 1], "#");
9464         break;
9465     }
9466     if (appData.debugMode) {
9467         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9468     }
9469
9470 }
9471
9472 /* Updates currentMove if not pausing */
9473 void
9474 ShowMove(fromX, fromY, toX, toY)
9475 {
9476     int instant = (gameMode == PlayFromGameFile) ?
9477         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9478     if(appData.noGUI) return;
9479     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9480         if (!instant) {
9481             if (forwardMostMove == currentMove + 1) {
9482                 AnimateMove(boards[forwardMostMove - 1],
9483                             fromX, fromY, toX, toY);
9484             }
9485             if (appData.highlightLastMove) {
9486                 SetHighlights(fromX, fromY, toX, toY);
9487             }
9488         }
9489         currentMove = forwardMostMove;
9490     }
9491
9492     if (instant) return;
9493
9494     DisplayMove(currentMove - 1);
9495     DrawPosition(FALSE, boards[currentMove]);
9496     DisplayBothClocks();
9497     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9498 }
9499
9500 void SendEgtPath(ChessProgramState *cps)
9501 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9502         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9503
9504         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9505
9506         while(*p) {
9507             char c, *q = name+1, *r, *s;
9508
9509             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9510             while(*p && *p != ',') *q++ = *p++;
9511             *q++ = ':'; *q = 0;
9512             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9513                 strcmp(name, ",nalimov:") == 0 ) {
9514                 // take nalimov path from the menu-changeable option first, if it is defined
9515               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9516                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9517             } else
9518             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9519                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9520                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9521                 s = r = StrStr(s, ":") + 1; // beginning of path info
9522                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9523                 c = *r; *r = 0;             // temporarily null-terminate path info
9524                     *--q = 0;               // strip of trailig ':' from name
9525                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9526                 *r = c;
9527                 SendToProgram(buf,cps);     // send egtbpath command for this format
9528             }
9529             if(*p == ',') p++; // read away comma to position for next format name
9530         }
9531 }
9532
9533 void
9534 InitChessProgram(cps, setup)
9535      ChessProgramState *cps;
9536      int setup; /* [HGM] needed to setup FRC opening position */
9537 {
9538     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9539     if (appData.noChessProgram) return;
9540     hintRequested = FALSE;
9541     bookRequested = FALSE;
9542
9543     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9544     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9545     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9546     if(cps->memSize) { /* [HGM] memory */
9547       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9548         SendToProgram(buf, cps);
9549     }
9550     SendEgtPath(cps); /* [HGM] EGT */
9551     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9552       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9553         SendToProgram(buf, cps);
9554     }
9555
9556     SendToProgram(cps->initString, cps);
9557     if (gameInfo.variant != VariantNormal &&
9558         gameInfo.variant != VariantLoadable
9559         /* [HGM] also send variant if board size non-standard */
9560         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9561                                             ) {
9562       char *v = VariantName(gameInfo.variant);
9563       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9564         /* [HGM] in protocol 1 we have to assume all variants valid */
9565         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9566         DisplayFatalError(buf, 0, 1);
9567         return;
9568       }
9569
9570       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9571       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9572       if( gameInfo.variant == VariantXiangqi )
9573            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9574       if( gameInfo.variant == VariantShogi )
9575            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9576       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9577            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9578       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9579           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9580            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9581       if( gameInfo.variant == VariantCourier )
9582            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9583       if( gameInfo.variant == VariantSuper )
9584            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9585       if( gameInfo.variant == VariantGreat )
9586            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9587       if( gameInfo.variant == VariantSChess )
9588            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9589       if( gameInfo.variant == VariantGrand )
9590            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9591
9592       if(overruled) {
9593         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9594                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9595            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9596            if(StrStr(cps->variants, b) == NULL) {
9597                // specific sized variant not known, check if general sizing allowed
9598                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9599                    if(StrStr(cps->variants, "boardsize") == NULL) {
9600                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9601                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9602                        DisplayFatalError(buf, 0, 1);
9603                        return;
9604                    }
9605                    /* [HGM] here we really should compare with the maximum supported board size */
9606                }
9607            }
9608       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9609       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9610       SendToProgram(buf, cps);
9611     }
9612     currentlyInitializedVariant = gameInfo.variant;
9613
9614     /* [HGM] send opening position in FRC to first engine */
9615     if(setup) {
9616           SendToProgram("force\n", cps);
9617           SendBoard(cps, 0);
9618           /* engine is now in force mode! Set flag to wake it up after first move. */
9619           setboardSpoiledMachineBlack = 1;
9620     }
9621
9622     if (cps->sendICS) {
9623       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9624       SendToProgram(buf, cps);
9625     }
9626     cps->maybeThinking = FALSE;
9627     cps->offeredDraw = 0;
9628     if (!appData.icsActive) {
9629         SendTimeControl(cps, movesPerSession, timeControl,
9630                         timeIncrement, appData.searchDepth,
9631                         searchTime);
9632     }
9633     if (appData.showThinking
9634         // [HGM] thinking: four options require thinking output to be sent
9635         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9636                                 ) {
9637         SendToProgram("post\n", cps);
9638     }
9639     SendToProgram("hard\n", cps);
9640     if (!appData.ponderNextMove) {
9641         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9642            it without being sure what state we are in first.  "hard"
9643            is not a toggle, so that one is OK.
9644          */
9645         SendToProgram("easy\n", cps);
9646     }
9647     if (cps->usePing) {
9648       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9649       SendToProgram(buf, cps);
9650     }
9651     cps->initDone = TRUE;
9652     ClearEngineOutputPane(cps == &second);
9653 }
9654
9655
9656 void
9657 StartChessProgram(cps)
9658      ChessProgramState *cps;
9659 {
9660     char buf[MSG_SIZ];
9661     int err;
9662
9663     if (appData.noChessProgram) return;
9664     cps->initDone = FALSE;
9665
9666     if (strcmp(cps->host, "localhost") == 0) {
9667         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9668     } else if (*appData.remoteShell == NULLCHAR) {
9669         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9670     } else {
9671         if (*appData.remoteUser == NULLCHAR) {
9672           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9673                     cps->program);
9674         } else {
9675           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9676                     cps->host, appData.remoteUser, cps->program);
9677         }
9678         err = StartChildProcess(buf, "", &cps->pr);
9679     }
9680
9681     if (err != 0) {
9682       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9683         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9684         if(cps != &first) return;
9685         appData.noChessProgram = TRUE;
9686         ThawUI();
9687         SetNCPMode();
9688 //      DisplayFatalError(buf, err, 1);
9689 //      cps->pr = NoProc;
9690 //      cps->isr = NULL;
9691         return;
9692     }
9693
9694     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9695     if (cps->protocolVersion > 1) {
9696       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9697       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9698       cps->comboCnt = 0;  //                and values of combo boxes
9699       SendToProgram(buf, cps);
9700     } else {
9701       SendToProgram("xboard\n", cps);
9702     }
9703 }
9704
9705 void
9706 TwoMachinesEventIfReady P((void))
9707 {
9708   static int curMess = 0;
9709   if (first.lastPing != first.lastPong) {
9710     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9711     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9712     return;
9713   }
9714   if (second.lastPing != second.lastPong) {
9715     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9716     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9717     return;
9718   }
9719   DisplayMessage("", ""); curMess = 0;
9720   ThawUI();
9721   TwoMachinesEvent();
9722 }
9723
9724 char *
9725 MakeName(char *template)
9726 {
9727     time_t clock;
9728     struct tm *tm;
9729     static char buf[MSG_SIZ];
9730     char *p = buf;
9731     int i;
9732
9733     clock = time((time_t *)NULL);
9734     tm = localtime(&clock);
9735
9736     while(*p++ = *template++) if(p[-1] == '%') {
9737         switch(*template++) {
9738           case 0:   *p = 0; return buf;
9739           case 'Y': i = tm->tm_year+1900; break;
9740           case 'y': i = tm->tm_year-100; break;
9741           case 'M': i = tm->tm_mon+1; break;
9742           case 'd': i = tm->tm_mday; break;
9743           case 'h': i = tm->tm_hour; break;
9744           case 'm': i = tm->tm_min; break;
9745           case 's': i = tm->tm_sec; break;
9746           default:  i = 0;
9747         }
9748         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9749     }
9750     return buf;
9751 }
9752
9753 int
9754 CountPlayers(char *p)
9755 {
9756     int n = 0;
9757     while(p = strchr(p, '\n')) p++, n++; // count participants
9758     return n;
9759 }
9760
9761 FILE *
9762 WriteTourneyFile(char *results, FILE *f)
9763 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9764     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9765     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9766         // create a file with tournament description
9767         fprintf(f, "-participants {%s}\n", appData.participants);
9768         fprintf(f, "-seedBase %d\n", appData.seedBase);
9769         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9770         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9771         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9772         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9773         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9774         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9775         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9776         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9777         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9778         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9779         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9780         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9781         if(searchTime > 0)
9782                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9783         else {
9784                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9785                 fprintf(f, "-tc %s\n", appData.timeControl);
9786                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9787         }
9788         fprintf(f, "-results \"%s\"\n", results);
9789     }
9790     return f;
9791 }
9792
9793 #define MAXENGINES 1000
9794 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9795
9796 void Substitute(char *participants, int expunge)
9797 {
9798     int i, changed, changes=0, nPlayers=0;
9799     char *p, *q, *r, buf[MSG_SIZ];
9800     if(participants == NULL) return;
9801     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9802     r = p = participants; q = appData.participants;
9803     while(*p && *p == *q) {
9804         if(*p == '\n') r = p+1, nPlayers++;
9805         p++; q++;
9806     }
9807     if(*p) { // difference
9808         while(*p && *p++ != '\n');
9809         while(*q && *q++ != '\n');
9810       changed = nPlayers;
9811         changes = 1 + (strcmp(p, q) != 0);
9812     }
9813     if(changes == 1) { // a single engine mnemonic was changed
9814         q = r; while(*q) nPlayers += (*q++ == '\n');
9815         p = buf; while(*r && (*p = *r++) != '\n') p++;
9816         *p = NULLCHAR;
9817         NamesToList(firstChessProgramNames, command, mnemonic);
9818         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9819         if(mnemonic[i]) { // The substitute is valid
9820             FILE *f;
9821             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9822                 flock(fileno(f), LOCK_EX);
9823                 ParseArgsFromFile(f);
9824                 fseek(f, 0, SEEK_SET);
9825                 FREE(appData.participants); appData.participants = participants;
9826                 if(expunge) { // erase results of replaced engine
9827                     int len = strlen(appData.results), w, b, dummy;
9828                     for(i=0; i<len; i++) {
9829                         Pairing(i, nPlayers, &w, &b, &dummy);
9830                         if((w == changed || b == changed) && appData.results[i] == '*') {
9831                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9832                             fclose(f);
9833                             return;
9834                         }
9835                     }
9836                     for(i=0; i<len; i++) {
9837                         Pairing(i, nPlayers, &w, &b, &dummy);
9838                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9839                     }
9840                 }
9841                 WriteTourneyFile(appData.results, f);
9842                 fclose(f); // release lock
9843                 return;
9844             }
9845         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9846     }
9847     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9848     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9849     free(participants);
9850     return;
9851 }
9852
9853 int
9854 CreateTourney(char *name)
9855 {
9856         FILE *f;
9857         if(matchMode && strcmp(name, appData.tourneyFile)) {
9858              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9859         }
9860         if(name[0] == NULLCHAR) {
9861             if(appData.participants[0])
9862                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9863             return 0;
9864         }
9865         f = fopen(name, "r");
9866         if(f) { // file exists
9867             ASSIGN(appData.tourneyFile, name);
9868             ParseArgsFromFile(f); // parse it
9869         } else {
9870             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9871             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9872                 DisplayError(_("Not enough participants"), 0);
9873                 return 0;
9874             }
9875             ASSIGN(appData.tourneyFile, name);
9876             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9877             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9878         }
9879         fclose(f);
9880         appData.noChessProgram = FALSE;
9881         appData.clockMode = TRUE;
9882         SetGNUMode();
9883         return 1;
9884 }
9885
9886 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9887 {
9888     char buf[MSG_SIZ], *p, *q;
9889     int i=1;
9890     while(*names) {
9891         p = names; q = buf;
9892         while(*p && *p != '\n') *q++ = *p++;
9893         *q = 0;
9894         if(engineList[i]) free(engineList[i]);
9895         engineList[i] = strdup(buf);
9896         if(*p == '\n') p++;
9897         TidyProgramName(engineList[i], "localhost", buf);
9898         if(engineMnemonic[i]) free(engineMnemonic[i]);
9899         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9900             strcat(buf, " (");
9901             sscanf(q + 8, "%s", buf + strlen(buf));
9902             strcat(buf, ")");
9903         }
9904         engineMnemonic[i] = strdup(buf);
9905         names = p; i++;
9906       if(i > MAXENGINES - 2) break;
9907     }
9908     engineList[i] = engineMnemonic[i] = NULL;
9909 }
9910
9911 // following implemented as macro to avoid type limitations
9912 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9913
9914 void SwapEngines(int n)
9915 {   // swap settings for first engine and other engine (so far only some selected options)
9916     int h;
9917     char *p;
9918     if(n == 0) return;
9919     SWAP(directory, p)
9920     SWAP(chessProgram, p)
9921     SWAP(isUCI, h)
9922     SWAP(hasOwnBookUCI, h)
9923     SWAP(protocolVersion, h)
9924     SWAP(reuse, h)
9925     SWAP(scoreIsAbsolute, h)
9926     SWAP(timeOdds, h)
9927     SWAP(logo, p)
9928     SWAP(pgnName, p)
9929     SWAP(pvSAN, h)
9930     SWAP(engOptions, p)
9931 }
9932
9933 void
9934 SetPlayer(int player)
9935 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9936     int i;
9937     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9938     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9939     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9940     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9941     if(mnemonic[i]) {
9942         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9943         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9944         appData.firstHasOwnBookUCI = !appData.defNoBook;
9945         ParseArgsFromString(buf);
9946     }
9947     free(engineName);
9948 }
9949
9950 int
9951 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9952 {   // determine players from game number
9953     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9954
9955     if(appData.tourneyType == 0) {
9956         roundsPerCycle = (nPlayers - 1) | 1;
9957         pairingsPerRound = nPlayers / 2;
9958     } else if(appData.tourneyType > 0) {
9959         roundsPerCycle = nPlayers - appData.tourneyType;
9960         pairingsPerRound = appData.tourneyType;
9961     }
9962     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9963     gamesPerCycle = gamesPerRound * roundsPerCycle;
9964     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9965     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9966     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9967     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9968     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9969     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9970
9971     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9972     if(appData.roundSync) *syncInterval = gamesPerRound;
9973
9974     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9975
9976     if(appData.tourneyType == 0) {
9977         if(curPairing == (nPlayers-1)/2 ) {
9978             *whitePlayer = curRound;
9979             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9980         } else {
9981             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9982             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9983             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9984             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9985         }
9986     } else if(appData.tourneyType > 0) {
9987         *whitePlayer = curPairing;
9988         *blackPlayer = curRound + appData.tourneyType;
9989     }
9990
9991     // take care of white/black alternation per round. 
9992     // For cycles and games this is already taken care of by default, derived from matchGame!
9993     return curRound & 1;
9994 }
9995
9996 int
9997 NextTourneyGame(int nr, int *swapColors)
9998 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9999     char *p, *q;
10000     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10001     FILE *tf;
10002     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10003     tf = fopen(appData.tourneyFile, "r");
10004     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10005     ParseArgsFromFile(tf); fclose(tf);
10006     InitTimeControls(); // TC might be altered from tourney file
10007
10008     nPlayers = CountPlayers(appData.participants); // count participants
10009     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10010     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10011
10012     if(syncInterval) {
10013         p = q = appData.results;
10014         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10015         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10016             DisplayMessage(_("Waiting for other game(s)"),"");
10017             waitingForGame = TRUE;
10018             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10019             return 0;
10020         }
10021         waitingForGame = FALSE;
10022     }
10023
10024     if(appData.tourneyType < 0) {
10025         if(nr>=0 && !pairingReceived) {
10026             char buf[1<<16];
10027             if(pairing.pr == NoProc) {
10028                 if(!appData.pairingEngine[0]) {
10029                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10030                     return 0;
10031                 }
10032                 StartChessProgram(&pairing); // starts the pairing engine
10033             }
10034             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10035             SendToProgram(buf, &pairing);
10036             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10037             SendToProgram(buf, &pairing);
10038             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10039         }
10040         pairingReceived = 0;                              // ... so we continue here 
10041         *swapColors = 0;
10042         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10043         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10044         matchGame = 1; roundNr = nr / syncInterval + 1;
10045     }
10046
10047     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10048
10049     // redefine engines, engine dir, etc.
10050     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10051     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10052     SwapEngines(1);
10053     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10054     SwapEngines(1);         // and make that valid for second engine by swapping
10055     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10056     InitEngine(&second, 1);
10057     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10058     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10059     return 1;
10060 }
10061
10062 void
10063 NextMatchGame()
10064 {   // performs game initialization that does not invoke engines, and then tries to start the game
10065     int res, firstWhite, swapColors = 0;
10066     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10067     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10068     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10069     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10070     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10071     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10072     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10073     Reset(FALSE, first.pr != NoProc);
10074     res = LoadGameOrPosition(matchGame); // setup game
10075     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10076     if(!res) return; // abort when bad game/pos file
10077     TwoMachinesEvent();
10078 }
10079
10080 void UserAdjudicationEvent( int result )
10081 {
10082     ChessMove gameResult = GameIsDrawn;
10083
10084     if( result > 0 ) {
10085         gameResult = WhiteWins;
10086     }
10087     else if( result < 0 ) {
10088         gameResult = BlackWins;
10089     }
10090
10091     if( gameMode == TwoMachinesPlay ) {
10092         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10093     }
10094 }
10095
10096
10097 // [HGM] save: calculate checksum of game to make games easily identifiable
10098 int StringCheckSum(char *s)
10099 {
10100         int i = 0;
10101         if(s==NULL) return 0;
10102         while(*s) i = i*259 + *s++;
10103         return i;
10104 }
10105
10106 int GameCheckSum()
10107 {
10108         int i, sum=0;
10109         for(i=backwardMostMove; i<forwardMostMove; i++) {
10110                 sum += pvInfoList[i].depth;
10111                 sum += StringCheckSum(parseList[i]);
10112                 sum += StringCheckSum(commentList[i]);
10113                 sum *= 261;
10114         }
10115         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10116         return sum + StringCheckSum(commentList[i]);
10117 } // end of save patch
10118
10119 void
10120 GameEnds(result, resultDetails, whosays)
10121      ChessMove result;
10122      char *resultDetails;
10123      int whosays;
10124 {
10125     GameMode nextGameMode;
10126     int isIcsGame;
10127     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10128
10129     if(endingGame) return; /* [HGM] crash: forbid recursion */
10130     endingGame = 1;
10131     if(twoBoards) { // [HGM] dual: switch back to one board
10132         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10133         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10134     }
10135     if (appData.debugMode) {
10136       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10137               result, resultDetails ? resultDetails : "(null)", whosays);
10138     }
10139
10140     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10141
10142     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10143         /* If we are playing on ICS, the server decides when the
10144            game is over, but the engine can offer to draw, claim
10145            a draw, or resign.
10146          */
10147 #if ZIPPY
10148         if (appData.zippyPlay && first.initDone) {
10149             if (result == GameIsDrawn) {
10150                 /* In case draw still needs to be claimed */
10151                 SendToICS(ics_prefix);
10152                 SendToICS("draw\n");
10153             } else if (StrCaseStr(resultDetails, "resign")) {
10154                 SendToICS(ics_prefix);
10155                 SendToICS("resign\n");
10156             }
10157         }
10158 #endif
10159         endingGame = 0; /* [HGM] crash */
10160         return;
10161     }
10162
10163     /* If we're loading the game from a file, stop */
10164     if (whosays == GE_FILE) {
10165       (void) StopLoadGameTimer();
10166       gameFileFP = NULL;
10167     }
10168
10169     /* Cancel draw offers */
10170     first.offeredDraw = second.offeredDraw = 0;
10171
10172     /* If this is an ICS game, only ICS can really say it's done;
10173        if not, anyone can. */
10174     isIcsGame = (gameMode == IcsPlayingWhite ||
10175                  gameMode == IcsPlayingBlack ||
10176                  gameMode == IcsObserving    ||
10177                  gameMode == IcsExamining);
10178
10179     if (!isIcsGame || whosays == GE_ICS) {
10180         /* OK -- not an ICS game, or ICS said it was done */
10181         StopClocks();
10182         if (!isIcsGame && !appData.noChessProgram)
10183           SetUserThinkingEnables();
10184
10185         /* [HGM] if a machine claims the game end we verify this claim */
10186         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10187             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10188                 char claimer;
10189                 ChessMove trueResult = (ChessMove) -1;
10190
10191                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10192                                             first.twoMachinesColor[0] :
10193                                             second.twoMachinesColor[0] ;
10194
10195                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10196                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10197                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10198                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10199                 } else
10200                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10201                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10202                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10203                 } else
10204                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10205                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10206                 }
10207
10208                 // now verify win claims, but not in drop games, as we don't understand those yet
10209                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10210                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10211                     (result == WhiteWins && claimer == 'w' ||
10212                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10213                       if (appData.debugMode) {
10214                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10215                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10216                       }
10217                       if(result != trueResult) {
10218                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10219                               result = claimer == 'w' ? BlackWins : WhiteWins;
10220                               resultDetails = buf;
10221                       }
10222                 } else
10223                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10224                     && (forwardMostMove <= backwardMostMove ||
10225                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10226                         (claimer=='b')==(forwardMostMove&1))
10227                                                                                   ) {
10228                       /* [HGM] verify: draws that were not flagged are false claims */
10229                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10230                       result = claimer == 'w' ? BlackWins : WhiteWins;
10231                       resultDetails = buf;
10232                 }
10233                 /* (Claiming a loss is accepted no questions asked!) */
10234             }
10235             /* [HGM] bare: don't allow bare King to win */
10236             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10237                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10238                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10239                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10240                && result != GameIsDrawn)
10241             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10242                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10243                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10244                         if(p >= 0 && p <= (int)WhiteKing) k++;
10245                 }
10246                 if (appData.debugMode) {
10247                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10248                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10249                 }
10250                 if(k <= 1) {
10251                         result = GameIsDrawn;
10252                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10253                         resultDetails = buf;
10254                 }
10255             }
10256         }
10257
10258
10259         if(serverMoves != NULL && !loadFlag) { char c = '=';
10260             if(result==WhiteWins) c = '+';
10261             if(result==BlackWins) c = '-';
10262             if(resultDetails != NULL)
10263                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10264         }
10265         if (resultDetails != NULL) {
10266             gameInfo.result = result;
10267             gameInfo.resultDetails = StrSave(resultDetails);
10268
10269             /* display last move only if game was not loaded from file */
10270             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10271                 DisplayMove(currentMove - 1);
10272
10273             if (forwardMostMove != 0) {
10274                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10275                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10276                                                                 ) {
10277                     if (*appData.saveGameFile != NULLCHAR) {
10278                         SaveGameToFile(appData.saveGameFile, TRUE);
10279                     } else if (appData.autoSaveGames) {
10280                         AutoSaveGame();
10281                     }
10282                     if (*appData.savePositionFile != NULLCHAR) {
10283                         SavePositionToFile(appData.savePositionFile);
10284                     }
10285                 }
10286             }
10287
10288             /* Tell program how game ended in case it is learning */
10289             /* [HGM] Moved this to after saving the PGN, just in case */
10290             /* engine died and we got here through time loss. In that */
10291             /* case we will get a fatal error writing the pipe, which */
10292             /* would otherwise lose us the PGN.                       */
10293             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10294             /* output during GameEnds should never be fatal anymore   */
10295             if (gameMode == MachinePlaysWhite ||
10296                 gameMode == MachinePlaysBlack ||
10297                 gameMode == TwoMachinesPlay ||
10298                 gameMode == IcsPlayingWhite ||
10299                 gameMode == IcsPlayingBlack ||
10300                 gameMode == BeginningOfGame) {
10301                 char buf[MSG_SIZ];
10302                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10303                         resultDetails);
10304                 if (first.pr != NoProc) {
10305                     SendToProgram(buf, &first);
10306                 }
10307                 if (second.pr != NoProc &&
10308                     gameMode == TwoMachinesPlay) {
10309                     SendToProgram(buf, &second);
10310                 }
10311             }
10312         }
10313
10314         if (appData.icsActive) {
10315             if (appData.quietPlay &&
10316                 (gameMode == IcsPlayingWhite ||
10317                  gameMode == IcsPlayingBlack)) {
10318                 SendToICS(ics_prefix);
10319                 SendToICS("set shout 1\n");
10320             }
10321             nextGameMode = IcsIdle;
10322             ics_user_moved = FALSE;
10323             /* clean up premove.  It's ugly when the game has ended and the
10324              * premove highlights are still on the board.
10325              */
10326             if (gotPremove) {
10327               gotPremove = FALSE;
10328               ClearPremoveHighlights();
10329               DrawPosition(FALSE, boards[currentMove]);
10330             }
10331             if (whosays == GE_ICS) {
10332                 switch (result) {
10333                 case WhiteWins:
10334                     if (gameMode == IcsPlayingWhite)
10335                         PlayIcsWinSound();
10336                     else if(gameMode == IcsPlayingBlack)
10337                         PlayIcsLossSound();
10338                     break;
10339                 case BlackWins:
10340                     if (gameMode == IcsPlayingBlack)
10341                         PlayIcsWinSound();
10342                     else if(gameMode == IcsPlayingWhite)
10343                         PlayIcsLossSound();
10344                     break;
10345                 case GameIsDrawn:
10346                     PlayIcsDrawSound();
10347                     break;
10348                 default:
10349                     PlayIcsUnfinishedSound();
10350                 }
10351             }
10352         } else if (gameMode == EditGame ||
10353                    gameMode == PlayFromGameFile ||
10354                    gameMode == AnalyzeMode ||
10355                    gameMode == AnalyzeFile) {
10356             nextGameMode = gameMode;
10357         } else {
10358             nextGameMode = EndOfGame;
10359         }
10360         pausing = FALSE;
10361         ModeHighlight();
10362     } else {
10363         nextGameMode = gameMode;
10364     }
10365
10366     if (appData.noChessProgram) {
10367         gameMode = nextGameMode;
10368         ModeHighlight();
10369         endingGame = 0; /* [HGM] crash */
10370         return;
10371     }
10372
10373     if (first.reuse) {
10374         /* Put first chess program into idle state */
10375         if (first.pr != NoProc &&
10376             (gameMode == MachinePlaysWhite ||
10377              gameMode == MachinePlaysBlack ||
10378              gameMode == TwoMachinesPlay ||
10379              gameMode == IcsPlayingWhite ||
10380              gameMode == IcsPlayingBlack ||
10381              gameMode == BeginningOfGame)) {
10382             SendToProgram("force\n", &first);
10383             if (first.usePing) {
10384               char buf[MSG_SIZ];
10385               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10386               SendToProgram(buf, &first);
10387             }
10388         }
10389     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10390         /* Kill off first chess program */
10391         if (first.isr != NULL)
10392           RemoveInputSource(first.isr);
10393         first.isr = NULL;
10394
10395         if (first.pr != NoProc) {
10396             ExitAnalyzeMode();
10397             DoSleep( appData.delayBeforeQuit );
10398             SendToProgram("quit\n", &first);
10399             DoSleep( appData.delayAfterQuit );
10400             DestroyChildProcess(first.pr, first.useSigterm);
10401         }
10402         first.pr = NoProc;
10403     }
10404     if (second.reuse) {
10405         /* Put second chess program into idle state */
10406         if (second.pr != NoProc &&
10407             gameMode == TwoMachinesPlay) {
10408             SendToProgram("force\n", &second);
10409             if (second.usePing) {
10410               char buf[MSG_SIZ];
10411               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10412               SendToProgram(buf, &second);
10413             }
10414         }
10415     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10416         /* Kill off second chess program */
10417         if (second.isr != NULL)
10418           RemoveInputSource(second.isr);
10419         second.isr = NULL;
10420
10421         if (second.pr != NoProc) {
10422             DoSleep( appData.delayBeforeQuit );
10423             SendToProgram("quit\n", &second);
10424             DoSleep( appData.delayAfterQuit );
10425             DestroyChildProcess(second.pr, second.useSigterm);
10426         }
10427         second.pr = NoProc;
10428     }
10429
10430     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10431         char resChar = '=';
10432         switch (result) {
10433         case WhiteWins:
10434           resChar = '+';
10435           if (first.twoMachinesColor[0] == 'w') {
10436             first.matchWins++;
10437           } else {
10438             second.matchWins++;
10439           }
10440           break;
10441         case BlackWins:
10442           resChar = '-';
10443           if (first.twoMachinesColor[0] == 'b') {
10444             first.matchWins++;
10445           } else {
10446             second.matchWins++;
10447           }
10448           break;
10449         case GameUnfinished:
10450           resChar = ' ';
10451         default:
10452           break;
10453         }
10454
10455         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10456         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10457             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10458             ReserveGame(nextGame, resChar); // sets nextGame
10459             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10460             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10461         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10462
10463         if (nextGame <= appData.matchGames && !abortMatch) {
10464             gameMode = nextGameMode;
10465             matchGame = nextGame; // this will be overruled in tourney mode!
10466             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10467             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10468             endingGame = 0; /* [HGM] crash */
10469             return;
10470         } else {
10471             gameMode = nextGameMode;
10472             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10473                      first.tidy, second.tidy,
10474                      first.matchWins, second.matchWins,
10475                      appData.matchGames - (first.matchWins + second.matchWins));
10476             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10477             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10478             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10479                 first.twoMachinesColor = "black\n";
10480                 second.twoMachinesColor = "white\n";
10481             } else {
10482                 first.twoMachinesColor = "white\n";
10483                 second.twoMachinesColor = "black\n";
10484             }
10485         }
10486     }
10487     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10488         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10489       ExitAnalyzeMode();
10490     gameMode = nextGameMode;
10491     ModeHighlight();
10492     endingGame = 0;  /* [HGM] crash */
10493     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10494         if(matchMode == TRUE) { // match through command line: exit with or without popup
10495             if(ranking) {
10496                 ToNrEvent(forwardMostMove);
10497                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10498                 else ExitEvent(0);
10499             } else DisplayFatalError(buf, 0, 0);
10500         } else { // match through menu; just stop, with or without popup
10501             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10502             ModeHighlight();
10503             if(ranking){
10504                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10505             } else DisplayNote(buf);
10506       }
10507       if(ranking) free(ranking);
10508     }
10509 }
10510
10511 /* Assumes program was just initialized (initString sent).
10512    Leaves program in force mode. */
10513 void
10514 FeedMovesToProgram(cps, upto)
10515      ChessProgramState *cps;
10516      int upto;
10517 {
10518     int i;
10519
10520     if (appData.debugMode)
10521       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10522               startedFromSetupPosition ? "position and " : "",
10523               backwardMostMove, upto, cps->which);
10524     if(currentlyInitializedVariant != gameInfo.variant) {
10525       char buf[MSG_SIZ];
10526         // [HGM] variantswitch: make engine aware of new variant
10527         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10528                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10529         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10530         SendToProgram(buf, cps);
10531         currentlyInitializedVariant = gameInfo.variant;
10532     }
10533     SendToProgram("force\n", cps);
10534     if (startedFromSetupPosition) {
10535         SendBoard(cps, backwardMostMove);
10536     if (appData.debugMode) {
10537         fprintf(debugFP, "feedMoves\n");
10538     }
10539     }
10540     for (i = backwardMostMove; i < upto; i++) {
10541         SendMoveToProgram(i, cps);
10542     }
10543 }
10544
10545
10546 int
10547 ResurrectChessProgram()
10548 {
10549      /* The chess program may have exited.
10550         If so, restart it and feed it all the moves made so far. */
10551     static int doInit = 0;
10552
10553     if (appData.noChessProgram) return 1;
10554
10555     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10556         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10557         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10558         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10559     } else {
10560         if (first.pr != NoProc) return 1;
10561         StartChessProgram(&first);
10562     }
10563     InitChessProgram(&first, FALSE);
10564     FeedMovesToProgram(&first, currentMove);
10565
10566     if (!first.sendTime) {
10567         /* can't tell gnuchess what its clock should read,
10568            so we bow to its notion. */
10569         ResetClocks();
10570         timeRemaining[0][currentMove] = whiteTimeRemaining;
10571         timeRemaining[1][currentMove] = blackTimeRemaining;
10572     }
10573
10574     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10575                 appData.icsEngineAnalyze) && first.analysisSupport) {
10576       SendToProgram("analyze\n", &first);
10577       first.analyzing = TRUE;
10578     }
10579     return 1;
10580 }
10581
10582 /*
10583  * Button procedures
10584  */
10585 void
10586 Reset(redraw, init)
10587      int redraw, init;
10588 {
10589     int i;
10590
10591     if (appData.debugMode) {
10592         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10593                 redraw, init, gameMode);
10594     }
10595     CleanupTail(); // [HGM] vari: delete any stored variations
10596     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10597     pausing = pauseExamInvalid = FALSE;
10598     startedFromSetupPosition = blackPlaysFirst = FALSE;
10599     firstMove = TRUE;
10600     whiteFlag = blackFlag = FALSE;
10601     userOfferedDraw = FALSE;
10602     hintRequested = bookRequested = FALSE;
10603     first.maybeThinking = FALSE;
10604     second.maybeThinking = FALSE;
10605     first.bookSuspend = FALSE; // [HGM] book
10606     second.bookSuspend = FALSE;
10607     thinkOutput[0] = NULLCHAR;
10608     lastHint[0] = NULLCHAR;
10609     ClearGameInfo(&gameInfo);
10610     gameInfo.variant = StringToVariant(appData.variant);
10611     ics_user_moved = ics_clock_paused = FALSE;
10612     ics_getting_history = H_FALSE;
10613     ics_gamenum = -1;
10614     white_holding[0] = black_holding[0] = NULLCHAR;
10615     ClearProgramStats();
10616     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10617
10618     ResetFrontEnd();
10619     ClearHighlights();
10620     flipView = appData.flipView;
10621     ClearPremoveHighlights();
10622     gotPremove = FALSE;
10623     alarmSounded = FALSE;
10624
10625     GameEnds(EndOfFile, NULL, GE_PLAYER);
10626     if(appData.serverMovesName != NULL) {
10627         /* [HGM] prepare to make moves file for broadcasting */
10628         clock_t t = clock();
10629         if(serverMoves != NULL) fclose(serverMoves);
10630         serverMoves = fopen(appData.serverMovesName, "r");
10631         if(serverMoves != NULL) {
10632             fclose(serverMoves);
10633             /* delay 15 sec before overwriting, so all clients can see end */
10634             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10635         }
10636         serverMoves = fopen(appData.serverMovesName, "w");
10637     }
10638
10639     ExitAnalyzeMode();
10640     gameMode = BeginningOfGame;
10641     ModeHighlight();
10642     if(appData.icsActive) gameInfo.variant = VariantNormal;
10643     currentMove = forwardMostMove = backwardMostMove = 0;
10644     InitPosition(redraw);
10645     for (i = 0; i < MAX_MOVES; i++) {
10646         if (commentList[i] != NULL) {
10647             free(commentList[i]);
10648             commentList[i] = NULL;
10649         }
10650     }
10651     ResetClocks();
10652     timeRemaining[0][0] = whiteTimeRemaining;
10653     timeRemaining[1][0] = blackTimeRemaining;
10654
10655     if (first.pr == NoProc) {
10656         StartChessProgram(&first);
10657     }
10658     if (init) {
10659             InitChessProgram(&first, startedFromSetupPosition);
10660     }
10661     DisplayTitle("");
10662     DisplayMessage("", "");
10663     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10664     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10665 }
10666
10667 void
10668 AutoPlayGameLoop()
10669 {
10670     for (;;) {
10671         if (!AutoPlayOneMove())
10672           return;
10673         if (matchMode || appData.timeDelay == 0)
10674           continue;
10675         if (appData.timeDelay < 0)
10676           return;
10677         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10678         break;
10679     }
10680 }
10681
10682
10683 int
10684 AutoPlayOneMove()
10685 {
10686     int fromX, fromY, toX, toY;
10687
10688     if (appData.debugMode) {
10689       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10690     }
10691
10692     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10693       return FALSE;
10694
10695     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10696       pvInfoList[currentMove].depth = programStats.depth;
10697       pvInfoList[currentMove].score = programStats.score;
10698       pvInfoList[currentMove].time  = 0;
10699       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10700     }
10701
10702     if (currentMove >= forwardMostMove) {
10703       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10704 //      gameMode = EndOfGame;
10705 //      ModeHighlight();
10706
10707       /* [AS] Clear current move marker at the end of a game */
10708       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10709
10710       return FALSE;
10711     }
10712
10713     toX = moveList[currentMove][2] - AAA;
10714     toY = moveList[currentMove][3] - ONE;
10715
10716     if (moveList[currentMove][1] == '@') {
10717         if (appData.highlightLastMove) {
10718             SetHighlights(-1, -1, toX, toY);
10719         }
10720     } else {
10721         fromX = moveList[currentMove][0] - AAA;
10722         fromY = moveList[currentMove][1] - ONE;
10723
10724         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10725
10726         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10727
10728         if (appData.highlightLastMove) {
10729             SetHighlights(fromX, fromY, toX, toY);
10730         }
10731     }
10732     DisplayMove(currentMove);
10733     SendMoveToProgram(currentMove++, &first);
10734     DisplayBothClocks();
10735     DrawPosition(FALSE, boards[currentMove]);
10736     // [HGM] PV info: always display, routine tests if empty
10737     DisplayComment(currentMove - 1, commentList[currentMove]);
10738     return TRUE;
10739 }
10740
10741
10742 int
10743 LoadGameOneMove(readAhead)
10744      ChessMove readAhead;
10745 {
10746     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10747     char promoChar = NULLCHAR;
10748     ChessMove moveType;
10749     char move[MSG_SIZ];
10750     char *p, *q;
10751
10752     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10753         gameMode != AnalyzeMode && gameMode != Training) {
10754         gameFileFP = NULL;
10755         return FALSE;
10756     }
10757
10758     yyboardindex = forwardMostMove;
10759     if (readAhead != EndOfFile) {
10760       moveType = readAhead;
10761     } else {
10762       if (gameFileFP == NULL)
10763           return FALSE;
10764       moveType = (ChessMove) Myylex();
10765     }
10766
10767     done = FALSE;
10768     switch (moveType) {
10769       case Comment:
10770         if (appData.debugMode)
10771           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10772         p = yy_text;
10773
10774         /* append the comment but don't display it */
10775         AppendComment(currentMove, p, FALSE);
10776         return TRUE;
10777
10778       case WhiteCapturesEnPassant:
10779       case BlackCapturesEnPassant:
10780       case WhitePromotion:
10781       case BlackPromotion:
10782       case WhiteNonPromotion:
10783       case BlackNonPromotion:
10784       case NormalMove:
10785       case WhiteKingSideCastle:
10786       case WhiteQueenSideCastle:
10787       case BlackKingSideCastle:
10788       case BlackQueenSideCastle:
10789       case WhiteKingSideCastleWild:
10790       case WhiteQueenSideCastleWild:
10791       case BlackKingSideCastleWild:
10792       case BlackQueenSideCastleWild:
10793       /* PUSH Fabien */
10794       case WhiteHSideCastleFR:
10795       case WhiteASideCastleFR:
10796       case BlackHSideCastleFR:
10797       case BlackASideCastleFR:
10798       /* POP Fabien */
10799         if (appData.debugMode)
10800           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10801         fromX = currentMoveString[0] - AAA;
10802         fromY = currentMoveString[1] - ONE;
10803         toX = currentMoveString[2] - AAA;
10804         toY = currentMoveString[3] - ONE;
10805         promoChar = currentMoveString[4];
10806         break;
10807
10808       case WhiteDrop:
10809       case BlackDrop:
10810         if (appData.debugMode)
10811           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10812         fromX = moveType == WhiteDrop ?
10813           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10814         (int) CharToPiece(ToLower(currentMoveString[0]));
10815         fromY = DROP_RANK;
10816         toX = currentMoveString[2] - AAA;
10817         toY = currentMoveString[3] - ONE;
10818         break;
10819
10820       case WhiteWins:
10821       case BlackWins:
10822       case GameIsDrawn:
10823       case GameUnfinished:
10824         if (appData.debugMode)
10825           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10826         p = strchr(yy_text, '{');
10827         if (p == NULL) p = strchr(yy_text, '(');
10828         if (p == NULL) {
10829             p = yy_text;
10830             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10831         } else {
10832             q = strchr(p, *p == '{' ? '}' : ')');
10833             if (q != NULL) *q = NULLCHAR;
10834             p++;
10835         }
10836         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10837         GameEnds(moveType, p, GE_FILE);
10838         done = TRUE;
10839         if (cmailMsgLoaded) {
10840             ClearHighlights();
10841             flipView = WhiteOnMove(currentMove);
10842             if (moveType == GameUnfinished) flipView = !flipView;
10843             if (appData.debugMode)
10844               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10845         }
10846         break;
10847
10848       case EndOfFile:
10849         if (appData.debugMode)
10850           fprintf(debugFP, "Parser hit end of file\n");
10851         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10852           case MT_NONE:
10853           case MT_CHECK:
10854             break;
10855           case MT_CHECKMATE:
10856           case MT_STAINMATE:
10857             if (WhiteOnMove(currentMove)) {
10858                 GameEnds(BlackWins, "Black mates", GE_FILE);
10859             } else {
10860                 GameEnds(WhiteWins, "White mates", GE_FILE);
10861             }
10862             break;
10863           case MT_STALEMATE:
10864             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10865             break;
10866         }
10867         done = TRUE;
10868         break;
10869
10870       case MoveNumberOne:
10871         if (lastLoadGameStart == GNUChessGame) {
10872             /* GNUChessGames have numbers, but they aren't move numbers */
10873             if (appData.debugMode)
10874               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10875                       yy_text, (int) moveType);
10876             return LoadGameOneMove(EndOfFile); /* tail recursion */
10877         }
10878         /* else fall thru */
10879
10880       case XBoardGame:
10881       case GNUChessGame:
10882       case PGNTag:
10883         /* Reached start of next game in file */
10884         if (appData.debugMode)
10885           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10886         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10887           case MT_NONE:
10888           case MT_CHECK:
10889             break;
10890           case MT_CHECKMATE:
10891           case MT_STAINMATE:
10892             if (WhiteOnMove(currentMove)) {
10893                 GameEnds(BlackWins, "Black mates", GE_FILE);
10894             } else {
10895                 GameEnds(WhiteWins, "White mates", GE_FILE);
10896             }
10897             break;
10898           case MT_STALEMATE:
10899             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10900             break;
10901         }
10902         done = TRUE;
10903         break;
10904
10905       case PositionDiagram:     /* should not happen; ignore */
10906       case ElapsedTime:         /* ignore */
10907       case NAG:                 /* ignore */
10908         if (appData.debugMode)
10909           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10910                   yy_text, (int) moveType);
10911         return LoadGameOneMove(EndOfFile); /* tail recursion */
10912
10913       case IllegalMove:
10914         if (appData.testLegality) {
10915             if (appData.debugMode)
10916               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10917             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10918                     (forwardMostMove / 2) + 1,
10919                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10920             DisplayError(move, 0);
10921             done = TRUE;
10922         } else {
10923             if (appData.debugMode)
10924               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10925                       yy_text, currentMoveString);
10926             fromX = currentMoveString[0] - AAA;
10927             fromY = currentMoveString[1] - ONE;
10928             toX = currentMoveString[2] - AAA;
10929             toY = currentMoveString[3] - ONE;
10930             promoChar = currentMoveString[4];
10931         }
10932         break;
10933
10934       case AmbiguousMove:
10935         if (appData.debugMode)
10936           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10937         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10938                 (forwardMostMove / 2) + 1,
10939                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10940         DisplayError(move, 0);
10941         done = TRUE;
10942         break;
10943
10944       default:
10945       case ImpossibleMove:
10946         if (appData.debugMode)
10947           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10948         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10949                 (forwardMostMove / 2) + 1,
10950                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10951         DisplayError(move, 0);
10952         done = TRUE;
10953         break;
10954     }
10955
10956     if (done) {
10957         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10958             DrawPosition(FALSE, boards[currentMove]);
10959             DisplayBothClocks();
10960             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10961               DisplayComment(currentMove - 1, commentList[currentMove]);
10962         }
10963         (void) StopLoadGameTimer();
10964         gameFileFP = NULL;
10965         cmailOldMove = forwardMostMove;
10966         return FALSE;
10967     } else {
10968         /* currentMoveString is set as a side-effect of yylex */
10969
10970         thinkOutput[0] = NULLCHAR;
10971         MakeMove(fromX, fromY, toX, toY, promoChar);
10972         currentMove = forwardMostMove;
10973         return TRUE;
10974     }
10975 }
10976
10977 /* Load the nth game from the given file */
10978 int
10979 LoadGameFromFile(filename, n, title, useList)
10980      char *filename;
10981      int n;
10982      char *title;
10983      /*Boolean*/ int useList;
10984 {
10985     FILE *f;
10986     char buf[MSG_SIZ];
10987
10988     if (strcmp(filename, "-") == 0) {
10989         f = stdin;
10990         title = "stdin";
10991     } else {
10992         f = fopen(filename, "rb");
10993         if (f == NULL) {
10994           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10995             DisplayError(buf, errno);
10996             return FALSE;
10997         }
10998     }
10999     if (fseek(f, 0, 0) == -1) {
11000         /* f is not seekable; probably a pipe */
11001         useList = FALSE;
11002     }
11003     if (useList && n == 0) {
11004         int error = GameListBuild(f);
11005         if (error) {
11006             DisplayError(_("Cannot build game list"), error);
11007         } else if (!ListEmpty(&gameList) &&
11008                    ((ListGame *) gameList.tailPred)->number > 1) {
11009             GameListPopUp(f, title);
11010             return TRUE;
11011         }
11012         GameListDestroy();
11013         n = 1;
11014     }
11015     if (n == 0) n = 1;
11016     return LoadGame(f, n, title, FALSE);
11017 }
11018
11019
11020 void
11021 MakeRegisteredMove()
11022 {
11023     int fromX, fromY, toX, toY;
11024     char promoChar;
11025     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11026         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11027           case CMAIL_MOVE:
11028           case CMAIL_DRAW:
11029             if (appData.debugMode)
11030               fprintf(debugFP, "Restoring %s for game %d\n",
11031                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11032
11033             thinkOutput[0] = NULLCHAR;
11034             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11035             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11036             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11037             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11038             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11039             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11040             MakeMove(fromX, fromY, toX, toY, promoChar);
11041             ShowMove(fromX, fromY, toX, toY);
11042
11043             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11044               case MT_NONE:
11045               case MT_CHECK:
11046                 break;
11047
11048               case MT_CHECKMATE:
11049               case MT_STAINMATE:
11050                 if (WhiteOnMove(currentMove)) {
11051                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11052                 } else {
11053                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11054                 }
11055                 break;
11056
11057               case MT_STALEMATE:
11058                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11059                 break;
11060             }
11061
11062             break;
11063
11064           case CMAIL_RESIGN:
11065             if (WhiteOnMove(currentMove)) {
11066                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11067             } else {
11068                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11069             }
11070             break;
11071
11072           case CMAIL_ACCEPT:
11073             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11074             break;
11075
11076           default:
11077             break;
11078         }
11079     }
11080
11081     return;
11082 }
11083
11084 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11085 int
11086 CmailLoadGame(f, gameNumber, title, useList)
11087      FILE *f;
11088      int gameNumber;
11089      char *title;
11090      int useList;
11091 {
11092     int retVal;
11093
11094     if (gameNumber > nCmailGames) {
11095         DisplayError(_("No more games in this message"), 0);
11096         return FALSE;
11097     }
11098     if (f == lastLoadGameFP) {
11099         int offset = gameNumber - lastLoadGameNumber;
11100         if (offset == 0) {
11101             cmailMsg[0] = NULLCHAR;
11102             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11103                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11104                 nCmailMovesRegistered--;
11105             }
11106             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11107             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11108                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11109             }
11110         } else {
11111             if (! RegisterMove()) return FALSE;
11112         }
11113     }
11114
11115     retVal = LoadGame(f, gameNumber, title, useList);
11116
11117     /* Make move registered during previous look at this game, if any */
11118     MakeRegisteredMove();
11119
11120     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11121         commentList[currentMove]
11122           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11123         DisplayComment(currentMove - 1, commentList[currentMove]);
11124     }
11125
11126     return retVal;
11127 }
11128
11129 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11130 int
11131 ReloadGame(offset)
11132      int offset;
11133 {
11134     int gameNumber = lastLoadGameNumber + offset;
11135     if (lastLoadGameFP == NULL) {
11136         DisplayError(_("No game has been loaded yet"), 0);
11137         return FALSE;
11138     }
11139     if (gameNumber <= 0) {
11140         DisplayError(_("Can't back up any further"), 0);
11141         return FALSE;
11142     }
11143     if (cmailMsgLoaded) {
11144         return CmailLoadGame(lastLoadGameFP, gameNumber,
11145                              lastLoadGameTitle, lastLoadGameUseList);
11146     } else {
11147         return LoadGame(lastLoadGameFP, gameNumber,
11148                         lastLoadGameTitle, lastLoadGameUseList);
11149     }
11150 }
11151
11152 int keys[EmptySquare+1];
11153
11154 int
11155 PositionMatches(Board b1, Board b2)
11156 {
11157     int r, f, sum=0;
11158     switch(appData.searchMode) {
11159         case 1: return CompareWithRights(b1, b2);
11160         case 2:
11161             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11162                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11163             }
11164             return TRUE;
11165         case 3:
11166             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11167               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11168                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11169             }
11170             return sum==0;
11171         case 4:
11172             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11173                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11174             }
11175             return sum==0;
11176     }
11177     return TRUE;
11178 }
11179
11180 #define Q_PROMO  4
11181 #define Q_EP     3
11182 #define Q_BCASTL 2
11183 #define Q_WCASTL 1
11184
11185 int pieceList[256], quickBoard[256];
11186 ChessSquare pieceType[256] = { EmptySquare };
11187 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11188 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11189 int soughtTotal, turn;
11190 Boolean epOK, flipSearch;
11191
11192 typedef struct {
11193     unsigned char piece, to;
11194 } Move;
11195
11196 #define DSIZE (250000)
11197
11198 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11199 Move *moveDatabase = initialSpace;
11200 unsigned int movePtr, dataSize = DSIZE;
11201
11202 int MakePieceList(Board board, int *counts)
11203 {
11204     int r, f, n=Q_PROMO, total=0;
11205     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11206     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11207         int sq = f + (r<<4);
11208         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11209             quickBoard[sq] = ++n;
11210             pieceList[n] = sq;
11211             pieceType[n] = board[r][f];
11212             counts[board[r][f]]++;
11213             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11214             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11215             total++;
11216         }
11217     }
11218     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11219     return total;
11220 }
11221
11222 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11223 {
11224     int sq = fromX + (fromY<<4);
11225     int piece = quickBoard[sq];
11226     quickBoard[sq] = 0;
11227     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11228     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11229         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11230         moveDatabase[movePtr++].piece = Q_WCASTL;
11231         quickBoard[sq] = piece;
11232         piece = quickBoard[from]; quickBoard[from] = 0;
11233         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11234     } else
11235     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11236         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11237         moveDatabase[movePtr++].piece = Q_BCASTL;
11238         quickBoard[sq] = piece;
11239         piece = quickBoard[from]; quickBoard[from] = 0;
11240         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11241     } else
11242     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11243         quickBoard[(fromY<<4)+toX] = 0;
11244         moveDatabase[movePtr].piece = Q_EP;
11245         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11246         moveDatabase[movePtr].to = sq;
11247     } else
11248     if(promoPiece != pieceType[piece]) {
11249         moveDatabase[movePtr++].piece = Q_PROMO;
11250         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11251     }
11252     moveDatabase[movePtr].piece = piece;
11253     quickBoard[sq] = piece;
11254     movePtr++;
11255 }
11256
11257 int PackGame(Board board)
11258 {
11259     Move *newSpace = NULL;
11260     moveDatabase[movePtr].piece = 0; // terminate previous game
11261     if(movePtr > dataSize) {
11262         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11263         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11264         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11265         if(newSpace) {
11266             int i;
11267             Move *p = moveDatabase, *q = newSpace;
11268             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11269             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11270             moveDatabase = newSpace;
11271         } else { // calloc failed, we must be out of memory. Too bad...
11272             dataSize = 0; // prevent calloc events for all subsequent games
11273             return 0;     // and signal this one isn't cached
11274         }
11275     }
11276     movePtr++;
11277     MakePieceList(board, counts);
11278     return movePtr;
11279 }
11280
11281 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11282 {   // compare according to search mode
11283     int r, f;
11284     switch(appData.searchMode)
11285     {
11286       case 1: // exact position match
11287         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11288         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11289             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11290         }
11291         break;
11292       case 2: // can have extra material on empty squares
11293         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11294             if(board[r][f] == EmptySquare) continue;
11295             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11296         }
11297         break;
11298       case 3: // material with exact Pawn structure
11299         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11300             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11301             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11302         } // fall through to material comparison
11303       case 4: // exact material
11304         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11305         break;
11306       case 6: // material range with given imbalance
11307         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11308         // fall through to range comparison
11309       case 5: // material range
11310         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11311     }
11312     return TRUE;
11313 }
11314
11315 int QuickScan(Board board, Move *move)
11316 {   // reconstruct game,and compare all positions in it
11317     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11318     do {
11319         int piece = move->piece;
11320         int to = move->to, from = pieceList[piece];
11321         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11322           if(!piece) return -1;
11323           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11324             piece = (++move)->piece;
11325             from = pieceList[piece];
11326             counts[pieceType[piece]]--;
11327             pieceType[piece] = (ChessSquare) move->to;
11328             counts[move->to]++;
11329           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11330             counts[pieceType[quickBoard[to]]]--;
11331             quickBoard[to] = 0; total--;
11332             move++;
11333             continue;
11334           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11335             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11336             from  = pieceList[piece]; // so this must be King
11337             quickBoard[from] = 0;
11338             quickBoard[to] = piece;
11339             pieceList[piece] = to;
11340             move++;
11341             continue;
11342           }
11343         }
11344         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11345         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11346         quickBoard[from] = 0;
11347         quickBoard[to] = piece;
11348         pieceList[piece] = to;
11349         cnt++; turn ^= 3;
11350         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11351            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11352            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11353                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11354           ) {
11355             static int lastCounts[EmptySquare+1];
11356             int i;
11357             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11358             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11359         } else stretch = 0;
11360         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11361         move++;
11362     } while(1);
11363 }
11364
11365 void InitSearch()
11366 {
11367     int r, f;
11368     flipSearch = FALSE;
11369     CopyBoard(soughtBoard, boards[currentMove]);
11370     soughtTotal = MakePieceList(soughtBoard, maxSought);
11371     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11372     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11373     CopyBoard(reverseBoard, boards[currentMove]);
11374     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11375         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11376         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11377         reverseBoard[r][f] = piece;
11378     }
11379     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11380     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11381     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11382                  || (boards[currentMove][CASTLING][2] == NoRights || 
11383                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11384                  && (boards[currentMove][CASTLING][5] == NoRights || 
11385                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11386       ) {
11387         flipSearch = TRUE;
11388         CopyBoard(flipBoard, soughtBoard);
11389         CopyBoard(rotateBoard, reverseBoard);
11390         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11391             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11392             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11393         }
11394     }
11395     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11396     if(appData.searchMode >= 5) {
11397         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11398         MakePieceList(soughtBoard, minSought);
11399         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11400     }
11401     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11402         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11403 }
11404
11405 GameInfo dummyInfo;
11406
11407 int GameContainsPosition(FILE *f, ListGame *lg)
11408 {
11409     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11410     int fromX, fromY, toX, toY;
11411     char promoChar;
11412     static int initDone=FALSE;
11413
11414     // weed out games based on numerical tag comparison
11415     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11416     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11417     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11418     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11419     if(!initDone) {
11420         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11421         initDone = TRUE;
11422     }
11423     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11424     else CopyBoard(boards[scratch], initialPosition); // default start position
11425     if(lg->moves) {
11426         turn = btm + 1;
11427         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11428         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11429     }
11430     if(btm) plyNr++;
11431     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11432     fseek(f, lg->offset, 0);
11433     yynewfile(f);
11434     while(1) {
11435         yyboardindex = scratch;
11436         quickFlag = plyNr+1;
11437         next = Myylex();
11438         quickFlag = 0;
11439         switch(next) {
11440             case PGNTag:
11441                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11442             default:
11443                 continue;
11444
11445             case XBoardGame:
11446             case GNUChessGame:
11447                 if(plyNr) return -1; // after we have seen moves, this is for new game
11448               continue;
11449
11450             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11451             case ImpossibleMove:
11452             case WhiteWins: // game ends here with these four
11453             case BlackWins:
11454             case GameIsDrawn:
11455             case GameUnfinished:
11456                 return -1;
11457
11458             case IllegalMove:
11459                 if(appData.testLegality) return -1;
11460             case WhiteCapturesEnPassant:
11461             case BlackCapturesEnPassant:
11462             case WhitePromotion:
11463             case BlackPromotion:
11464             case WhiteNonPromotion:
11465             case BlackNonPromotion:
11466             case NormalMove:
11467             case WhiteKingSideCastle:
11468             case WhiteQueenSideCastle:
11469             case BlackKingSideCastle:
11470             case BlackQueenSideCastle:
11471             case WhiteKingSideCastleWild:
11472             case WhiteQueenSideCastleWild:
11473             case BlackKingSideCastleWild:
11474             case BlackQueenSideCastleWild:
11475             case WhiteHSideCastleFR:
11476             case WhiteASideCastleFR:
11477             case BlackHSideCastleFR:
11478             case BlackASideCastleFR:
11479                 fromX = currentMoveString[0] - AAA;
11480                 fromY = currentMoveString[1] - ONE;
11481                 toX = currentMoveString[2] - AAA;
11482                 toY = currentMoveString[3] - ONE;
11483                 promoChar = currentMoveString[4];
11484                 break;
11485             case WhiteDrop:
11486             case BlackDrop:
11487                 fromX = next == WhiteDrop ?
11488                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11489                   (int) CharToPiece(ToLower(currentMoveString[0]));
11490                 fromY = DROP_RANK;
11491                 toX = currentMoveString[2] - AAA;
11492                 toY = currentMoveString[3] - ONE;
11493                 promoChar = 0;
11494                 break;
11495         }
11496         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11497         plyNr++;
11498         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11499         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11500         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11501         if(appData.findMirror) {
11502             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11503             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11504         }
11505     }
11506 }
11507
11508 /* Load the nth game from open file f */
11509 int
11510 LoadGame(f, gameNumber, title, useList)
11511      FILE *f;
11512      int gameNumber;
11513      char *title;
11514      int useList;
11515 {
11516     ChessMove cm;
11517     char buf[MSG_SIZ];
11518     int gn = gameNumber;
11519     ListGame *lg = NULL;
11520     int numPGNTags = 0;
11521     int err, pos = -1;
11522     GameMode oldGameMode;
11523     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11524
11525     if (appData.debugMode)
11526         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11527
11528     if (gameMode == Training )
11529         SetTrainingModeOff();
11530
11531     oldGameMode = gameMode;
11532     if (gameMode != BeginningOfGame) {
11533       Reset(FALSE, TRUE);
11534     }
11535
11536     gameFileFP = f;
11537     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11538         fclose(lastLoadGameFP);
11539     }
11540
11541     if (useList) {
11542         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11543
11544         if (lg) {
11545             fseek(f, lg->offset, 0);
11546             GameListHighlight(gameNumber);
11547             pos = lg->position;
11548             gn = 1;
11549         }
11550         else {
11551             DisplayError(_("Game number out of range"), 0);
11552             return FALSE;
11553         }
11554     } else {
11555         GameListDestroy();
11556         if (fseek(f, 0, 0) == -1) {
11557             if (f == lastLoadGameFP ?
11558                 gameNumber == lastLoadGameNumber + 1 :
11559                 gameNumber == 1) {
11560                 gn = 1;
11561             } else {
11562                 DisplayError(_("Can't seek on game file"), 0);
11563                 return FALSE;
11564             }
11565         }
11566     }
11567     lastLoadGameFP = f;
11568     lastLoadGameNumber = gameNumber;
11569     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11570     lastLoadGameUseList = useList;
11571
11572     yynewfile(f);
11573
11574     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11575       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11576                 lg->gameInfo.black);
11577             DisplayTitle(buf);
11578     } else if (*title != NULLCHAR) {
11579         if (gameNumber > 1) {
11580           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11581             DisplayTitle(buf);
11582         } else {
11583             DisplayTitle(title);
11584         }
11585     }
11586
11587     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11588         gameMode = PlayFromGameFile;
11589         ModeHighlight();
11590     }
11591
11592     currentMove = forwardMostMove = backwardMostMove = 0;
11593     CopyBoard(boards[0], initialPosition);
11594     StopClocks();
11595
11596     /*
11597      * Skip the first gn-1 games in the file.
11598      * Also skip over anything that precedes an identifiable
11599      * start of game marker, to avoid being confused by
11600      * garbage at the start of the file.  Currently
11601      * recognized start of game markers are the move number "1",
11602      * the pattern "gnuchess .* game", the pattern
11603      * "^[#;%] [^ ]* game file", and a PGN tag block.
11604      * A game that starts with one of the latter two patterns
11605      * will also have a move number 1, possibly
11606      * following a position diagram.
11607      * 5-4-02: Let's try being more lenient and allowing a game to
11608      * start with an unnumbered move.  Does that break anything?
11609      */
11610     cm = lastLoadGameStart = EndOfFile;
11611     while (gn > 0) {
11612         yyboardindex = forwardMostMove;
11613         cm = (ChessMove) Myylex();
11614         switch (cm) {
11615           case EndOfFile:
11616             if (cmailMsgLoaded) {
11617                 nCmailGames = CMAIL_MAX_GAMES - gn;
11618             } else {
11619                 Reset(TRUE, TRUE);
11620                 DisplayError(_("Game not found in file"), 0);
11621             }
11622             return FALSE;
11623
11624           case GNUChessGame:
11625           case XBoardGame:
11626             gn--;
11627             lastLoadGameStart = cm;
11628             break;
11629
11630           case MoveNumberOne:
11631             switch (lastLoadGameStart) {
11632               case GNUChessGame:
11633               case XBoardGame:
11634               case PGNTag:
11635                 break;
11636               case MoveNumberOne:
11637               case EndOfFile:
11638                 gn--;           /* count this game */
11639                 lastLoadGameStart = cm;
11640                 break;
11641               default:
11642                 /* impossible */
11643                 break;
11644             }
11645             break;
11646
11647           case PGNTag:
11648             switch (lastLoadGameStart) {
11649               case GNUChessGame:
11650               case PGNTag:
11651               case MoveNumberOne:
11652               case EndOfFile:
11653                 gn--;           /* count this game */
11654                 lastLoadGameStart = cm;
11655                 break;
11656               case XBoardGame:
11657                 lastLoadGameStart = cm; /* game counted already */
11658                 break;
11659               default:
11660                 /* impossible */
11661                 break;
11662             }
11663             if (gn > 0) {
11664                 do {
11665                     yyboardindex = forwardMostMove;
11666                     cm = (ChessMove) Myylex();
11667                 } while (cm == PGNTag || cm == Comment);
11668             }
11669             break;
11670
11671           case WhiteWins:
11672           case BlackWins:
11673           case GameIsDrawn:
11674             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11675                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11676                     != CMAIL_OLD_RESULT) {
11677                     nCmailResults ++ ;
11678                     cmailResult[  CMAIL_MAX_GAMES
11679                                 - gn - 1] = CMAIL_OLD_RESULT;
11680                 }
11681             }
11682             break;
11683
11684           case NormalMove:
11685             /* Only a NormalMove can be at the start of a game
11686              * without a position diagram. */
11687             if (lastLoadGameStart == EndOfFile ) {
11688               gn--;
11689               lastLoadGameStart = MoveNumberOne;
11690             }
11691             break;
11692
11693           default:
11694             break;
11695         }
11696     }
11697
11698     if (appData.debugMode)
11699       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11700
11701     if (cm == XBoardGame) {
11702         /* Skip any header junk before position diagram and/or move 1 */
11703         for (;;) {
11704             yyboardindex = forwardMostMove;
11705             cm = (ChessMove) Myylex();
11706
11707             if (cm == EndOfFile ||
11708                 cm == GNUChessGame || cm == XBoardGame) {
11709                 /* Empty game; pretend end-of-file and handle later */
11710                 cm = EndOfFile;
11711                 break;
11712             }
11713
11714             if (cm == MoveNumberOne || cm == PositionDiagram ||
11715                 cm == PGNTag || cm == Comment)
11716               break;
11717         }
11718     } else if (cm == GNUChessGame) {
11719         if (gameInfo.event != NULL) {
11720             free(gameInfo.event);
11721         }
11722         gameInfo.event = StrSave(yy_text);
11723     }
11724
11725     startedFromSetupPosition = FALSE;
11726     while (cm == PGNTag) {
11727         if (appData.debugMode)
11728           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11729         err = ParsePGNTag(yy_text, &gameInfo);
11730         if (!err) numPGNTags++;
11731
11732         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11733         if(gameInfo.variant != oldVariant) {
11734             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11735             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11736             InitPosition(TRUE);
11737             oldVariant = gameInfo.variant;
11738             if (appData.debugMode)
11739               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11740         }
11741
11742
11743         if (gameInfo.fen != NULL) {
11744           Board initial_position;
11745           startedFromSetupPosition = TRUE;
11746           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11747             Reset(TRUE, TRUE);
11748             DisplayError(_("Bad FEN position in file"), 0);
11749             return FALSE;
11750           }
11751           CopyBoard(boards[0], initial_position);
11752           if (blackPlaysFirst) {
11753             currentMove = forwardMostMove = backwardMostMove = 1;
11754             CopyBoard(boards[1], initial_position);
11755             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11756             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11757             timeRemaining[0][1] = whiteTimeRemaining;
11758             timeRemaining[1][1] = blackTimeRemaining;
11759             if (commentList[0] != NULL) {
11760               commentList[1] = commentList[0];
11761               commentList[0] = NULL;
11762             }
11763           } else {
11764             currentMove = forwardMostMove = backwardMostMove = 0;
11765           }
11766           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11767           {   int i;
11768               initialRulePlies = FENrulePlies;
11769               for( i=0; i< nrCastlingRights; i++ )
11770                   initialRights[i] = initial_position[CASTLING][i];
11771           }
11772           yyboardindex = forwardMostMove;
11773           free(gameInfo.fen);
11774           gameInfo.fen = NULL;
11775         }
11776
11777         yyboardindex = forwardMostMove;
11778         cm = (ChessMove) Myylex();
11779
11780         /* Handle comments interspersed among the tags */
11781         while (cm == Comment) {
11782             char *p;
11783             if (appData.debugMode)
11784               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11785             p = yy_text;
11786             AppendComment(currentMove, p, FALSE);
11787             yyboardindex = forwardMostMove;
11788             cm = (ChessMove) Myylex();
11789         }
11790     }
11791
11792     /* don't rely on existence of Event tag since if game was
11793      * pasted from clipboard the Event tag may not exist
11794      */
11795     if (numPGNTags > 0){
11796         char *tags;
11797         if (gameInfo.variant == VariantNormal) {
11798           VariantClass v = StringToVariant(gameInfo.event);
11799           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11800           if(v < VariantShogi) gameInfo.variant = v;
11801         }
11802         if (!matchMode) {
11803           if( appData.autoDisplayTags ) {
11804             tags = PGNTags(&gameInfo);
11805             TagsPopUp(tags, CmailMsg());
11806             free(tags);
11807           }
11808         }
11809     } else {
11810         /* Make something up, but don't display it now */
11811         SetGameInfo();
11812         TagsPopDown();
11813     }
11814
11815     if (cm == PositionDiagram) {
11816         int i, j;
11817         char *p;
11818         Board initial_position;
11819
11820         if (appData.debugMode)
11821           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11822
11823         if (!startedFromSetupPosition) {
11824             p = yy_text;
11825             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11826               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11827                 switch (*p) {
11828                   case '{':
11829                   case '[':
11830                   case '-':
11831                   case ' ':
11832                   case '\t':
11833                   case '\n':
11834                   case '\r':
11835                     break;
11836                   default:
11837                     initial_position[i][j++] = CharToPiece(*p);
11838                     break;
11839                 }
11840             while (*p == ' ' || *p == '\t' ||
11841                    *p == '\n' || *p == '\r') p++;
11842
11843             if (strncmp(p, "black", strlen("black"))==0)
11844               blackPlaysFirst = TRUE;
11845             else
11846               blackPlaysFirst = FALSE;
11847             startedFromSetupPosition = TRUE;
11848
11849             CopyBoard(boards[0], initial_position);
11850             if (blackPlaysFirst) {
11851                 currentMove = forwardMostMove = backwardMostMove = 1;
11852                 CopyBoard(boards[1], initial_position);
11853                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11854                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11855                 timeRemaining[0][1] = whiteTimeRemaining;
11856                 timeRemaining[1][1] = blackTimeRemaining;
11857                 if (commentList[0] != NULL) {
11858                     commentList[1] = commentList[0];
11859                     commentList[0] = NULL;
11860                 }
11861             } else {
11862                 currentMove = forwardMostMove = backwardMostMove = 0;
11863             }
11864         }
11865         yyboardindex = forwardMostMove;
11866         cm = (ChessMove) Myylex();
11867     }
11868
11869     if (first.pr == NoProc) {
11870         StartChessProgram(&first);
11871     }
11872     InitChessProgram(&first, FALSE);
11873     SendToProgram("force\n", &first);
11874     if (startedFromSetupPosition) {
11875         SendBoard(&first, forwardMostMove);
11876     if (appData.debugMode) {
11877         fprintf(debugFP, "Load Game\n");
11878     }
11879         DisplayBothClocks();
11880     }
11881
11882     /* [HGM] server: flag to write setup moves in broadcast file as one */
11883     loadFlag = appData.suppressLoadMoves;
11884
11885     while (cm == Comment) {
11886         char *p;
11887         if (appData.debugMode)
11888           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11889         p = yy_text;
11890         AppendComment(currentMove, p, FALSE);
11891         yyboardindex = forwardMostMove;
11892         cm = (ChessMove) Myylex();
11893     }
11894
11895     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11896         cm == WhiteWins || cm == BlackWins ||
11897         cm == GameIsDrawn || cm == GameUnfinished) {
11898         DisplayMessage("", _("No moves in game"));
11899         if (cmailMsgLoaded) {
11900             if (appData.debugMode)
11901               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11902             ClearHighlights();
11903             flipView = FALSE;
11904         }
11905         DrawPosition(FALSE, boards[currentMove]);
11906         DisplayBothClocks();
11907         gameMode = EditGame;
11908         ModeHighlight();
11909         gameFileFP = NULL;
11910         cmailOldMove = 0;
11911         return TRUE;
11912     }
11913
11914     // [HGM] PV info: routine tests if comment empty
11915     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11916         DisplayComment(currentMove - 1, commentList[currentMove]);
11917     }
11918     if (!matchMode && appData.timeDelay != 0)
11919       DrawPosition(FALSE, boards[currentMove]);
11920
11921     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11922       programStats.ok_to_send = 1;
11923     }
11924
11925     /* if the first token after the PGN tags is a move
11926      * and not move number 1, retrieve it from the parser
11927      */
11928     if (cm != MoveNumberOne)
11929         LoadGameOneMove(cm);
11930
11931     /* load the remaining moves from the file */
11932     while (LoadGameOneMove(EndOfFile)) {
11933       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11934       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11935     }
11936
11937     /* rewind to the start of the game */
11938     currentMove = backwardMostMove;
11939
11940     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11941
11942     if (oldGameMode == AnalyzeFile ||
11943         oldGameMode == AnalyzeMode) {
11944       AnalyzeFileEvent();
11945     }
11946
11947     if (!matchMode && pos >= 0) {
11948         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11949     } else
11950     if (matchMode || appData.timeDelay == 0) {
11951       ToEndEvent();
11952     } else if (appData.timeDelay > 0) {
11953       AutoPlayGameLoop();
11954     }
11955
11956     if (appData.debugMode)
11957         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11958
11959     loadFlag = 0; /* [HGM] true game starts */
11960     return TRUE;
11961 }
11962
11963 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11964 int
11965 ReloadPosition(offset)
11966      int offset;
11967 {
11968     int positionNumber = lastLoadPositionNumber + offset;
11969     if (lastLoadPositionFP == NULL) {
11970         DisplayError(_("No position has been loaded yet"), 0);
11971         return FALSE;
11972     }
11973     if (positionNumber <= 0) {
11974         DisplayError(_("Can't back up any further"), 0);
11975         return FALSE;
11976     }
11977     return LoadPosition(lastLoadPositionFP, positionNumber,
11978                         lastLoadPositionTitle);
11979 }
11980
11981 /* Load the nth position from the given file */
11982 int
11983 LoadPositionFromFile(filename, n, title)
11984      char *filename;
11985      int n;
11986      char *title;
11987 {
11988     FILE *f;
11989     char buf[MSG_SIZ];
11990
11991     if (strcmp(filename, "-") == 0) {
11992         return LoadPosition(stdin, n, "stdin");
11993     } else {
11994         f = fopen(filename, "rb");
11995         if (f == NULL) {
11996             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11997             DisplayError(buf, errno);
11998             return FALSE;
11999         } else {
12000             return LoadPosition(f, n, title);
12001         }
12002     }
12003 }
12004
12005 /* Load the nth position from the given open file, and close it */
12006 int
12007 LoadPosition(f, positionNumber, title)
12008      FILE *f;
12009      int positionNumber;
12010      char *title;
12011 {
12012     char *p, line[MSG_SIZ];
12013     Board initial_position;
12014     int i, j, fenMode, pn;
12015
12016     if (gameMode == Training )
12017         SetTrainingModeOff();
12018
12019     if (gameMode != BeginningOfGame) {
12020         Reset(FALSE, TRUE);
12021     }
12022     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12023         fclose(lastLoadPositionFP);
12024     }
12025     if (positionNumber == 0) positionNumber = 1;
12026     lastLoadPositionFP = f;
12027     lastLoadPositionNumber = positionNumber;
12028     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12029     if (first.pr == NoProc && !appData.noChessProgram) {
12030       StartChessProgram(&first);
12031       InitChessProgram(&first, FALSE);
12032     }
12033     pn = positionNumber;
12034     if (positionNumber < 0) {
12035         /* Negative position number means to seek to that byte offset */
12036         if (fseek(f, -positionNumber, 0) == -1) {
12037             DisplayError(_("Can't seek on position file"), 0);
12038             return FALSE;
12039         };
12040         pn = 1;
12041     } else {
12042         if (fseek(f, 0, 0) == -1) {
12043             if (f == lastLoadPositionFP ?
12044                 positionNumber == lastLoadPositionNumber + 1 :
12045                 positionNumber == 1) {
12046                 pn = 1;
12047             } else {
12048                 DisplayError(_("Can't seek on position file"), 0);
12049                 return FALSE;
12050             }
12051         }
12052     }
12053     /* See if this file is FEN or old-style xboard */
12054     if (fgets(line, MSG_SIZ, f) == NULL) {
12055         DisplayError(_("Position not found in file"), 0);
12056         return FALSE;
12057     }
12058     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12059     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12060
12061     if (pn >= 2) {
12062         if (fenMode || line[0] == '#') pn--;
12063         while (pn > 0) {
12064             /* skip positions before number pn */
12065             if (fgets(line, MSG_SIZ, f) == NULL) {
12066                 Reset(TRUE, TRUE);
12067                 DisplayError(_("Position not found in file"), 0);
12068                 return FALSE;
12069             }
12070             if (fenMode || line[0] == '#') pn--;
12071         }
12072     }
12073
12074     if (fenMode) {
12075         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12076             DisplayError(_("Bad FEN position in file"), 0);
12077             return FALSE;
12078         }
12079     } else {
12080         (void) fgets(line, MSG_SIZ, f);
12081         (void) fgets(line, MSG_SIZ, f);
12082
12083         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12084             (void) fgets(line, MSG_SIZ, f);
12085             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12086                 if (*p == ' ')
12087                   continue;
12088                 initial_position[i][j++] = CharToPiece(*p);
12089             }
12090         }
12091
12092         blackPlaysFirst = FALSE;
12093         if (!feof(f)) {
12094             (void) fgets(line, MSG_SIZ, f);
12095             if (strncmp(line, "black", strlen("black"))==0)
12096               blackPlaysFirst = TRUE;
12097         }
12098     }
12099     startedFromSetupPosition = TRUE;
12100
12101     CopyBoard(boards[0], initial_position);
12102     if (blackPlaysFirst) {
12103         currentMove = forwardMostMove = backwardMostMove = 1;
12104         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12105         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12106         CopyBoard(boards[1], initial_position);
12107         DisplayMessage("", _("Black to play"));
12108     } else {
12109         currentMove = forwardMostMove = backwardMostMove = 0;
12110         DisplayMessage("", _("White to play"));
12111     }
12112     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12113     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12114         SendToProgram("force\n", &first);
12115         SendBoard(&first, forwardMostMove);
12116     }
12117     if (appData.debugMode) {
12118 int i, j;
12119   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12120   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12121         fprintf(debugFP, "Load Position\n");
12122     }
12123
12124     if (positionNumber > 1) {
12125       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12126         DisplayTitle(line);
12127     } else {
12128         DisplayTitle(title);
12129     }
12130     gameMode = EditGame;
12131     ModeHighlight();
12132     ResetClocks();
12133     timeRemaining[0][1] = whiteTimeRemaining;
12134     timeRemaining[1][1] = blackTimeRemaining;
12135     DrawPosition(FALSE, boards[currentMove]);
12136
12137     return TRUE;
12138 }
12139
12140
12141 void
12142 CopyPlayerNameIntoFileName(dest, src)
12143      char **dest, *src;
12144 {
12145     while (*src != NULLCHAR && *src != ',') {
12146         if (*src == ' ') {
12147             *(*dest)++ = '_';
12148             src++;
12149         } else {
12150             *(*dest)++ = *src++;
12151         }
12152     }
12153 }
12154
12155 char *DefaultFileName(ext)
12156      char *ext;
12157 {
12158     static char def[MSG_SIZ];
12159     char *p;
12160
12161     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12162         p = def;
12163         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12164         *p++ = '-';
12165         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12166         *p++ = '.';
12167         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12168     } else {
12169         def[0] = NULLCHAR;
12170     }
12171     return def;
12172 }
12173
12174 /* Save the current game to the given file */
12175 int
12176 SaveGameToFile(filename, append)
12177      char *filename;
12178      int append;
12179 {
12180     FILE *f;
12181     char buf[MSG_SIZ];
12182     int result, i, t,tot=0;
12183
12184     if (strcmp(filename, "-") == 0) {
12185         return SaveGame(stdout, 0, NULL);
12186     } else {
12187         for(i=0; i<10; i++) { // upto 10 tries
12188              f = fopen(filename, append ? "a" : "w");
12189              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12190              if(f || errno != 13) break;
12191              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12192              tot += t;
12193         }
12194         if (f == NULL) {
12195             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12196             DisplayError(buf, errno);
12197             return FALSE;
12198         } else {
12199             safeStrCpy(buf, lastMsg, MSG_SIZ);
12200             DisplayMessage(_("Waiting for access to save file"), "");
12201             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12202             DisplayMessage(_("Saving game"), "");
12203             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
12204             result = SaveGame(f, 0, NULL);
12205             DisplayMessage(buf, "");
12206             return result;
12207         }
12208     }
12209 }
12210
12211 char *
12212 SavePart(str)
12213      char *str;
12214 {
12215     static char buf[MSG_SIZ];
12216     char *p;
12217
12218     p = strchr(str, ' ');
12219     if (p == NULL) return str;
12220     strncpy(buf, str, p - str);
12221     buf[p - str] = NULLCHAR;
12222     return buf;
12223 }
12224
12225 #define PGN_MAX_LINE 75
12226
12227 #define PGN_SIDE_WHITE  0
12228 #define PGN_SIDE_BLACK  1
12229
12230 /* [AS] */
12231 static int FindFirstMoveOutOfBook( int side )
12232 {
12233     int result = -1;
12234
12235     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12236         int index = backwardMostMove;
12237         int has_book_hit = 0;
12238
12239         if( (index % 2) != side ) {
12240             index++;
12241         }
12242
12243         while( index < forwardMostMove ) {
12244             /* Check to see if engine is in book */
12245             int depth = pvInfoList[index].depth;
12246             int score = pvInfoList[index].score;
12247             int in_book = 0;
12248
12249             if( depth <= 2 ) {
12250                 in_book = 1;
12251             }
12252             else if( score == 0 && depth == 63 ) {
12253                 in_book = 1; /* Zappa */
12254             }
12255             else if( score == 2 && depth == 99 ) {
12256                 in_book = 1; /* Abrok */
12257             }
12258
12259             has_book_hit += in_book;
12260
12261             if( ! in_book ) {
12262                 result = index;
12263
12264                 break;
12265             }
12266
12267             index += 2;
12268         }
12269     }
12270
12271     return result;
12272 }
12273
12274 /* [AS] */
12275 void GetOutOfBookInfo( char * buf )
12276 {
12277     int oob[2];
12278     int i;
12279     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12280
12281     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12282     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12283
12284     *buf = '\0';
12285
12286     if( oob[0] >= 0 || oob[1] >= 0 ) {
12287         for( i=0; i<2; i++ ) {
12288             int idx = oob[i];
12289
12290             if( idx >= 0 ) {
12291                 if( i > 0 && oob[0] >= 0 ) {
12292                     strcat( buf, "   " );
12293                 }
12294
12295                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12296                 sprintf( buf+strlen(buf), "%s%.2f",
12297                     pvInfoList[idx].score >= 0 ? "+" : "",
12298                     pvInfoList[idx].score / 100.0 );
12299             }
12300         }
12301     }
12302 }
12303
12304 /* Save game in PGN style and close the file */
12305 int
12306 SaveGamePGN(f)
12307      FILE *f;
12308 {
12309     int i, offset, linelen, newblock;
12310     time_t tm;
12311 //    char *movetext;
12312     char numtext[32];
12313     int movelen, numlen, blank;
12314     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12315
12316     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12317
12318     tm = time((time_t *) NULL);
12319
12320     PrintPGNTags(f, &gameInfo);
12321
12322     if (backwardMostMove > 0 || startedFromSetupPosition) {
12323         char *fen = PositionToFEN(backwardMostMove, NULL);
12324         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12325         fprintf(f, "\n{--------------\n");
12326         PrintPosition(f, backwardMostMove);
12327         fprintf(f, "--------------}\n");
12328         free(fen);
12329     }
12330     else {
12331         /* [AS] Out of book annotation */
12332         if( appData.saveOutOfBookInfo ) {
12333             char buf[64];
12334
12335             GetOutOfBookInfo( buf );
12336
12337             if( buf[0] != '\0' ) {
12338                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12339             }
12340         }
12341
12342         fprintf(f, "\n");
12343     }
12344
12345     i = backwardMostMove;
12346     linelen = 0;
12347     newblock = TRUE;
12348
12349     while (i < forwardMostMove) {
12350         /* Print comments preceding this move */
12351         if (commentList[i] != NULL) {
12352             if (linelen > 0) fprintf(f, "\n");
12353             fprintf(f, "%s", commentList[i]);
12354             linelen = 0;
12355             newblock = TRUE;
12356         }
12357
12358         /* Format move number */
12359         if ((i % 2) == 0)
12360           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12361         else
12362           if (newblock)
12363             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12364           else
12365             numtext[0] = NULLCHAR;
12366
12367         numlen = strlen(numtext);
12368         newblock = FALSE;
12369
12370         /* Print move number */
12371         blank = linelen > 0 && numlen > 0;
12372         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12373             fprintf(f, "\n");
12374             linelen = 0;
12375             blank = 0;
12376         }
12377         if (blank) {
12378             fprintf(f, " ");
12379             linelen++;
12380         }
12381         fprintf(f, "%s", numtext);
12382         linelen += numlen;
12383
12384         /* Get move */
12385         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12386         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12387
12388         /* Print move */
12389         blank = linelen > 0 && movelen > 0;
12390         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12391             fprintf(f, "\n");
12392             linelen = 0;
12393             blank = 0;
12394         }
12395         if (blank) {
12396             fprintf(f, " ");
12397             linelen++;
12398         }
12399         fprintf(f, "%s", move_buffer);
12400         linelen += movelen;
12401
12402         /* [AS] Add PV info if present */
12403         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12404             /* [HGM] add time */
12405             char buf[MSG_SIZ]; int seconds;
12406
12407             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12408
12409             if( seconds <= 0)
12410               buf[0] = 0;
12411             else
12412               if( seconds < 30 )
12413                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12414               else
12415                 {
12416                   seconds = (seconds + 4)/10; // round to full seconds
12417                   if( seconds < 60 )
12418                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12419                   else
12420                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12421                 }
12422
12423             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12424                       pvInfoList[i].score >= 0 ? "+" : "",
12425                       pvInfoList[i].score / 100.0,
12426                       pvInfoList[i].depth,
12427                       buf );
12428
12429             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12430
12431             /* Print score/depth */
12432             blank = linelen > 0 && movelen > 0;
12433             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12434                 fprintf(f, "\n");
12435                 linelen = 0;
12436                 blank = 0;
12437             }
12438             if (blank) {
12439                 fprintf(f, " ");
12440                 linelen++;
12441             }
12442             fprintf(f, "%s", move_buffer);
12443             linelen += movelen;
12444         }
12445
12446         i++;
12447     }
12448
12449     /* Start a new line */
12450     if (linelen > 0) fprintf(f, "\n");
12451
12452     /* Print comments after last move */
12453     if (commentList[i] != NULL) {
12454         fprintf(f, "%s\n", commentList[i]);
12455     }
12456
12457     /* Print result */
12458     if (gameInfo.resultDetails != NULL &&
12459         gameInfo.resultDetails[0] != NULLCHAR) {
12460         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12461                 PGNResult(gameInfo.result));
12462     } else {
12463         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12464     }
12465
12466     fclose(f);
12467     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12468     return TRUE;
12469 }
12470
12471 /* Save game in old style and close the file */
12472 int
12473 SaveGameOldStyle(f)
12474      FILE *f;
12475 {
12476     int i, offset;
12477     time_t tm;
12478
12479     tm = time((time_t *) NULL);
12480
12481     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12482     PrintOpponents(f);
12483
12484     if (backwardMostMove > 0 || startedFromSetupPosition) {
12485         fprintf(f, "\n[--------------\n");
12486         PrintPosition(f, backwardMostMove);
12487         fprintf(f, "--------------]\n");
12488     } else {
12489         fprintf(f, "\n");
12490     }
12491
12492     i = backwardMostMove;
12493     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12494
12495     while (i < forwardMostMove) {
12496         if (commentList[i] != NULL) {
12497             fprintf(f, "[%s]\n", commentList[i]);
12498         }
12499
12500         if ((i % 2) == 1) {
12501             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12502             i++;
12503         } else {
12504             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12505             i++;
12506             if (commentList[i] != NULL) {
12507                 fprintf(f, "\n");
12508                 continue;
12509             }
12510             if (i >= forwardMostMove) {
12511                 fprintf(f, "\n");
12512                 break;
12513             }
12514             fprintf(f, "%s\n", parseList[i]);
12515             i++;
12516         }
12517     }
12518
12519     if (commentList[i] != NULL) {
12520         fprintf(f, "[%s]\n", commentList[i]);
12521     }
12522
12523     /* This isn't really the old style, but it's close enough */
12524     if (gameInfo.resultDetails != NULL &&
12525         gameInfo.resultDetails[0] != NULLCHAR) {
12526         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12527                 gameInfo.resultDetails);
12528     } else {
12529         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12530     }
12531
12532     fclose(f);
12533     return TRUE;
12534 }
12535
12536 /* Save the current game to open file f and close the file */
12537 int
12538 SaveGame(f, dummy, dummy2)
12539      FILE *f;
12540      int dummy;
12541      char *dummy2;
12542 {
12543     if (gameMode == EditPosition) EditPositionDone(TRUE);
12544     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12545     if (appData.oldSaveStyle)
12546       return SaveGameOldStyle(f);
12547     else
12548       return SaveGamePGN(f);
12549 }
12550
12551 /* Save the current position to the given file */
12552 int
12553 SavePositionToFile(filename)
12554      char *filename;
12555 {
12556     FILE *f;
12557     char buf[MSG_SIZ];
12558
12559     if (strcmp(filename, "-") == 0) {
12560         return SavePosition(stdout, 0, NULL);
12561     } else {
12562         f = fopen(filename, "a");
12563         if (f == NULL) {
12564             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12565             DisplayError(buf, errno);
12566             return FALSE;
12567         } else {
12568             safeStrCpy(buf, lastMsg, MSG_SIZ);
12569             DisplayMessage(_("Waiting for access to save file"), "");
12570             flock(fileno(f), LOCK_EX); // [HGM] lock
12571             DisplayMessage(_("Saving position"), "");
12572             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12573             SavePosition(f, 0, NULL);
12574             DisplayMessage(buf, "");
12575             return TRUE;
12576         }
12577     }
12578 }
12579
12580 /* Save the current position to the given open file and close the file */
12581 int
12582 SavePosition(f, dummy, dummy2)
12583      FILE *f;
12584      int dummy;
12585      char *dummy2;
12586 {
12587     time_t tm;
12588     char *fen;
12589
12590     if (gameMode == EditPosition) EditPositionDone(TRUE);
12591     if (appData.oldSaveStyle) {
12592         tm = time((time_t *) NULL);
12593
12594         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12595         PrintOpponents(f);
12596         fprintf(f, "[--------------\n");
12597         PrintPosition(f, currentMove);
12598         fprintf(f, "--------------]\n");
12599     } else {
12600         fen = PositionToFEN(currentMove, NULL);
12601         fprintf(f, "%s\n", fen);
12602         free(fen);
12603     }
12604     fclose(f);
12605     return TRUE;
12606 }
12607
12608 void
12609 ReloadCmailMsgEvent(unregister)
12610      int unregister;
12611 {
12612 #if !WIN32
12613     static char *inFilename = NULL;
12614     static char *outFilename;
12615     int i;
12616     struct stat inbuf, outbuf;
12617     int status;
12618
12619     /* Any registered moves are unregistered if unregister is set, */
12620     /* i.e. invoked by the signal handler */
12621     if (unregister) {
12622         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12623             cmailMoveRegistered[i] = FALSE;
12624             if (cmailCommentList[i] != NULL) {
12625                 free(cmailCommentList[i]);
12626                 cmailCommentList[i] = NULL;
12627             }
12628         }
12629         nCmailMovesRegistered = 0;
12630     }
12631
12632     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12633         cmailResult[i] = CMAIL_NOT_RESULT;
12634     }
12635     nCmailResults = 0;
12636
12637     if (inFilename == NULL) {
12638         /* Because the filenames are static they only get malloced once  */
12639         /* and they never get freed                                      */
12640         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12641         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12642
12643         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12644         sprintf(outFilename, "%s.out", appData.cmailGameName);
12645     }
12646
12647     status = stat(outFilename, &outbuf);
12648     if (status < 0) {
12649         cmailMailedMove = FALSE;
12650     } else {
12651         status = stat(inFilename, &inbuf);
12652         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12653     }
12654
12655     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12656        counts the games, notes how each one terminated, etc.
12657
12658        It would be nice to remove this kludge and instead gather all
12659        the information while building the game list.  (And to keep it
12660        in the game list nodes instead of having a bunch of fixed-size
12661        parallel arrays.)  Note this will require getting each game's
12662        termination from the PGN tags, as the game list builder does
12663        not process the game moves.  --mann
12664        */
12665     cmailMsgLoaded = TRUE;
12666     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12667
12668     /* Load first game in the file or popup game menu */
12669     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12670
12671 #endif /* !WIN32 */
12672     return;
12673 }
12674
12675 int
12676 RegisterMove()
12677 {
12678     FILE *f;
12679     char string[MSG_SIZ];
12680
12681     if (   cmailMailedMove
12682         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12683         return TRUE;            /* Allow free viewing  */
12684     }
12685
12686     /* Unregister move to ensure that we don't leave RegisterMove        */
12687     /* with the move registered when the conditions for registering no   */
12688     /* longer hold                                                       */
12689     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12690         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12691         nCmailMovesRegistered --;
12692
12693         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12694           {
12695               free(cmailCommentList[lastLoadGameNumber - 1]);
12696               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12697           }
12698     }
12699
12700     if (cmailOldMove == -1) {
12701         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12702         return FALSE;
12703     }
12704
12705     if (currentMove > cmailOldMove + 1) {
12706         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12707         return FALSE;
12708     }
12709
12710     if (currentMove < cmailOldMove) {
12711         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12712         return FALSE;
12713     }
12714
12715     if (forwardMostMove > currentMove) {
12716         /* Silently truncate extra moves */
12717         TruncateGame();
12718     }
12719
12720     if (   (currentMove == cmailOldMove + 1)
12721         || (   (currentMove == cmailOldMove)
12722             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12723                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12724         if (gameInfo.result != GameUnfinished) {
12725             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12726         }
12727
12728         if (commentList[currentMove] != NULL) {
12729             cmailCommentList[lastLoadGameNumber - 1]
12730               = StrSave(commentList[currentMove]);
12731         }
12732         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12733
12734         if (appData.debugMode)
12735           fprintf(debugFP, "Saving %s for game %d\n",
12736                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12737
12738         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12739
12740         f = fopen(string, "w");
12741         if (appData.oldSaveStyle) {
12742             SaveGameOldStyle(f); /* also closes the file */
12743
12744             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12745             f = fopen(string, "w");
12746             SavePosition(f, 0, NULL); /* also closes the file */
12747         } else {
12748             fprintf(f, "{--------------\n");
12749             PrintPosition(f, currentMove);
12750             fprintf(f, "--------------}\n\n");
12751
12752             SaveGame(f, 0, NULL); /* also closes the file*/
12753         }
12754
12755         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12756         nCmailMovesRegistered ++;
12757     } else if (nCmailGames == 1) {
12758         DisplayError(_("You have not made a move yet"), 0);
12759         return FALSE;
12760     }
12761
12762     return TRUE;
12763 }
12764
12765 void
12766 MailMoveEvent()
12767 {
12768 #if !WIN32
12769     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12770     FILE *commandOutput;
12771     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12772     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12773     int nBuffers;
12774     int i;
12775     int archived;
12776     char *arcDir;
12777
12778     if (! cmailMsgLoaded) {
12779         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12780         return;
12781     }
12782
12783     if (nCmailGames == nCmailResults) {
12784         DisplayError(_("No unfinished games"), 0);
12785         return;
12786     }
12787
12788 #if CMAIL_PROHIBIT_REMAIL
12789     if (cmailMailedMove) {
12790       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);
12791         DisplayError(msg, 0);
12792         return;
12793     }
12794 #endif
12795
12796     if (! (cmailMailedMove || RegisterMove())) return;
12797
12798     if (   cmailMailedMove
12799         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12800       snprintf(string, MSG_SIZ, partCommandString,
12801                appData.debugMode ? " -v" : "", appData.cmailGameName);
12802         commandOutput = popen(string, "r");
12803
12804         if (commandOutput == NULL) {
12805             DisplayError(_("Failed to invoke cmail"), 0);
12806         } else {
12807             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12808                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12809             }
12810             if (nBuffers > 1) {
12811                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12812                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12813                 nBytes = MSG_SIZ - 1;
12814             } else {
12815                 (void) memcpy(msg, buffer, nBytes);
12816             }
12817             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12818
12819             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12820                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12821
12822                 archived = TRUE;
12823                 for (i = 0; i < nCmailGames; i ++) {
12824                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12825                         archived = FALSE;
12826                     }
12827                 }
12828                 if (   archived
12829                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12830                         != NULL)) {
12831                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12832                            arcDir,
12833                            appData.cmailGameName,
12834                            gameInfo.date);
12835                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12836                     cmailMsgLoaded = FALSE;
12837                 }
12838             }
12839
12840             DisplayInformation(msg);
12841             pclose(commandOutput);
12842         }
12843     } else {
12844         if ((*cmailMsg) != '\0') {
12845             DisplayInformation(cmailMsg);
12846         }
12847     }
12848
12849     return;
12850 #endif /* !WIN32 */
12851 }
12852
12853 char *
12854 CmailMsg()
12855 {
12856 #if WIN32
12857     return NULL;
12858 #else
12859     int  prependComma = 0;
12860     char number[5];
12861     char string[MSG_SIZ];       /* Space for game-list */
12862     int  i;
12863
12864     if (!cmailMsgLoaded) return "";
12865
12866     if (cmailMailedMove) {
12867       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12868     } else {
12869         /* Create a list of games left */
12870       snprintf(string, MSG_SIZ, "[");
12871         for (i = 0; i < nCmailGames; i ++) {
12872             if (! (   cmailMoveRegistered[i]
12873                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12874                 if (prependComma) {
12875                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12876                 } else {
12877                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12878                     prependComma = 1;
12879                 }
12880
12881                 strcat(string, number);
12882             }
12883         }
12884         strcat(string, "]");
12885
12886         if (nCmailMovesRegistered + nCmailResults == 0) {
12887             switch (nCmailGames) {
12888               case 1:
12889                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12890                 break;
12891
12892               case 2:
12893                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12894                 break;
12895
12896               default:
12897                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12898                          nCmailGames);
12899                 break;
12900             }
12901         } else {
12902             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12903               case 1:
12904                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12905                          string);
12906                 break;
12907
12908               case 0:
12909                 if (nCmailResults == nCmailGames) {
12910                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12911                 } else {
12912                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12913                 }
12914                 break;
12915
12916               default:
12917                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12918                          string);
12919             }
12920         }
12921     }
12922     return cmailMsg;
12923 #endif /* WIN32 */
12924 }
12925
12926 void
12927 ResetGameEvent()
12928 {
12929     if (gameMode == Training)
12930       SetTrainingModeOff();
12931
12932     Reset(TRUE, TRUE);
12933     cmailMsgLoaded = FALSE;
12934     if (appData.icsActive) {
12935       SendToICS(ics_prefix);
12936       SendToICS("refresh\n");
12937     }
12938 }
12939
12940 void
12941 ExitEvent(status)
12942      int status;
12943 {
12944     exiting++;
12945     if (exiting > 2) {
12946       /* Give up on clean exit */
12947       exit(status);
12948     }
12949     if (exiting > 1) {
12950       /* Keep trying for clean exit */
12951       return;
12952     }
12953
12954     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12955
12956     if (telnetISR != NULL) {
12957       RemoveInputSource(telnetISR);
12958     }
12959     if (icsPR != NoProc) {
12960       DestroyChildProcess(icsPR, TRUE);
12961     }
12962
12963     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12964     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12965
12966     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12967     /* make sure this other one finishes before killing it!                  */
12968     if(endingGame) { int count = 0;
12969         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12970         while(endingGame && count++ < 10) DoSleep(1);
12971         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12972     }
12973
12974     /* Kill off chess programs */
12975     if (first.pr != NoProc) {
12976         ExitAnalyzeMode();
12977
12978         DoSleep( appData.delayBeforeQuit );
12979         SendToProgram("quit\n", &first);
12980         DoSleep( appData.delayAfterQuit );
12981         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12982     }
12983     if (second.pr != NoProc) {
12984         DoSleep( appData.delayBeforeQuit );
12985         SendToProgram("quit\n", &second);
12986         DoSleep( appData.delayAfterQuit );
12987         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12988     }
12989     if (first.isr != NULL) {
12990         RemoveInputSource(first.isr);
12991     }
12992     if (second.isr != NULL) {
12993         RemoveInputSource(second.isr);
12994     }
12995
12996     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12997     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12998
12999     ShutDownFrontEnd();
13000     exit(status);
13001 }
13002
13003 void
13004 PauseEvent()
13005 {
13006     if (appData.debugMode)
13007         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13008     if (pausing) {
13009         pausing = FALSE;
13010         ModeHighlight();
13011         if (gameMode == MachinePlaysWhite ||
13012             gameMode == MachinePlaysBlack) {
13013             StartClocks();
13014         } else {
13015             DisplayBothClocks();
13016         }
13017         if (gameMode == PlayFromGameFile) {
13018             if (appData.timeDelay >= 0)
13019                 AutoPlayGameLoop();
13020         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13021             Reset(FALSE, TRUE);
13022             SendToICS(ics_prefix);
13023             SendToICS("refresh\n");
13024         } else if (currentMove < forwardMostMove) {
13025             ForwardInner(forwardMostMove);
13026         }
13027         pauseExamInvalid = FALSE;
13028     } else {
13029         switch (gameMode) {
13030           default:
13031             return;
13032           case IcsExamining:
13033             pauseExamForwardMostMove = forwardMostMove;
13034             pauseExamInvalid = FALSE;
13035             /* fall through */
13036           case IcsObserving:
13037           case IcsPlayingWhite:
13038           case IcsPlayingBlack:
13039             pausing = TRUE;
13040             ModeHighlight();
13041             return;
13042           case PlayFromGameFile:
13043             (void) StopLoadGameTimer();
13044             pausing = TRUE;
13045             ModeHighlight();
13046             break;
13047           case BeginningOfGame:
13048             if (appData.icsActive) return;
13049             /* else fall through */
13050           case MachinePlaysWhite:
13051           case MachinePlaysBlack:
13052           case TwoMachinesPlay:
13053             if (forwardMostMove == 0)
13054               return;           /* don't pause if no one has moved */
13055             if ((gameMode == MachinePlaysWhite &&
13056                  !WhiteOnMove(forwardMostMove)) ||
13057                 (gameMode == MachinePlaysBlack &&
13058                  WhiteOnMove(forwardMostMove))) {
13059                 StopClocks();
13060             }
13061             pausing = TRUE;
13062             ModeHighlight();
13063             break;
13064         }
13065     }
13066 }
13067
13068 void
13069 EditCommentEvent()
13070 {
13071     char title[MSG_SIZ];
13072
13073     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13074       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13075     } else {
13076       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13077                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13078                parseList[currentMove - 1]);
13079     }
13080
13081     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13082 }
13083
13084
13085 void
13086 EditTagsEvent()
13087 {
13088     char *tags = PGNTags(&gameInfo);
13089     bookUp = FALSE;
13090     EditTagsPopUp(tags, NULL);
13091     free(tags);
13092 }
13093
13094 void
13095 AnalyzeModeEvent()
13096 {
13097     if (appData.noChessProgram || gameMode == AnalyzeMode)
13098       return;
13099
13100     if (gameMode != AnalyzeFile) {
13101         if (!appData.icsEngineAnalyze) {
13102                EditGameEvent();
13103                if (gameMode != EditGame) return;
13104         }
13105         ResurrectChessProgram();
13106         SendToProgram("analyze\n", &first);
13107         first.analyzing = TRUE;
13108         /*first.maybeThinking = TRUE;*/
13109         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13110         EngineOutputPopUp();
13111     }
13112     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13113     pausing = FALSE;
13114     ModeHighlight();
13115     SetGameInfo();
13116
13117     StartAnalysisClock();
13118     GetTimeMark(&lastNodeCountTime);
13119     lastNodeCount = 0;
13120 }
13121
13122 void
13123 AnalyzeFileEvent()
13124 {
13125     if (appData.noChessProgram || gameMode == AnalyzeFile)
13126       return;
13127
13128     if (gameMode != AnalyzeMode) {
13129         EditGameEvent();
13130         if (gameMode != EditGame) return;
13131         ResurrectChessProgram();
13132         SendToProgram("analyze\n", &first);
13133         first.analyzing = TRUE;
13134         /*first.maybeThinking = TRUE;*/
13135         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13136         EngineOutputPopUp();
13137     }
13138     gameMode = AnalyzeFile;
13139     pausing = FALSE;
13140     ModeHighlight();
13141     SetGameInfo();
13142
13143     StartAnalysisClock();
13144     GetTimeMark(&lastNodeCountTime);
13145     lastNodeCount = 0;
13146     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13147 }
13148
13149 void
13150 MachineWhiteEvent()
13151 {
13152     char buf[MSG_SIZ];
13153     char *bookHit = NULL;
13154
13155     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13156       return;
13157
13158
13159     if (gameMode == PlayFromGameFile ||
13160         gameMode == TwoMachinesPlay  ||
13161         gameMode == Training         ||
13162         gameMode == AnalyzeMode      ||
13163         gameMode == EndOfGame)
13164         EditGameEvent();
13165
13166     if (gameMode == EditPosition)
13167         EditPositionDone(TRUE);
13168
13169     if (!WhiteOnMove(currentMove)) {
13170         DisplayError(_("It is not White's turn"), 0);
13171         return;
13172     }
13173
13174     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13175       ExitAnalyzeMode();
13176
13177     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13178         gameMode == AnalyzeFile)
13179         TruncateGame();
13180
13181     ResurrectChessProgram();    /* in case it isn't running */
13182     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13183         gameMode = MachinePlaysWhite;
13184         ResetClocks();
13185     } else
13186     gameMode = MachinePlaysWhite;
13187     pausing = FALSE;
13188     ModeHighlight();
13189     SetGameInfo();
13190     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13191     DisplayTitle(buf);
13192     if (first.sendName) {
13193       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13194       SendToProgram(buf, &first);
13195     }
13196     if (first.sendTime) {
13197       if (first.useColors) {
13198         SendToProgram("black\n", &first); /*gnu kludge*/
13199       }
13200       SendTimeRemaining(&first, TRUE);
13201     }
13202     if (first.useColors) {
13203       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13204     }
13205     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13206     SetMachineThinkingEnables();
13207     first.maybeThinking = TRUE;
13208     StartClocks();
13209     firstMove = FALSE;
13210
13211     if (appData.autoFlipView && !flipView) {
13212       flipView = !flipView;
13213       DrawPosition(FALSE, NULL);
13214       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13215     }
13216
13217     if(bookHit) { // [HGM] book: simulate book reply
13218         static char bookMove[MSG_SIZ]; // a bit generous?
13219
13220         programStats.nodes = programStats.depth = programStats.time =
13221         programStats.score = programStats.got_only_move = 0;
13222         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13223
13224         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13225         strcat(bookMove, bookHit);
13226         HandleMachineMove(bookMove, &first);
13227     }
13228 }
13229
13230 void
13231 MachineBlackEvent()
13232 {
13233   char buf[MSG_SIZ];
13234   char *bookHit = NULL;
13235
13236     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13237         return;
13238
13239
13240     if (gameMode == PlayFromGameFile ||
13241         gameMode == TwoMachinesPlay  ||
13242         gameMode == Training         ||
13243         gameMode == AnalyzeMode      ||
13244         gameMode == EndOfGame)
13245         EditGameEvent();
13246
13247     if (gameMode == EditPosition)
13248         EditPositionDone(TRUE);
13249
13250     if (WhiteOnMove(currentMove)) {
13251         DisplayError(_("It is not Black's turn"), 0);
13252         return;
13253     }
13254
13255     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13256       ExitAnalyzeMode();
13257
13258     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13259         gameMode == AnalyzeFile)
13260         TruncateGame();
13261
13262     ResurrectChessProgram();    /* in case it isn't running */
13263     gameMode = MachinePlaysBlack;
13264     pausing = FALSE;
13265     ModeHighlight();
13266     SetGameInfo();
13267     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13268     DisplayTitle(buf);
13269     if (first.sendName) {
13270       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13271       SendToProgram(buf, &first);
13272     }
13273     if (first.sendTime) {
13274       if (first.useColors) {
13275         SendToProgram("white\n", &first); /*gnu kludge*/
13276       }
13277       SendTimeRemaining(&first, FALSE);
13278     }
13279     if (first.useColors) {
13280       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13281     }
13282     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13283     SetMachineThinkingEnables();
13284     first.maybeThinking = TRUE;
13285     StartClocks();
13286
13287     if (appData.autoFlipView && flipView) {
13288       flipView = !flipView;
13289       DrawPosition(FALSE, NULL);
13290       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13291     }
13292     if(bookHit) { // [HGM] book: simulate book reply
13293         static char bookMove[MSG_SIZ]; // a bit generous?
13294
13295         programStats.nodes = programStats.depth = programStats.time =
13296         programStats.score = programStats.got_only_move = 0;
13297         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13298
13299         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13300         strcat(bookMove, bookHit);
13301         HandleMachineMove(bookMove, &first);
13302     }
13303 }
13304
13305
13306 void
13307 DisplayTwoMachinesTitle()
13308 {
13309     char buf[MSG_SIZ];
13310     if (appData.matchGames > 0) {
13311         if(appData.tourneyFile[0]) {
13312           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13313                    gameInfo.white, gameInfo.black,
13314                    nextGame+1, appData.matchGames+1,
13315                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13316         } else 
13317         if (first.twoMachinesColor[0] == 'w') {
13318           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13319                    gameInfo.white, gameInfo.black,
13320                    first.matchWins, second.matchWins,
13321                    matchGame - 1 - (first.matchWins + second.matchWins));
13322         } else {
13323           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13324                    gameInfo.white, gameInfo.black,
13325                    second.matchWins, first.matchWins,
13326                    matchGame - 1 - (first.matchWins + second.matchWins));
13327         }
13328     } else {
13329       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13330     }
13331     DisplayTitle(buf);
13332 }
13333
13334 void
13335 SettingsMenuIfReady()
13336 {
13337   if (second.lastPing != second.lastPong) {
13338     DisplayMessage("", _("Waiting for second chess program"));
13339     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13340     return;
13341   }
13342   ThawUI();
13343   DisplayMessage("", "");
13344   SettingsPopUp(&second);
13345 }
13346
13347 int
13348 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13349 {
13350     char buf[MSG_SIZ];
13351     if (cps->pr == NoProc) {
13352         StartChessProgram(cps);
13353         if (cps->protocolVersion == 1) {
13354           retry();
13355         } else {
13356           /* kludge: allow timeout for initial "feature" command */
13357           FreezeUI();
13358           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13359           DisplayMessage("", buf);
13360           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13361         }
13362         return 1;
13363     }
13364     return 0;
13365 }
13366
13367 void
13368 TwoMachinesEvent P((void))
13369 {
13370     int i;
13371     char buf[MSG_SIZ];
13372     ChessProgramState *onmove;
13373     char *bookHit = NULL;
13374     static int stalling = 0;
13375     TimeMark now;
13376     long wait;
13377
13378     if (appData.noChessProgram) return;
13379
13380     switch (gameMode) {
13381       case TwoMachinesPlay:
13382         return;
13383       case MachinePlaysWhite:
13384       case MachinePlaysBlack:
13385         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13386             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13387             return;
13388         }
13389         /* fall through */
13390       case BeginningOfGame:
13391       case PlayFromGameFile:
13392       case EndOfGame:
13393         EditGameEvent();
13394         if (gameMode != EditGame) return;
13395         break;
13396       case EditPosition:
13397         EditPositionDone(TRUE);
13398         break;
13399       case AnalyzeMode:
13400       case AnalyzeFile:
13401         ExitAnalyzeMode();
13402         break;
13403       case EditGame:
13404       default:
13405         break;
13406     }
13407
13408 //    forwardMostMove = currentMove;
13409     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13410
13411     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13412
13413     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13414     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13415       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13416       return;
13417     }
13418     if(!stalling) {
13419       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13420       SendToProgram("force\n", &second);
13421       stalling = 1;
13422       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13423       return;
13424     }
13425     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13426     if(appData.matchPause>10000 || appData.matchPause<10)
13427                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13428     wait = SubtractTimeMarks(&now, &pauseStart);
13429     if(wait < appData.matchPause) {
13430         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13431         return;
13432     }
13433     stalling = 0;
13434     DisplayMessage("", "");
13435     if (startedFromSetupPosition) {
13436         SendBoard(&second, backwardMostMove);
13437     if (appData.debugMode) {
13438         fprintf(debugFP, "Two Machines\n");
13439     }
13440     }
13441     for (i = backwardMostMove; i < forwardMostMove; i++) {
13442         SendMoveToProgram(i, &second);
13443     }
13444
13445     gameMode = TwoMachinesPlay;
13446     pausing = FALSE;
13447     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13448     SetGameInfo();
13449     DisplayTwoMachinesTitle();
13450     firstMove = TRUE;
13451     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13452         onmove = &first;
13453     } else {
13454         onmove = &second;
13455     }
13456     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13457     SendToProgram(first.computerString, &first);
13458     if (first.sendName) {
13459       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13460       SendToProgram(buf, &first);
13461     }
13462     SendToProgram(second.computerString, &second);
13463     if (second.sendName) {
13464       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13465       SendToProgram(buf, &second);
13466     }
13467
13468     ResetClocks();
13469     if (!first.sendTime || !second.sendTime) {
13470         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13471         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13472     }
13473     if (onmove->sendTime) {
13474       if (onmove->useColors) {
13475         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13476       }
13477       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13478     }
13479     if (onmove->useColors) {
13480       SendToProgram(onmove->twoMachinesColor, onmove);
13481     }
13482     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13483 //    SendToProgram("go\n", onmove);
13484     onmove->maybeThinking = TRUE;
13485     SetMachineThinkingEnables();
13486
13487     StartClocks();
13488
13489     if(bookHit) { // [HGM] book: simulate book reply
13490         static char bookMove[MSG_SIZ]; // a bit generous?
13491
13492         programStats.nodes = programStats.depth = programStats.time =
13493         programStats.score = programStats.got_only_move = 0;
13494         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13495
13496         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13497         strcat(bookMove, bookHit);
13498         savedMessage = bookMove; // args for deferred call
13499         savedState = onmove;
13500         ScheduleDelayedEvent(DeferredBookMove, 1);
13501     }
13502 }
13503
13504 void
13505 TrainingEvent()
13506 {
13507     if (gameMode == Training) {
13508       SetTrainingModeOff();
13509       gameMode = PlayFromGameFile;
13510       DisplayMessage("", _("Training mode off"));
13511     } else {
13512       gameMode = Training;
13513       animateTraining = appData.animate;
13514
13515       /* make sure we are not already at the end of the game */
13516       if (currentMove < forwardMostMove) {
13517         SetTrainingModeOn();
13518         DisplayMessage("", _("Training mode on"));
13519       } else {
13520         gameMode = PlayFromGameFile;
13521         DisplayError(_("Already at end of game"), 0);
13522       }
13523     }
13524     ModeHighlight();
13525 }
13526
13527 void
13528 IcsClientEvent()
13529 {
13530     if (!appData.icsActive) return;
13531     switch (gameMode) {
13532       case IcsPlayingWhite:
13533       case IcsPlayingBlack:
13534       case IcsObserving:
13535       case IcsIdle:
13536       case BeginningOfGame:
13537       case IcsExamining:
13538         return;
13539
13540       case EditGame:
13541         break;
13542
13543       case EditPosition:
13544         EditPositionDone(TRUE);
13545         break;
13546
13547       case AnalyzeMode:
13548       case AnalyzeFile:
13549         ExitAnalyzeMode();
13550         break;
13551
13552       default:
13553         EditGameEvent();
13554         break;
13555     }
13556
13557     gameMode = IcsIdle;
13558     ModeHighlight();
13559     return;
13560 }
13561
13562
13563 void
13564 EditGameEvent()
13565 {
13566     int i;
13567
13568     switch (gameMode) {
13569       case Training:
13570         SetTrainingModeOff();
13571         break;
13572       case MachinePlaysWhite:
13573       case MachinePlaysBlack:
13574       case BeginningOfGame:
13575         SendToProgram("force\n", &first);
13576         SetUserThinkingEnables();
13577         break;
13578       case PlayFromGameFile:
13579         (void) StopLoadGameTimer();
13580         if (gameFileFP != NULL) {
13581             gameFileFP = NULL;
13582         }
13583         break;
13584       case EditPosition:
13585         EditPositionDone(TRUE);
13586         break;
13587       case AnalyzeMode:
13588       case AnalyzeFile:
13589         ExitAnalyzeMode();
13590         SendToProgram("force\n", &first);
13591         break;
13592       case TwoMachinesPlay:
13593         GameEnds(EndOfFile, NULL, GE_PLAYER);
13594         ResurrectChessProgram();
13595         SetUserThinkingEnables();
13596         break;
13597       case EndOfGame:
13598         ResurrectChessProgram();
13599         break;
13600       case IcsPlayingBlack:
13601       case IcsPlayingWhite:
13602         DisplayError(_("Warning: You are still playing a game"), 0);
13603         break;
13604       case IcsObserving:
13605         DisplayError(_("Warning: You are still observing a game"), 0);
13606         break;
13607       case IcsExamining:
13608         DisplayError(_("Warning: You are still examining a game"), 0);
13609         break;
13610       case IcsIdle:
13611         break;
13612       case EditGame:
13613       default:
13614         return;
13615     }
13616
13617     pausing = FALSE;
13618     StopClocks();
13619     first.offeredDraw = second.offeredDraw = 0;
13620
13621     if (gameMode == PlayFromGameFile) {
13622         whiteTimeRemaining = timeRemaining[0][currentMove];
13623         blackTimeRemaining = timeRemaining[1][currentMove];
13624         DisplayTitle("");
13625     }
13626
13627     if (gameMode == MachinePlaysWhite ||
13628         gameMode == MachinePlaysBlack ||
13629         gameMode == TwoMachinesPlay ||
13630         gameMode == EndOfGame) {
13631         i = forwardMostMove;
13632         while (i > currentMove) {
13633             SendToProgram("undo\n", &first);
13634             i--;
13635         }
13636         if(!adjustedClock) {
13637         whiteTimeRemaining = timeRemaining[0][currentMove];
13638         blackTimeRemaining = timeRemaining[1][currentMove];
13639         DisplayBothClocks();
13640         }
13641         if (whiteFlag || blackFlag) {
13642             whiteFlag = blackFlag = 0;
13643         }
13644         DisplayTitle("");
13645     }
13646
13647     gameMode = EditGame;
13648     ModeHighlight();
13649     SetGameInfo();
13650 }
13651
13652
13653 void
13654 EditPositionEvent()
13655 {
13656     if (gameMode == EditPosition) {
13657         EditGameEvent();
13658         return;
13659     }
13660
13661     EditGameEvent();
13662     if (gameMode != EditGame) return;
13663
13664     gameMode = EditPosition;
13665     ModeHighlight();
13666     SetGameInfo();
13667     if (currentMove > 0)
13668       CopyBoard(boards[0], boards[currentMove]);
13669
13670     blackPlaysFirst = !WhiteOnMove(currentMove);
13671     ResetClocks();
13672     currentMove = forwardMostMove = backwardMostMove = 0;
13673     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13674     DisplayMove(-1);
13675 }
13676
13677 void
13678 ExitAnalyzeMode()
13679 {
13680     /* [DM] icsEngineAnalyze - possible call from other functions */
13681     if (appData.icsEngineAnalyze) {
13682         appData.icsEngineAnalyze = FALSE;
13683
13684         DisplayMessage("",_("Close ICS engine analyze..."));
13685     }
13686     if (first.analysisSupport && first.analyzing) {
13687       SendToProgram("exit\n", &first);
13688       first.analyzing = FALSE;
13689     }
13690     thinkOutput[0] = NULLCHAR;
13691 }
13692
13693 void
13694 EditPositionDone(Boolean fakeRights)
13695 {
13696     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13697
13698     startedFromSetupPosition = TRUE;
13699     InitChessProgram(&first, FALSE);
13700     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13701       boards[0][EP_STATUS] = EP_NONE;
13702       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13703     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13704         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13705         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13706       } else boards[0][CASTLING][2] = NoRights;
13707     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13708         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13709         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13710       } else boards[0][CASTLING][5] = NoRights;
13711     }
13712     SendToProgram("force\n", &first);
13713     if (blackPlaysFirst) {
13714         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13715         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13716         currentMove = forwardMostMove = backwardMostMove = 1;
13717         CopyBoard(boards[1], boards[0]);
13718     } else {
13719         currentMove = forwardMostMove = backwardMostMove = 0;
13720     }
13721     SendBoard(&first, forwardMostMove);
13722     if (appData.debugMode) {
13723         fprintf(debugFP, "EditPosDone\n");
13724     }
13725     DisplayTitle("");
13726     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13727     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13728     gameMode = EditGame;
13729     ModeHighlight();
13730     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13731     ClearHighlights(); /* [AS] */
13732 }
13733
13734 /* Pause for `ms' milliseconds */
13735 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13736 void
13737 TimeDelay(ms)
13738      long ms;
13739 {
13740     TimeMark m1, m2;
13741
13742     GetTimeMark(&m1);
13743     do {
13744         GetTimeMark(&m2);
13745     } while (SubtractTimeMarks(&m2, &m1) < ms);
13746 }
13747
13748 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13749 void
13750 SendMultiLineToICS(buf)
13751      char *buf;
13752 {
13753     char temp[MSG_SIZ+1], *p;
13754     int len;
13755
13756     len = strlen(buf);
13757     if (len > MSG_SIZ)
13758       len = MSG_SIZ;
13759
13760     strncpy(temp, buf, len);
13761     temp[len] = 0;
13762
13763     p = temp;
13764     while (*p) {
13765         if (*p == '\n' || *p == '\r')
13766           *p = ' ';
13767         ++p;
13768     }
13769
13770     strcat(temp, "\n");
13771     SendToICS(temp);
13772     SendToPlayer(temp, strlen(temp));
13773 }
13774
13775 void
13776 SetWhiteToPlayEvent()
13777 {
13778     if (gameMode == EditPosition) {
13779         blackPlaysFirst = FALSE;
13780         DisplayBothClocks();    /* works because currentMove is 0 */
13781     } else if (gameMode == IcsExamining) {
13782         SendToICS(ics_prefix);
13783         SendToICS("tomove white\n");
13784     }
13785 }
13786
13787 void
13788 SetBlackToPlayEvent()
13789 {
13790     if (gameMode == EditPosition) {
13791         blackPlaysFirst = TRUE;
13792         currentMove = 1;        /* kludge */
13793         DisplayBothClocks();
13794         currentMove = 0;
13795     } else if (gameMode == IcsExamining) {
13796         SendToICS(ics_prefix);
13797         SendToICS("tomove black\n");
13798     }
13799 }
13800
13801 void
13802 EditPositionMenuEvent(selection, x, y)
13803      ChessSquare selection;
13804      int x, y;
13805 {
13806     char buf[MSG_SIZ];
13807     ChessSquare piece = boards[0][y][x];
13808
13809     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13810
13811     switch (selection) {
13812       case ClearBoard:
13813         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13814             SendToICS(ics_prefix);
13815             SendToICS("bsetup clear\n");
13816         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13817             SendToICS(ics_prefix);
13818             SendToICS("clearboard\n");
13819         } else {
13820             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13821                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13822                 for (y = 0; y < BOARD_HEIGHT; y++) {
13823                     if (gameMode == IcsExamining) {
13824                         if (boards[currentMove][y][x] != EmptySquare) {
13825                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13826                                     AAA + x, ONE + y);
13827                             SendToICS(buf);
13828                         }
13829                     } else {
13830                         boards[0][y][x] = p;
13831                     }
13832                 }
13833             }
13834         }
13835         if (gameMode == EditPosition) {
13836             DrawPosition(FALSE, boards[0]);
13837         }
13838         break;
13839
13840       case WhitePlay:
13841         SetWhiteToPlayEvent();
13842         break;
13843
13844       case BlackPlay:
13845         SetBlackToPlayEvent();
13846         break;
13847
13848       case EmptySquare:
13849         if (gameMode == IcsExamining) {
13850             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13851             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13852             SendToICS(buf);
13853         } else {
13854             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13855                 if(x == BOARD_LEFT-2) {
13856                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13857                     boards[0][y][1] = 0;
13858                 } else
13859                 if(x == BOARD_RGHT+1) {
13860                     if(y >= gameInfo.holdingsSize) break;
13861                     boards[0][y][BOARD_WIDTH-2] = 0;
13862                 } else break;
13863             }
13864             boards[0][y][x] = EmptySquare;
13865             DrawPosition(FALSE, boards[0]);
13866         }
13867         break;
13868
13869       case PromotePiece:
13870         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13871            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13872             selection = (ChessSquare) (PROMOTED piece);
13873         } else if(piece == EmptySquare) selection = WhiteSilver;
13874         else selection = (ChessSquare)((int)piece - 1);
13875         goto defaultlabel;
13876
13877       case DemotePiece:
13878         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13879            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13880             selection = (ChessSquare) (DEMOTED piece);
13881         } else if(piece == EmptySquare) selection = BlackSilver;
13882         else selection = (ChessSquare)((int)piece + 1);
13883         goto defaultlabel;
13884
13885       case WhiteQueen:
13886       case BlackQueen:
13887         if(gameInfo.variant == VariantShatranj ||
13888            gameInfo.variant == VariantXiangqi  ||
13889            gameInfo.variant == VariantCourier  ||
13890            gameInfo.variant == VariantMakruk     )
13891             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13892         goto defaultlabel;
13893
13894       case WhiteKing:
13895       case BlackKing:
13896         if(gameInfo.variant == VariantXiangqi)
13897             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13898         if(gameInfo.variant == VariantKnightmate)
13899             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13900       default:
13901         defaultlabel:
13902         if (gameMode == IcsExamining) {
13903             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13904             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13905                      PieceToChar(selection), AAA + x, ONE + y);
13906             SendToICS(buf);
13907         } else {
13908             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13909                 int n;
13910                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13911                     n = PieceToNumber(selection - BlackPawn);
13912                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13913                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13914                     boards[0][BOARD_HEIGHT-1-n][1]++;
13915                 } else
13916                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13917                     n = PieceToNumber(selection);
13918                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13919                     boards[0][n][BOARD_WIDTH-1] = selection;
13920                     boards[0][n][BOARD_WIDTH-2]++;
13921                 }
13922             } else
13923             boards[0][y][x] = selection;
13924             DrawPosition(TRUE, boards[0]);
13925         }
13926         break;
13927     }
13928 }
13929
13930
13931 void
13932 DropMenuEvent(selection, x, y)
13933      ChessSquare selection;
13934      int x, y;
13935 {
13936     ChessMove moveType;
13937
13938     switch (gameMode) {
13939       case IcsPlayingWhite:
13940       case MachinePlaysBlack:
13941         if (!WhiteOnMove(currentMove)) {
13942             DisplayMoveError(_("It is Black's turn"));
13943             return;
13944         }
13945         moveType = WhiteDrop;
13946         break;
13947       case IcsPlayingBlack:
13948       case MachinePlaysWhite:
13949         if (WhiteOnMove(currentMove)) {
13950             DisplayMoveError(_("It is White's turn"));
13951             return;
13952         }
13953         moveType = BlackDrop;
13954         break;
13955       case EditGame:
13956         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13957         break;
13958       default:
13959         return;
13960     }
13961
13962     if (moveType == BlackDrop && selection < BlackPawn) {
13963       selection = (ChessSquare) ((int) selection
13964                                  + (int) BlackPawn - (int) WhitePawn);
13965     }
13966     if (boards[currentMove][y][x] != EmptySquare) {
13967         DisplayMoveError(_("That square is occupied"));
13968         return;
13969     }
13970
13971     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13972 }
13973
13974 void
13975 AcceptEvent()
13976 {
13977     /* Accept a pending offer of any kind from opponent */
13978
13979     if (appData.icsActive) {
13980         SendToICS(ics_prefix);
13981         SendToICS("accept\n");
13982     } else if (cmailMsgLoaded) {
13983         if (currentMove == cmailOldMove &&
13984             commentList[cmailOldMove] != NULL &&
13985             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13986                    "Black offers a draw" : "White offers a draw")) {
13987             TruncateGame();
13988             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13989             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13990         } else {
13991             DisplayError(_("There is no pending offer on this move"), 0);
13992             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13993         }
13994     } else {
13995         /* Not used for offers from chess program */
13996     }
13997 }
13998
13999 void
14000 DeclineEvent()
14001 {
14002     /* Decline a pending offer of any kind from opponent */
14003
14004     if (appData.icsActive) {
14005         SendToICS(ics_prefix);
14006         SendToICS("decline\n");
14007     } else if (cmailMsgLoaded) {
14008         if (currentMove == cmailOldMove &&
14009             commentList[cmailOldMove] != NULL &&
14010             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14011                    "Black offers a draw" : "White offers a draw")) {
14012 #ifdef NOTDEF
14013             AppendComment(cmailOldMove, "Draw declined", TRUE);
14014             DisplayComment(cmailOldMove - 1, "Draw declined");
14015 #endif /*NOTDEF*/
14016         } else {
14017             DisplayError(_("There is no pending offer on this move"), 0);
14018         }
14019     } else {
14020         /* Not used for offers from chess program */
14021     }
14022 }
14023
14024 void
14025 RematchEvent()
14026 {
14027     /* Issue ICS rematch command */
14028     if (appData.icsActive) {
14029         SendToICS(ics_prefix);
14030         SendToICS("rematch\n");
14031     }
14032 }
14033
14034 void
14035 CallFlagEvent()
14036 {
14037     /* Call your opponent's flag (claim a win on time) */
14038     if (appData.icsActive) {
14039         SendToICS(ics_prefix);
14040         SendToICS("flag\n");
14041     } else {
14042         switch (gameMode) {
14043           default:
14044             return;
14045           case MachinePlaysWhite:
14046             if (whiteFlag) {
14047                 if (blackFlag)
14048                   GameEnds(GameIsDrawn, "Both players ran out of time",
14049                            GE_PLAYER);
14050                 else
14051                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14052             } else {
14053                 DisplayError(_("Your opponent is not out of time"), 0);
14054             }
14055             break;
14056           case MachinePlaysBlack:
14057             if (blackFlag) {
14058                 if (whiteFlag)
14059                   GameEnds(GameIsDrawn, "Both players ran out of time",
14060                            GE_PLAYER);
14061                 else
14062                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14063             } else {
14064                 DisplayError(_("Your opponent is not out of time"), 0);
14065             }
14066             break;
14067         }
14068     }
14069 }
14070
14071 void
14072 ClockClick(int which)
14073 {       // [HGM] code moved to back-end from winboard.c
14074         if(which) { // black clock
14075           if (gameMode == EditPosition || gameMode == IcsExamining) {
14076             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14077             SetBlackToPlayEvent();
14078           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14079           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14080           } else if (shiftKey) {
14081             AdjustClock(which, -1);
14082           } else if (gameMode == IcsPlayingWhite ||
14083                      gameMode == MachinePlaysBlack) {
14084             CallFlagEvent();
14085           }
14086         } else { // white clock
14087           if (gameMode == EditPosition || gameMode == IcsExamining) {
14088             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14089             SetWhiteToPlayEvent();
14090           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14091           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14092           } else if (shiftKey) {
14093             AdjustClock(which, -1);
14094           } else if (gameMode == IcsPlayingBlack ||
14095                    gameMode == MachinePlaysWhite) {
14096             CallFlagEvent();
14097           }
14098         }
14099 }
14100
14101 void
14102 DrawEvent()
14103 {
14104     /* Offer draw or accept pending draw offer from opponent */
14105
14106     if (appData.icsActive) {
14107         /* Note: tournament rules require draw offers to be
14108            made after you make your move but before you punch
14109            your clock.  Currently ICS doesn't let you do that;
14110            instead, you immediately punch your clock after making
14111            a move, but you can offer a draw at any time. */
14112
14113         SendToICS(ics_prefix);
14114         SendToICS("draw\n");
14115         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14116     } else if (cmailMsgLoaded) {
14117         if (currentMove == cmailOldMove &&
14118             commentList[cmailOldMove] != NULL &&
14119             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14120                    "Black offers a draw" : "White offers a draw")) {
14121             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14122             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14123         } else if (currentMove == cmailOldMove + 1) {
14124             char *offer = WhiteOnMove(cmailOldMove) ?
14125               "White offers a draw" : "Black offers a draw";
14126             AppendComment(currentMove, offer, TRUE);
14127             DisplayComment(currentMove - 1, offer);
14128             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14129         } else {
14130             DisplayError(_("You must make your move before offering a draw"), 0);
14131             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14132         }
14133     } else if (first.offeredDraw) {
14134         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14135     } else {
14136         if (first.sendDrawOffers) {
14137             SendToProgram("draw\n", &first);
14138             userOfferedDraw = TRUE;
14139         }
14140     }
14141 }
14142
14143 void
14144 AdjournEvent()
14145 {
14146     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14147
14148     if (appData.icsActive) {
14149         SendToICS(ics_prefix);
14150         SendToICS("adjourn\n");
14151     } else {
14152         /* Currently GNU Chess doesn't offer or accept Adjourns */
14153     }
14154 }
14155
14156
14157 void
14158 AbortEvent()
14159 {
14160     /* Offer Abort or accept pending Abort offer from opponent */
14161
14162     if (appData.icsActive) {
14163         SendToICS(ics_prefix);
14164         SendToICS("abort\n");
14165     } else {
14166         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14167     }
14168 }
14169
14170 void
14171 ResignEvent()
14172 {
14173     /* Resign.  You can do this even if it's not your turn. */
14174
14175     if (appData.icsActive) {
14176         SendToICS(ics_prefix);
14177         SendToICS("resign\n");
14178     } else {
14179         switch (gameMode) {
14180           case MachinePlaysWhite:
14181             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14182             break;
14183           case MachinePlaysBlack:
14184             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14185             break;
14186           case EditGame:
14187             if (cmailMsgLoaded) {
14188                 TruncateGame();
14189                 if (WhiteOnMove(cmailOldMove)) {
14190                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14191                 } else {
14192                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14193                 }
14194                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14195             }
14196             break;
14197           default:
14198             break;
14199         }
14200     }
14201 }
14202
14203
14204 void
14205 StopObservingEvent()
14206 {
14207     /* Stop observing current games */
14208     SendToICS(ics_prefix);
14209     SendToICS("unobserve\n");
14210 }
14211
14212 void
14213 StopExaminingEvent()
14214 {
14215     /* Stop observing current game */
14216     SendToICS(ics_prefix);
14217     SendToICS("unexamine\n");
14218 }
14219
14220 void
14221 ForwardInner(target)
14222      int target;
14223 {
14224     int limit;
14225
14226     if (appData.debugMode)
14227         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14228                 target, currentMove, forwardMostMove);
14229
14230     if (gameMode == EditPosition)
14231       return;
14232
14233     MarkTargetSquares(1);
14234
14235     if (gameMode == PlayFromGameFile && !pausing)
14236       PauseEvent();
14237
14238     if (gameMode == IcsExamining && pausing)
14239       limit = pauseExamForwardMostMove;
14240     else
14241       limit = forwardMostMove;
14242
14243     if (target > limit) target = limit;
14244
14245     if (target > 0 && moveList[target - 1][0]) {
14246         int fromX, fromY, toX, toY;
14247         toX = moveList[target - 1][2] - AAA;
14248         toY = moveList[target - 1][3] - ONE;
14249         if (moveList[target - 1][1] == '@') {
14250             if (appData.highlightLastMove) {
14251                 SetHighlights(-1, -1, toX, toY);
14252             }
14253         } else {
14254             fromX = moveList[target - 1][0] - AAA;
14255             fromY = moveList[target - 1][1] - ONE;
14256             if (target == currentMove + 1) {
14257                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14258             }
14259             if (appData.highlightLastMove) {
14260                 SetHighlights(fromX, fromY, toX, toY);
14261             }
14262         }
14263     }
14264     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14265         gameMode == Training || gameMode == PlayFromGameFile ||
14266         gameMode == AnalyzeFile) {
14267         while (currentMove < target) {
14268             SendMoveToProgram(currentMove++, &first);
14269         }
14270     } else {
14271         currentMove = target;
14272     }
14273
14274     if (gameMode == EditGame || gameMode == EndOfGame) {
14275         whiteTimeRemaining = timeRemaining[0][currentMove];
14276         blackTimeRemaining = timeRemaining[1][currentMove];
14277     }
14278     DisplayBothClocks();
14279     DisplayMove(currentMove - 1);
14280     DrawPosition(FALSE, boards[currentMove]);
14281     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14282     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14283         DisplayComment(currentMove - 1, commentList[currentMove]);
14284     }
14285 }
14286
14287
14288 void
14289 ForwardEvent()
14290 {
14291     if (gameMode == IcsExamining && !pausing) {
14292         SendToICS(ics_prefix);
14293         SendToICS("forward\n");
14294     } else {
14295         ForwardInner(currentMove + 1);
14296     }
14297 }
14298
14299 void
14300 ToEndEvent()
14301 {
14302     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14303         /* to optimze, we temporarily turn off analysis mode while we feed
14304          * the remaining moves to the engine. Otherwise we get analysis output
14305          * after each move.
14306          */
14307         if (first.analysisSupport) {
14308           SendToProgram("exit\nforce\n", &first);
14309           first.analyzing = FALSE;
14310         }
14311     }
14312
14313     if (gameMode == IcsExamining && !pausing) {
14314         SendToICS(ics_prefix);
14315         SendToICS("forward 999999\n");
14316     } else {
14317         ForwardInner(forwardMostMove);
14318     }
14319
14320     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14321         /* we have fed all the moves, so reactivate analysis mode */
14322         SendToProgram("analyze\n", &first);
14323         first.analyzing = TRUE;
14324         /*first.maybeThinking = TRUE;*/
14325         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14326     }
14327 }
14328
14329 void
14330 BackwardInner(target)
14331      int target;
14332 {
14333     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14334
14335     if (appData.debugMode)
14336         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14337                 target, currentMove, forwardMostMove);
14338
14339     if (gameMode == EditPosition) return;
14340     MarkTargetSquares(1);
14341     if (currentMove <= backwardMostMove) {
14342         ClearHighlights();
14343         DrawPosition(full_redraw, boards[currentMove]);
14344         return;
14345     }
14346     if (gameMode == PlayFromGameFile && !pausing)
14347       PauseEvent();
14348
14349     if (moveList[target][0]) {
14350         int fromX, fromY, toX, toY;
14351         toX = moveList[target][2] - AAA;
14352         toY = moveList[target][3] - ONE;
14353         if (moveList[target][1] == '@') {
14354             if (appData.highlightLastMove) {
14355                 SetHighlights(-1, -1, toX, toY);
14356             }
14357         } else {
14358             fromX = moveList[target][0] - AAA;
14359             fromY = moveList[target][1] - ONE;
14360             if (target == currentMove - 1) {
14361                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14362             }
14363             if (appData.highlightLastMove) {
14364                 SetHighlights(fromX, fromY, toX, toY);
14365             }
14366         }
14367     }
14368     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14369         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14370         while (currentMove > target) {
14371             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14372                 // null move cannot be undone. Reload program with move history before it.
14373                 int i;
14374                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14375                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14376                 }
14377                 SendBoard(&first, i); 
14378                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14379                 break;
14380             }
14381             SendToProgram("undo\n", &first);
14382             currentMove--;
14383         }
14384     } else {
14385         currentMove = target;
14386     }
14387
14388     if (gameMode == EditGame || gameMode == EndOfGame) {
14389         whiteTimeRemaining = timeRemaining[0][currentMove];
14390         blackTimeRemaining = timeRemaining[1][currentMove];
14391     }
14392     DisplayBothClocks();
14393     DisplayMove(currentMove - 1);
14394     DrawPosition(full_redraw, boards[currentMove]);
14395     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14396     // [HGM] PV info: routine tests if comment empty
14397     DisplayComment(currentMove - 1, commentList[currentMove]);
14398 }
14399
14400 void
14401 BackwardEvent()
14402 {
14403     if (gameMode == IcsExamining && !pausing) {
14404         SendToICS(ics_prefix);
14405         SendToICS("backward\n");
14406     } else {
14407         BackwardInner(currentMove - 1);
14408     }
14409 }
14410
14411 void
14412 ToStartEvent()
14413 {
14414     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14415         /* to optimize, we temporarily turn off analysis mode while we undo
14416          * all the moves. Otherwise we get analysis output after each undo.
14417          */
14418         if (first.analysisSupport) {
14419           SendToProgram("exit\nforce\n", &first);
14420           first.analyzing = FALSE;
14421         }
14422     }
14423
14424     if (gameMode == IcsExamining && !pausing) {
14425         SendToICS(ics_prefix);
14426         SendToICS("backward 999999\n");
14427     } else {
14428         BackwardInner(backwardMostMove);
14429     }
14430
14431     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14432         /* we have fed all the moves, so reactivate analysis mode */
14433         SendToProgram("analyze\n", &first);
14434         first.analyzing = TRUE;
14435         /*first.maybeThinking = TRUE;*/
14436         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14437     }
14438 }
14439
14440 void
14441 ToNrEvent(int to)
14442 {
14443   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14444   if (to >= forwardMostMove) to = forwardMostMove;
14445   if (to <= backwardMostMove) to = backwardMostMove;
14446   if (to < currentMove) {
14447     BackwardInner(to);
14448   } else {
14449     ForwardInner(to);
14450   }
14451 }
14452
14453 void
14454 RevertEvent(Boolean annotate)
14455 {
14456     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14457         return;
14458     }
14459     if (gameMode != IcsExamining) {
14460         DisplayError(_("You are not examining a game"), 0);
14461         return;
14462     }
14463     if (pausing) {
14464         DisplayError(_("You can't revert while pausing"), 0);
14465         return;
14466     }
14467     SendToICS(ics_prefix);
14468     SendToICS("revert\n");
14469 }
14470
14471 void
14472 RetractMoveEvent()
14473 {
14474     switch (gameMode) {
14475       case MachinePlaysWhite:
14476       case MachinePlaysBlack:
14477         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14478             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14479             return;
14480         }
14481         if (forwardMostMove < 2) return;
14482         currentMove = forwardMostMove = forwardMostMove - 2;
14483         whiteTimeRemaining = timeRemaining[0][currentMove];
14484         blackTimeRemaining = timeRemaining[1][currentMove];
14485         DisplayBothClocks();
14486         DisplayMove(currentMove - 1);
14487         ClearHighlights();/*!! could figure this out*/
14488         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14489         SendToProgram("remove\n", &first);
14490         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14491         break;
14492
14493       case BeginningOfGame:
14494       default:
14495         break;
14496
14497       case IcsPlayingWhite:
14498       case IcsPlayingBlack:
14499         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14500             SendToICS(ics_prefix);
14501             SendToICS("takeback 2\n");
14502         } else {
14503             SendToICS(ics_prefix);
14504             SendToICS("takeback 1\n");
14505         }
14506         break;
14507     }
14508 }
14509
14510 void
14511 MoveNowEvent()
14512 {
14513     ChessProgramState *cps;
14514
14515     switch (gameMode) {
14516       case MachinePlaysWhite:
14517         if (!WhiteOnMove(forwardMostMove)) {
14518             DisplayError(_("It is your turn"), 0);
14519             return;
14520         }
14521         cps = &first;
14522         break;
14523       case MachinePlaysBlack:
14524         if (WhiteOnMove(forwardMostMove)) {
14525             DisplayError(_("It is your turn"), 0);
14526             return;
14527         }
14528         cps = &first;
14529         break;
14530       case TwoMachinesPlay:
14531         if (WhiteOnMove(forwardMostMove) ==
14532             (first.twoMachinesColor[0] == 'w')) {
14533             cps = &first;
14534         } else {
14535             cps = &second;
14536         }
14537         break;
14538       case BeginningOfGame:
14539       default:
14540         return;
14541     }
14542     SendToProgram("?\n", cps);
14543 }
14544
14545 void
14546 TruncateGameEvent()
14547 {
14548     EditGameEvent();
14549     if (gameMode != EditGame) return;
14550     TruncateGame();
14551 }
14552
14553 void
14554 TruncateGame()
14555 {
14556     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14557     if (forwardMostMove > currentMove) {
14558         if (gameInfo.resultDetails != NULL) {
14559             free(gameInfo.resultDetails);
14560             gameInfo.resultDetails = NULL;
14561             gameInfo.result = GameUnfinished;
14562         }
14563         forwardMostMove = currentMove;
14564         HistorySet(parseList, backwardMostMove, forwardMostMove,
14565                    currentMove-1);
14566     }
14567 }
14568
14569 void
14570 HintEvent()
14571 {
14572     if (appData.noChessProgram) return;
14573     switch (gameMode) {
14574       case MachinePlaysWhite:
14575         if (WhiteOnMove(forwardMostMove)) {
14576             DisplayError(_("Wait until your turn"), 0);
14577             return;
14578         }
14579         break;
14580       case BeginningOfGame:
14581       case MachinePlaysBlack:
14582         if (!WhiteOnMove(forwardMostMove)) {
14583             DisplayError(_("Wait until your turn"), 0);
14584             return;
14585         }
14586         break;
14587       default:
14588         DisplayError(_("No hint available"), 0);
14589         return;
14590     }
14591     SendToProgram("hint\n", &first);
14592     hintRequested = TRUE;
14593 }
14594
14595 void
14596 BookEvent()
14597 {
14598     if (appData.noChessProgram) return;
14599     switch (gameMode) {
14600       case MachinePlaysWhite:
14601         if (WhiteOnMove(forwardMostMove)) {
14602             DisplayError(_("Wait until your turn"), 0);
14603             return;
14604         }
14605         break;
14606       case BeginningOfGame:
14607       case MachinePlaysBlack:
14608         if (!WhiteOnMove(forwardMostMove)) {
14609             DisplayError(_("Wait until your turn"), 0);
14610             return;
14611         }
14612         break;
14613       case EditPosition:
14614         EditPositionDone(TRUE);
14615         break;
14616       case TwoMachinesPlay:
14617         return;
14618       default:
14619         break;
14620     }
14621     SendToProgram("bk\n", &first);
14622     bookOutput[0] = NULLCHAR;
14623     bookRequested = TRUE;
14624 }
14625
14626 void
14627 AboutGameEvent()
14628 {
14629     char *tags = PGNTags(&gameInfo);
14630     TagsPopUp(tags, CmailMsg());
14631     free(tags);
14632 }
14633
14634 /* end button procedures */
14635
14636 void
14637 PrintPosition(fp, move)
14638      FILE *fp;
14639      int move;
14640 {
14641     int i, j;
14642
14643     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14644         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14645             char c = PieceToChar(boards[move][i][j]);
14646             fputc(c == 'x' ? '.' : c, fp);
14647             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14648         }
14649     }
14650     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14651       fprintf(fp, "white to play\n");
14652     else
14653       fprintf(fp, "black to play\n");
14654 }
14655
14656 void
14657 PrintOpponents(fp)
14658      FILE *fp;
14659 {
14660     if (gameInfo.white != NULL) {
14661         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14662     } else {
14663         fprintf(fp, "\n");
14664     }
14665 }
14666
14667 /* Find last component of program's own name, using some heuristics */
14668 void
14669 TidyProgramName(prog, host, buf)
14670      char *prog, *host, buf[MSG_SIZ];
14671 {
14672     char *p, *q;
14673     int local = (strcmp(host, "localhost") == 0);
14674     while (!local && (p = strchr(prog, ';')) != NULL) {
14675         p++;
14676         while (*p == ' ') p++;
14677         prog = p;
14678     }
14679     if (*prog == '"' || *prog == '\'') {
14680         q = strchr(prog + 1, *prog);
14681     } else {
14682         q = strchr(prog, ' ');
14683     }
14684     if (q == NULL) q = prog + strlen(prog);
14685     p = q;
14686     while (p >= prog && *p != '/' && *p != '\\') p--;
14687     p++;
14688     if(p == prog && *p == '"') p++;
14689     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14690     memcpy(buf, p, q - p);
14691     buf[q - p] = NULLCHAR;
14692     if (!local) {
14693         strcat(buf, "@");
14694         strcat(buf, host);
14695     }
14696 }
14697
14698 char *
14699 TimeControlTagValue()
14700 {
14701     char buf[MSG_SIZ];
14702     if (!appData.clockMode) {
14703       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14704     } else if (movesPerSession > 0) {
14705       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14706     } else if (timeIncrement == 0) {
14707       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14708     } else {
14709       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14710     }
14711     return StrSave(buf);
14712 }
14713
14714 void
14715 SetGameInfo()
14716 {
14717     /* This routine is used only for certain modes */
14718     VariantClass v = gameInfo.variant;
14719     ChessMove r = GameUnfinished;
14720     char *p = NULL;
14721
14722     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14723         r = gameInfo.result;
14724         p = gameInfo.resultDetails;
14725         gameInfo.resultDetails = NULL;
14726     }
14727     ClearGameInfo(&gameInfo);
14728     gameInfo.variant = v;
14729
14730     switch (gameMode) {
14731       case MachinePlaysWhite:
14732         gameInfo.event = StrSave( appData.pgnEventHeader );
14733         gameInfo.site = StrSave(HostName());
14734         gameInfo.date = PGNDate();
14735         gameInfo.round = StrSave("-");
14736         gameInfo.white = StrSave(first.tidy);
14737         gameInfo.black = StrSave(UserName());
14738         gameInfo.timeControl = TimeControlTagValue();
14739         break;
14740
14741       case MachinePlaysBlack:
14742         gameInfo.event = StrSave( appData.pgnEventHeader );
14743         gameInfo.site = StrSave(HostName());
14744         gameInfo.date = PGNDate();
14745         gameInfo.round = StrSave("-");
14746         gameInfo.white = StrSave(UserName());
14747         gameInfo.black = StrSave(first.tidy);
14748         gameInfo.timeControl = TimeControlTagValue();
14749         break;
14750
14751       case TwoMachinesPlay:
14752         gameInfo.event = StrSave( appData.pgnEventHeader );
14753         gameInfo.site = StrSave(HostName());
14754         gameInfo.date = PGNDate();
14755         if (roundNr > 0) {
14756             char buf[MSG_SIZ];
14757             snprintf(buf, MSG_SIZ, "%d", roundNr);
14758             gameInfo.round = StrSave(buf);
14759         } else {
14760             gameInfo.round = StrSave("-");
14761         }
14762         if (first.twoMachinesColor[0] == 'w') {
14763             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14764             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14765         } else {
14766             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14767             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14768         }
14769         gameInfo.timeControl = TimeControlTagValue();
14770         break;
14771
14772       case EditGame:
14773         gameInfo.event = StrSave("Edited game");
14774         gameInfo.site = StrSave(HostName());
14775         gameInfo.date = PGNDate();
14776         gameInfo.round = StrSave("-");
14777         gameInfo.white = StrSave("-");
14778         gameInfo.black = StrSave("-");
14779         gameInfo.result = r;
14780         gameInfo.resultDetails = p;
14781         break;
14782
14783       case EditPosition:
14784         gameInfo.event = StrSave("Edited position");
14785         gameInfo.site = StrSave(HostName());
14786         gameInfo.date = PGNDate();
14787         gameInfo.round = StrSave("-");
14788         gameInfo.white = StrSave("-");
14789         gameInfo.black = StrSave("-");
14790         break;
14791
14792       case IcsPlayingWhite:
14793       case IcsPlayingBlack:
14794       case IcsObserving:
14795       case IcsExamining:
14796         break;
14797
14798       case PlayFromGameFile:
14799         gameInfo.event = StrSave("Game from non-PGN file");
14800         gameInfo.site = StrSave(HostName());
14801         gameInfo.date = PGNDate();
14802         gameInfo.round = StrSave("-");
14803         gameInfo.white = StrSave("?");
14804         gameInfo.black = StrSave("?");
14805         break;
14806
14807       default:
14808         break;
14809     }
14810 }
14811
14812 void
14813 ReplaceComment(index, text)
14814      int index;
14815      char *text;
14816 {
14817     int len;
14818     char *p;
14819     float score;
14820
14821     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14822        pvInfoList[index-1].depth == len &&
14823        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14824        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14825     while (*text == '\n') text++;
14826     len = strlen(text);
14827     while (len > 0 && text[len - 1] == '\n') len--;
14828
14829     if (commentList[index] != NULL)
14830       free(commentList[index]);
14831
14832     if (len == 0) {
14833         commentList[index] = NULL;
14834         return;
14835     }
14836   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14837       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14838       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14839     commentList[index] = (char *) malloc(len + 2);
14840     strncpy(commentList[index], text, len);
14841     commentList[index][len] = '\n';
14842     commentList[index][len + 1] = NULLCHAR;
14843   } else {
14844     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14845     char *p;
14846     commentList[index] = (char *) malloc(len + 7);
14847     safeStrCpy(commentList[index], "{\n", 3);
14848     safeStrCpy(commentList[index]+2, text, len+1);
14849     commentList[index][len+2] = NULLCHAR;
14850     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14851     strcat(commentList[index], "\n}\n");
14852   }
14853 }
14854
14855 void
14856 CrushCRs(text)
14857      char *text;
14858 {
14859   char *p = text;
14860   char *q = text;
14861   char ch;
14862
14863   do {
14864     ch = *p++;
14865     if (ch == '\r') continue;
14866     *q++ = ch;
14867   } while (ch != '\0');
14868 }
14869
14870 void
14871 AppendComment(index, text, addBraces)
14872      int index;
14873      char *text;
14874      Boolean addBraces; // [HGM] braces: tells if we should add {}
14875 {
14876     int oldlen, len;
14877     char *old;
14878
14879 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14880     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14881
14882     CrushCRs(text);
14883     while (*text == '\n') text++;
14884     len = strlen(text);
14885     while (len > 0 && text[len - 1] == '\n') len--;
14886
14887     if (len == 0) return;
14888
14889     if (commentList[index] != NULL) {
14890       Boolean addClosingBrace = addBraces;
14891         old = commentList[index];
14892         oldlen = strlen(old);
14893         while(commentList[index][oldlen-1] ==  '\n')
14894           commentList[index][--oldlen] = NULLCHAR;
14895         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14896         safeStrCpy(commentList[index], old, oldlen + len + 6);
14897         free(old);
14898         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14899         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14900           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14901           while (*text == '\n') { text++; len--; }
14902           commentList[index][--oldlen] = NULLCHAR;
14903       }
14904         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14905         else          strcat(commentList[index], "\n");
14906         strcat(commentList[index], text);
14907         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14908         else          strcat(commentList[index], "\n");
14909     } else {
14910         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14911         if(addBraces)
14912           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14913         else commentList[index][0] = NULLCHAR;
14914         strcat(commentList[index], text);
14915         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14916         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14917     }
14918 }
14919
14920 static char * FindStr( char * text, char * sub_text )
14921 {
14922     char * result = strstr( text, sub_text );
14923
14924     if( result != NULL ) {
14925         result += strlen( sub_text );
14926     }
14927
14928     return result;
14929 }
14930
14931 /* [AS] Try to extract PV info from PGN comment */
14932 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14933 char *GetInfoFromComment( int index, char * text )
14934 {
14935     char * sep = text, *p;
14936
14937     if( text != NULL && index > 0 ) {
14938         int score = 0;
14939         int depth = 0;
14940         int time = -1, sec = 0, deci;
14941         char * s_eval = FindStr( text, "[%eval " );
14942         char * s_emt = FindStr( text, "[%emt " );
14943
14944         if( s_eval != NULL || s_emt != NULL ) {
14945             /* New style */
14946             char delim;
14947
14948             if( s_eval != NULL ) {
14949                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14950                     return text;
14951                 }
14952
14953                 if( delim != ']' ) {
14954                     return text;
14955                 }
14956             }
14957
14958             if( s_emt != NULL ) {
14959             }
14960                 return text;
14961         }
14962         else {
14963             /* We expect something like: [+|-]nnn.nn/dd */
14964             int score_lo = 0;
14965
14966             if(*text != '{') return text; // [HGM] braces: must be normal comment
14967
14968             sep = strchr( text, '/' );
14969             if( sep == NULL || sep < (text+4) ) {
14970                 return text;
14971             }
14972
14973             p = text;
14974             if(p[1] == '(') { // comment starts with PV
14975                p = strchr(p, ')'); // locate end of PV
14976                if(p == NULL || sep < p+5) return text;
14977                // at this point we have something like "{(.*) +0.23/6 ..."
14978                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14979                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14980                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14981             }
14982             time = -1; sec = -1; deci = -1;
14983             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14984                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14985                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14986                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14987                 return text;
14988             }
14989
14990             if( score_lo < 0 || score_lo >= 100 ) {
14991                 return text;
14992             }
14993
14994             if(sec >= 0) time = 600*time + 10*sec; else
14995             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14996
14997             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14998
14999             /* [HGM] PV time: now locate end of PV info */
15000             while( *++sep >= '0' && *sep <= '9'); // strip depth
15001             if(time >= 0)
15002             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15003             if(sec >= 0)
15004             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15005             if(deci >= 0)
15006             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15007             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15008         }
15009
15010         if( depth <= 0 ) {
15011             return text;
15012         }
15013
15014         if( time < 0 ) {
15015             time = -1;
15016         }
15017
15018         pvInfoList[index-1].depth = depth;
15019         pvInfoList[index-1].score = score;
15020         pvInfoList[index-1].time  = 10*time; // centi-sec
15021         if(*sep == '}') *sep = 0; else *--sep = '{';
15022         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15023     }
15024     return sep;
15025 }
15026
15027 void
15028 SendToProgram(message, cps)
15029      char *message;
15030      ChessProgramState *cps;
15031 {
15032     int count, outCount, error;
15033     char buf[MSG_SIZ];
15034
15035     if (cps->pr == NoProc) return;
15036     Attention(cps);
15037
15038     if (appData.debugMode) {
15039         TimeMark now;
15040         GetTimeMark(&now);
15041         fprintf(debugFP, "%ld >%-6s: %s",
15042                 SubtractTimeMarks(&now, &programStartTime),
15043                 cps->which, message);
15044     }
15045
15046     count = strlen(message);
15047     outCount = OutputToProcess(cps->pr, message, count, &error);
15048     if (outCount < count && !exiting
15049                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15050       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15051       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15052         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15053             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15054                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15055                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15056                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15057             } else {
15058                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15059                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15060                 gameInfo.result = res;
15061             }
15062             gameInfo.resultDetails = StrSave(buf);
15063         }
15064         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15065         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15066     }
15067 }
15068
15069 void
15070 ReceiveFromProgram(isr, closure, message, count, error)
15071      InputSourceRef isr;
15072      VOIDSTAR closure;
15073      char *message;
15074      int count;
15075      int error;
15076 {
15077     char *end_str;
15078     char buf[MSG_SIZ];
15079     ChessProgramState *cps = (ChessProgramState *)closure;
15080
15081     if (isr != cps->isr) return; /* Killed intentionally */
15082     if (count <= 0) {
15083         if (count == 0) {
15084             RemoveInputSource(cps->isr);
15085             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15086             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15087                     _(cps->which), cps->program);
15088         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15089                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15090                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15091                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15092                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15093                 } else {
15094                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15095                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15096                     gameInfo.result = res;
15097                 }
15098                 gameInfo.resultDetails = StrSave(buf);
15099             }
15100             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15101             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15102         } else {
15103             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15104                     _(cps->which), cps->program);
15105             RemoveInputSource(cps->isr);
15106
15107             /* [AS] Program is misbehaving badly... kill it */
15108             if( count == -2 ) {
15109                 DestroyChildProcess( cps->pr, 9 );
15110                 cps->pr = NoProc;
15111             }
15112
15113             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15114         }
15115         return;
15116     }
15117
15118     if ((end_str = strchr(message, '\r')) != NULL)
15119       *end_str = NULLCHAR;
15120     if ((end_str = strchr(message, '\n')) != NULL)
15121       *end_str = NULLCHAR;
15122
15123     if (appData.debugMode) {
15124         TimeMark now; int print = 1;
15125         char *quote = ""; char c; int i;
15126
15127         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15128                 char start = message[0];
15129                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15130                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15131                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15132                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15133                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15134                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15135                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15136                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15137                    sscanf(message, "hint: %c", &c)!=1 && 
15138                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15139                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15140                     print = (appData.engineComments >= 2);
15141                 }
15142                 message[0] = start; // restore original message
15143         }
15144         if(print) {
15145                 GetTimeMark(&now);
15146                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15147                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15148                         quote,
15149                         message);
15150         }
15151     }
15152
15153     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15154     if (appData.icsEngineAnalyze) {
15155         if (strstr(message, "whisper") != NULL ||
15156              strstr(message, "kibitz") != NULL ||
15157             strstr(message, "tellics") != NULL) return;
15158     }
15159
15160     HandleMachineMove(message, cps);
15161 }
15162
15163
15164 void
15165 SendTimeControl(cps, mps, tc, inc, sd, st)
15166      ChessProgramState *cps;
15167      int mps, inc, sd, st;
15168      long tc;
15169 {
15170     char buf[MSG_SIZ];
15171     int seconds;
15172
15173     if( timeControl_2 > 0 ) {
15174         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15175             tc = timeControl_2;
15176         }
15177     }
15178     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15179     inc /= cps->timeOdds;
15180     st  /= cps->timeOdds;
15181
15182     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15183
15184     if (st > 0) {
15185       /* Set exact time per move, normally using st command */
15186       if (cps->stKludge) {
15187         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15188         seconds = st % 60;
15189         if (seconds == 0) {
15190           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15191         } else {
15192           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15193         }
15194       } else {
15195         snprintf(buf, MSG_SIZ, "st %d\n", st);
15196       }
15197     } else {
15198       /* Set conventional or incremental time control, using level command */
15199       if (seconds == 0) {
15200         /* Note old gnuchess bug -- minutes:seconds used to not work.
15201            Fixed in later versions, but still avoid :seconds
15202            when seconds is 0. */
15203         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15204       } else {
15205         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15206                  seconds, inc/1000.);
15207       }
15208     }
15209     SendToProgram(buf, cps);
15210
15211     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15212     /* Orthogonally, limit search to given depth */
15213     if (sd > 0) {
15214       if (cps->sdKludge) {
15215         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15216       } else {
15217         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15218       }
15219       SendToProgram(buf, cps);
15220     }
15221
15222     if(cps->nps >= 0) { /* [HGM] nps */
15223         if(cps->supportsNPS == FALSE)
15224           cps->nps = -1; // don't use if engine explicitly says not supported!
15225         else {
15226           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15227           SendToProgram(buf, cps);
15228         }
15229     }
15230 }
15231
15232 ChessProgramState *WhitePlayer()
15233 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15234 {
15235     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15236        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15237         return &second;
15238     return &first;
15239 }
15240
15241 void
15242 SendTimeRemaining(cps, machineWhite)
15243      ChessProgramState *cps;
15244      int /*boolean*/ machineWhite;
15245 {
15246     char message[MSG_SIZ];
15247     long time, otime;
15248
15249     /* Note: this routine must be called when the clocks are stopped
15250        or when they have *just* been set or switched; otherwise
15251        it will be off by the time since the current tick started.
15252     */
15253     if (machineWhite) {
15254         time = whiteTimeRemaining / 10;
15255         otime = blackTimeRemaining / 10;
15256     } else {
15257         time = blackTimeRemaining / 10;
15258         otime = whiteTimeRemaining / 10;
15259     }
15260     /* [HGM] translate opponent's time by time-odds factor */
15261     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15262     if (appData.debugMode) {
15263         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15264     }
15265
15266     if (time <= 0) time = 1;
15267     if (otime <= 0) otime = 1;
15268
15269     snprintf(message, MSG_SIZ, "time %ld\n", time);
15270     SendToProgram(message, cps);
15271
15272     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15273     SendToProgram(message, cps);
15274 }
15275
15276 int
15277 BoolFeature(p, name, loc, cps)
15278      char **p;
15279      char *name;
15280      int *loc;
15281      ChessProgramState *cps;
15282 {
15283   char buf[MSG_SIZ];
15284   int len = strlen(name);
15285   int val;
15286
15287   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15288     (*p) += len + 1;
15289     sscanf(*p, "%d", &val);
15290     *loc = (val != 0);
15291     while (**p && **p != ' ')
15292       (*p)++;
15293     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15294     SendToProgram(buf, cps);
15295     return TRUE;
15296   }
15297   return FALSE;
15298 }
15299
15300 int
15301 IntFeature(p, name, loc, cps)
15302      char **p;
15303      char *name;
15304      int *loc;
15305      ChessProgramState *cps;
15306 {
15307   char buf[MSG_SIZ];
15308   int len = strlen(name);
15309   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15310     (*p) += len + 1;
15311     sscanf(*p, "%d", loc);
15312     while (**p && **p != ' ') (*p)++;
15313     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15314     SendToProgram(buf, cps);
15315     return TRUE;
15316   }
15317   return FALSE;
15318 }
15319
15320 int
15321 StringFeature(p, name, loc, cps)
15322      char **p;
15323      char *name;
15324      char loc[];
15325      ChessProgramState *cps;
15326 {
15327   char buf[MSG_SIZ];
15328   int len = strlen(name);
15329   if (strncmp((*p), name, len) == 0
15330       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15331     (*p) += len + 2;
15332     sscanf(*p, "%[^\"]", loc);
15333     while (**p && **p != '\"') (*p)++;
15334     if (**p == '\"') (*p)++;
15335     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15336     SendToProgram(buf, cps);
15337     return TRUE;
15338   }
15339   return FALSE;
15340 }
15341
15342 int
15343 ParseOption(Option *opt, ChessProgramState *cps)
15344 // [HGM] options: process the string that defines an engine option, and determine
15345 // name, type, default value, and allowed value range
15346 {
15347         char *p, *q, buf[MSG_SIZ];
15348         int n, min = (-1)<<31, max = 1<<31, def;
15349
15350         if(p = strstr(opt->name, " -spin ")) {
15351             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15352             if(max < min) max = min; // enforce consistency
15353             if(def < min) def = min;
15354             if(def > max) def = max;
15355             opt->value = def;
15356             opt->min = min;
15357             opt->max = max;
15358             opt->type = Spin;
15359         } else if((p = strstr(opt->name, " -slider "))) {
15360             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15361             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15362             if(max < min) max = min; // enforce consistency
15363             if(def < min) def = min;
15364             if(def > max) def = max;
15365             opt->value = def;
15366             opt->min = min;
15367             opt->max = max;
15368             opt->type = Spin; // Slider;
15369         } else if((p = strstr(opt->name, " -string "))) {
15370             opt->textValue = p+9;
15371             opt->type = TextBox;
15372         } else if((p = strstr(opt->name, " -file "))) {
15373             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15374             opt->textValue = p+7;
15375             opt->type = FileName; // FileName;
15376         } else if((p = strstr(opt->name, " -path "))) {
15377             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15378             opt->textValue = p+7;
15379             opt->type = PathName; // PathName;
15380         } else if(p = strstr(opt->name, " -check ")) {
15381             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15382             opt->value = (def != 0);
15383             opt->type = CheckBox;
15384         } else if(p = strstr(opt->name, " -combo ")) {
15385             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15386             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15387             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15388             opt->value = n = 0;
15389             while(q = StrStr(q, " /// ")) {
15390                 n++; *q = 0;    // count choices, and null-terminate each of them
15391                 q += 5;
15392                 if(*q == '*') { // remember default, which is marked with * prefix
15393                     q++;
15394                     opt->value = n;
15395                 }
15396                 cps->comboList[cps->comboCnt++] = q;
15397             }
15398             cps->comboList[cps->comboCnt++] = NULL;
15399             opt->max = n + 1;
15400             opt->type = ComboBox;
15401         } else if(p = strstr(opt->name, " -button")) {
15402             opt->type = Button;
15403         } else if(p = strstr(opt->name, " -save")) {
15404             opt->type = SaveButton;
15405         } else return FALSE;
15406         *p = 0; // terminate option name
15407         // now look if the command-line options define a setting for this engine option.
15408         if(cps->optionSettings && cps->optionSettings[0])
15409             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15410         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15411           snprintf(buf, MSG_SIZ, "option %s", p);
15412                 if(p = strstr(buf, ",")) *p = 0;
15413                 if(q = strchr(buf, '=')) switch(opt->type) {
15414                     case ComboBox:
15415                         for(n=0; n<opt->max; n++)
15416                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15417                         break;
15418                     case TextBox:
15419                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15420                         break;
15421                     case Spin:
15422                     case CheckBox:
15423                         opt->value = atoi(q+1);
15424                     default:
15425                         break;
15426                 }
15427                 strcat(buf, "\n");
15428                 SendToProgram(buf, cps);
15429         }
15430         return TRUE;
15431 }
15432
15433 void
15434 FeatureDone(cps, val)
15435      ChessProgramState* cps;
15436      int val;
15437 {
15438   DelayedEventCallback cb = GetDelayedEvent();
15439   if ((cb == InitBackEnd3 && cps == &first) ||
15440       (cb == SettingsMenuIfReady && cps == &second) ||
15441       (cb == LoadEngine) ||
15442       (cb == TwoMachinesEventIfReady)) {
15443     CancelDelayedEvent();
15444     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15445   }
15446   cps->initDone = val;
15447 }
15448
15449 /* Parse feature command from engine */
15450 void
15451 ParseFeatures(args, cps)
15452      char* args;
15453      ChessProgramState *cps;
15454 {
15455   char *p = args;
15456   char *q;
15457   int val;
15458   char buf[MSG_SIZ];
15459
15460   for (;;) {
15461     while (*p == ' ') p++;
15462     if (*p == NULLCHAR) return;
15463
15464     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15465     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15466     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15467     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15468     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15469     if (BoolFeature(&p, "reuse", &val, cps)) {
15470       /* Engine can disable reuse, but can't enable it if user said no */
15471       if (!val) cps->reuse = FALSE;
15472       continue;
15473     }
15474     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15475     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15476       if (gameMode == TwoMachinesPlay) {
15477         DisplayTwoMachinesTitle();
15478       } else {
15479         DisplayTitle("");
15480       }
15481       continue;
15482     }
15483     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15484     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15485     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15486     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15487     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15488     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15489     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15490     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15491     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15492     if (IntFeature(&p, "done", &val, cps)) {
15493       FeatureDone(cps, val);
15494       continue;
15495     }
15496     /* Added by Tord: */
15497     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15498     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15499     /* End of additions by Tord */
15500
15501     /* [HGM] added features: */
15502     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15503     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15504     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15505     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15506     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15507     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15508     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15509         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15510           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15511             SendToProgram(buf, cps);
15512             continue;
15513         }
15514         if(cps->nrOptions >= MAX_OPTIONS) {
15515             cps->nrOptions--;
15516             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15517             DisplayError(buf, 0);
15518         }
15519         continue;
15520     }
15521     /* End of additions by HGM */
15522
15523     /* unknown feature: complain and skip */
15524     q = p;
15525     while (*q && *q != '=') q++;
15526     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15527     SendToProgram(buf, cps);
15528     p = q;
15529     if (*p == '=') {
15530       p++;
15531       if (*p == '\"') {
15532         p++;
15533         while (*p && *p != '\"') p++;
15534         if (*p == '\"') p++;
15535       } else {
15536         while (*p && *p != ' ') p++;
15537       }
15538     }
15539   }
15540
15541 }
15542
15543 void
15544 PeriodicUpdatesEvent(newState)
15545      int newState;
15546 {
15547     if (newState == appData.periodicUpdates)
15548       return;
15549
15550     appData.periodicUpdates=newState;
15551
15552     /* Display type changes, so update it now */
15553 //    DisplayAnalysis();
15554
15555     /* Get the ball rolling again... */
15556     if (newState) {
15557         AnalysisPeriodicEvent(1);
15558         StartAnalysisClock();
15559     }
15560 }
15561
15562 void
15563 PonderNextMoveEvent(newState)
15564      int newState;
15565 {
15566     if (newState == appData.ponderNextMove) return;
15567     if (gameMode == EditPosition) EditPositionDone(TRUE);
15568     if (newState) {
15569         SendToProgram("hard\n", &first);
15570         if (gameMode == TwoMachinesPlay) {
15571             SendToProgram("hard\n", &second);
15572         }
15573     } else {
15574         SendToProgram("easy\n", &first);
15575         thinkOutput[0] = NULLCHAR;
15576         if (gameMode == TwoMachinesPlay) {
15577             SendToProgram("easy\n", &second);
15578         }
15579     }
15580     appData.ponderNextMove = newState;
15581 }
15582
15583 void
15584 NewSettingEvent(option, feature, command, value)
15585      char *command;
15586      int option, value, *feature;
15587 {
15588     char buf[MSG_SIZ];
15589
15590     if (gameMode == EditPosition) EditPositionDone(TRUE);
15591     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15592     if(feature == NULL || *feature) SendToProgram(buf, &first);
15593     if (gameMode == TwoMachinesPlay) {
15594         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15595     }
15596 }
15597
15598 void
15599 ShowThinkingEvent()
15600 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15601 {
15602     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15603     int newState = appData.showThinking
15604         // [HGM] thinking: other features now need thinking output as well
15605         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15606
15607     if (oldState == newState) return;
15608     oldState = newState;
15609     if (gameMode == EditPosition) EditPositionDone(TRUE);
15610     if (oldState) {
15611         SendToProgram("post\n", &first);
15612         if (gameMode == TwoMachinesPlay) {
15613             SendToProgram("post\n", &second);
15614         }
15615     } else {
15616         SendToProgram("nopost\n", &first);
15617         thinkOutput[0] = NULLCHAR;
15618         if (gameMode == TwoMachinesPlay) {
15619             SendToProgram("nopost\n", &second);
15620         }
15621     }
15622 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15623 }
15624
15625 void
15626 AskQuestionEvent(title, question, replyPrefix, which)
15627      char *title; char *question; char *replyPrefix; char *which;
15628 {
15629   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15630   if (pr == NoProc) return;
15631   AskQuestion(title, question, replyPrefix, pr);
15632 }
15633
15634 void
15635 TypeInEvent(char firstChar)
15636 {
15637     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15638         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15639         gameMode == AnalyzeMode || gameMode == EditGame || 
15640         gameMode == EditPosition || gameMode == IcsExamining ||
15641         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15642         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15643                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15644                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15645         gameMode == Training) PopUpMoveDialog(firstChar);
15646 }
15647
15648 void
15649 TypeInDoneEvent(char *move)
15650 {
15651         Board board;
15652         int n, fromX, fromY, toX, toY;
15653         char promoChar;
15654         ChessMove moveType;
15655
15656         // [HGM] FENedit
15657         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15658                 EditPositionPasteFEN(move);
15659                 return;
15660         }
15661         // [HGM] movenum: allow move number to be typed in any mode
15662         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15663           ToNrEvent(2*n-1);
15664           return;
15665         }
15666
15667       if (gameMode != EditGame && currentMove != forwardMostMove && 
15668         gameMode != Training) {
15669         DisplayMoveError(_("Displayed move is not current"));
15670       } else {
15671         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15672           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15673         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15674         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15675           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15676           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15677         } else {
15678           DisplayMoveError(_("Could not parse move"));
15679         }
15680       }
15681 }
15682
15683 void
15684 DisplayMove(moveNumber)
15685      int moveNumber;
15686 {
15687     char message[MSG_SIZ];
15688     char res[MSG_SIZ];
15689     char cpThinkOutput[MSG_SIZ];
15690
15691     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15692
15693     if (moveNumber == forwardMostMove - 1 ||
15694         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15695
15696         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15697
15698         if (strchr(cpThinkOutput, '\n')) {
15699             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15700         }
15701     } else {
15702         *cpThinkOutput = NULLCHAR;
15703     }
15704
15705     /* [AS] Hide thinking from human user */
15706     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15707         *cpThinkOutput = NULLCHAR;
15708         if( thinkOutput[0] != NULLCHAR ) {
15709             int i;
15710
15711             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15712                 cpThinkOutput[i] = '.';
15713             }
15714             cpThinkOutput[i] = NULLCHAR;
15715             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15716         }
15717     }
15718
15719     if (moveNumber == forwardMostMove - 1 &&
15720         gameInfo.resultDetails != NULL) {
15721         if (gameInfo.resultDetails[0] == NULLCHAR) {
15722           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15723         } else {
15724           snprintf(res, MSG_SIZ, " {%s} %s",
15725                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15726         }
15727     } else {
15728         res[0] = NULLCHAR;
15729     }
15730
15731     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15732         DisplayMessage(res, cpThinkOutput);
15733     } else {
15734       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15735                 WhiteOnMove(moveNumber) ? " " : ".. ",
15736                 parseList[moveNumber], res);
15737         DisplayMessage(message, cpThinkOutput);
15738     }
15739 }
15740
15741 void
15742 DisplayComment(moveNumber, text)
15743      int moveNumber;
15744      char *text;
15745 {
15746     char title[MSG_SIZ];
15747
15748     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15749       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15750     } else {
15751       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15752               WhiteOnMove(moveNumber) ? " " : ".. ",
15753               parseList[moveNumber]);
15754     }
15755     if (text != NULL && (appData.autoDisplayComment || commentUp))
15756         CommentPopUp(title, text);
15757 }
15758
15759 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15760  * might be busy thinking or pondering.  It can be omitted if your
15761  * gnuchess is configured to stop thinking immediately on any user
15762  * input.  However, that gnuchess feature depends on the FIONREAD
15763  * ioctl, which does not work properly on some flavors of Unix.
15764  */
15765 void
15766 Attention(cps)
15767      ChessProgramState *cps;
15768 {
15769 #if ATTENTION
15770     if (!cps->useSigint) return;
15771     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15772     switch (gameMode) {
15773       case MachinePlaysWhite:
15774       case MachinePlaysBlack:
15775       case TwoMachinesPlay:
15776       case IcsPlayingWhite:
15777       case IcsPlayingBlack:
15778       case AnalyzeMode:
15779       case AnalyzeFile:
15780         /* Skip if we know it isn't thinking */
15781         if (!cps->maybeThinking) return;
15782         if (appData.debugMode)
15783           fprintf(debugFP, "Interrupting %s\n", cps->which);
15784         InterruptChildProcess(cps->pr);
15785         cps->maybeThinking = FALSE;
15786         break;
15787       default:
15788         break;
15789     }
15790 #endif /*ATTENTION*/
15791 }
15792
15793 int
15794 CheckFlags()
15795 {
15796     if (whiteTimeRemaining <= 0) {
15797         if (!whiteFlag) {
15798             whiteFlag = TRUE;
15799             if (appData.icsActive) {
15800                 if (appData.autoCallFlag &&
15801                     gameMode == IcsPlayingBlack && !blackFlag) {
15802                   SendToICS(ics_prefix);
15803                   SendToICS("flag\n");
15804                 }
15805             } else {
15806                 if (blackFlag) {
15807                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15808                 } else {
15809                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15810                     if (appData.autoCallFlag) {
15811                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15812                         return TRUE;
15813                     }
15814                 }
15815             }
15816         }
15817     }
15818     if (blackTimeRemaining <= 0) {
15819         if (!blackFlag) {
15820             blackFlag = TRUE;
15821             if (appData.icsActive) {
15822                 if (appData.autoCallFlag &&
15823                     gameMode == IcsPlayingWhite && !whiteFlag) {
15824                   SendToICS(ics_prefix);
15825                   SendToICS("flag\n");
15826                 }
15827             } else {
15828                 if (whiteFlag) {
15829                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15830                 } else {
15831                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15832                     if (appData.autoCallFlag) {
15833                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15834                         return TRUE;
15835                     }
15836                 }
15837             }
15838         }
15839     }
15840     return FALSE;
15841 }
15842
15843 void
15844 CheckTimeControl()
15845 {
15846     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15847         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15848
15849     /*
15850      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15851      */
15852     if ( !WhiteOnMove(forwardMostMove) ) {
15853         /* White made time control */
15854         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15855         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15856         /* [HGM] time odds: correct new time quota for time odds! */
15857                                             / WhitePlayer()->timeOdds;
15858         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15859     } else {
15860         lastBlack -= blackTimeRemaining;
15861         /* Black made time control */
15862         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15863                                             / WhitePlayer()->other->timeOdds;
15864         lastWhite = whiteTimeRemaining;
15865     }
15866 }
15867
15868 void
15869 DisplayBothClocks()
15870 {
15871     int wom = gameMode == EditPosition ?
15872       !blackPlaysFirst : WhiteOnMove(currentMove);
15873     DisplayWhiteClock(whiteTimeRemaining, wom);
15874     DisplayBlackClock(blackTimeRemaining, !wom);
15875 }
15876
15877
15878 /* Timekeeping seems to be a portability nightmare.  I think everyone
15879    has ftime(), but I'm really not sure, so I'm including some ifdefs
15880    to use other calls if you don't.  Clocks will be less accurate if
15881    you have neither ftime nor gettimeofday.
15882 */
15883
15884 /* VS 2008 requires the #include outside of the function */
15885 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15886 #include <sys/timeb.h>
15887 #endif
15888
15889 /* Get the current time as a TimeMark */
15890 void
15891 GetTimeMark(tm)
15892      TimeMark *tm;
15893 {
15894 #if HAVE_GETTIMEOFDAY
15895
15896     struct timeval timeVal;
15897     struct timezone timeZone;
15898
15899     gettimeofday(&timeVal, &timeZone);
15900     tm->sec = (long) timeVal.tv_sec;
15901     tm->ms = (int) (timeVal.tv_usec / 1000L);
15902
15903 #else /*!HAVE_GETTIMEOFDAY*/
15904 #if HAVE_FTIME
15905
15906 // include <sys/timeb.h> / moved to just above start of function
15907     struct timeb timeB;
15908
15909     ftime(&timeB);
15910     tm->sec = (long) timeB.time;
15911     tm->ms = (int) timeB.millitm;
15912
15913 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15914     tm->sec = (long) time(NULL);
15915     tm->ms = 0;
15916 #endif
15917 #endif
15918 }
15919
15920 /* Return the difference in milliseconds between two
15921    time marks.  We assume the difference will fit in a long!
15922 */
15923 long
15924 SubtractTimeMarks(tm2, tm1)
15925      TimeMark *tm2, *tm1;
15926 {
15927     return 1000L*(tm2->sec - tm1->sec) +
15928            (long) (tm2->ms - tm1->ms);
15929 }
15930
15931
15932 /*
15933  * Code to manage the game clocks.
15934  *
15935  * In tournament play, black starts the clock and then white makes a move.
15936  * We give the human user a slight advantage if he is playing white---the
15937  * clocks don't run until he makes his first move, so it takes zero time.
15938  * Also, we don't account for network lag, so we could get out of sync
15939  * with GNU Chess's clock -- but then, referees are always right.
15940  */
15941
15942 static TimeMark tickStartTM;
15943 static long intendedTickLength;
15944
15945 long
15946 NextTickLength(timeRemaining)
15947      long timeRemaining;
15948 {
15949     long nominalTickLength, nextTickLength;
15950
15951     if (timeRemaining > 0L && timeRemaining <= 10000L)
15952       nominalTickLength = 100L;
15953     else
15954       nominalTickLength = 1000L;
15955     nextTickLength = timeRemaining % nominalTickLength;
15956     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15957
15958     return nextTickLength;
15959 }
15960
15961 /* Adjust clock one minute up or down */
15962 void
15963 AdjustClock(Boolean which, int dir)
15964 {
15965     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15966     if(which) blackTimeRemaining += 60000*dir;
15967     else      whiteTimeRemaining += 60000*dir;
15968     DisplayBothClocks();
15969     adjustedClock = TRUE;
15970 }
15971
15972 /* Stop clocks and reset to a fresh time control */
15973 void
15974 ResetClocks()
15975 {
15976     (void) StopClockTimer();
15977     if (appData.icsActive) {
15978         whiteTimeRemaining = blackTimeRemaining = 0;
15979     } else if (searchTime) {
15980         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15981         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15982     } else { /* [HGM] correct new time quote for time odds */
15983         whiteTC = blackTC = fullTimeControlString;
15984         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15985         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15986     }
15987     if (whiteFlag || blackFlag) {
15988         DisplayTitle("");
15989         whiteFlag = blackFlag = FALSE;
15990     }
15991     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15992     DisplayBothClocks();
15993     adjustedClock = FALSE;
15994 }
15995
15996 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15997
15998 /* Decrement running clock by amount of time that has passed */
15999 void
16000 DecrementClocks()
16001 {
16002     long timeRemaining;
16003     long lastTickLength, fudge;
16004     TimeMark now;
16005
16006     if (!appData.clockMode) return;
16007     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16008
16009     GetTimeMark(&now);
16010
16011     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16012
16013     /* Fudge if we woke up a little too soon */
16014     fudge = intendedTickLength - lastTickLength;
16015     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16016
16017     if (WhiteOnMove(forwardMostMove)) {
16018         if(whiteNPS >= 0) lastTickLength = 0;
16019         timeRemaining = whiteTimeRemaining -= lastTickLength;
16020         if(timeRemaining < 0 && !appData.icsActive) {
16021             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16022             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16023                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16024                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16025             }
16026         }
16027         DisplayWhiteClock(whiteTimeRemaining - fudge,
16028                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16029     } else {
16030         if(blackNPS >= 0) lastTickLength = 0;
16031         timeRemaining = blackTimeRemaining -= lastTickLength;
16032         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16033             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16034             if(suddenDeath) {
16035                 blackStartMove = forwardMostMove;
16036                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16037             }
16038         }
16039         DisplayBlackClock(blackTimeRemaining - fudge,
16040                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16041     }
16042     if (CheckFlags()) return;
16043
16044     tickStartTM = now;
16045     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16046     StartClockTimer(intendedTickLength);
16047
16048     /* if the time remaining has fallen below the alarm threshold, sound the
16049      * alarm. if the alarm has sounded and (due to a takeback or time control
16050      * with increment) the time remaining has increased to a level above the
16051      * threshold, reset the alarm so it can sound again.
16052      */
16053
16054     if (appData.icsActive && appData.icsAlarm) {
16055
16056         /* make sure we are dealing with the user's clock */
16057         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16058                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16059            )) return;
16060
16061         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16062             alarmSounded = FALSE;
16063         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16064             PlayAlarmSound();
16065             alarmSounded = TRUE;
16066         }
16067     }
16068 }
16069
16070
16071 /* A player has just moved, so stop the previously running
16072    clock and (if in clock mode) start the other one.
16073    We redisplay both clocks in case we're in ICS mode, because
16074    ICS gives us an update to both clocks after every move.
16075    Note that this routine is called *after* forwardMostMove
16076    is updated, so the last fractional tick must be subtracted
16077    from the color that is *not* on move now.
16078 */
16079 void
16080 SwitchClocks(int newMoveNr)
16081 {
16082     long lastTickLength;
16083     TimeMark now;
16084     int flagged = FALSE;
16085
16086     GetTimeMark(&now);
16087
16088     if (StopClockTimer() && appData.clockMode) {
16089         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16090         if (!WhiteOnMove(forwardMostMove)) {
16091             if(blackNPS >= 0) lastTickLength = 0;
16092             blackTimeRemaining -= lastTickLength;
16093            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16094 //         if(pvInfoList[forwardMostMove].time == -1)
16095                  pvInfoList[forwardMostMove].time =               // use GUI time
16096                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16097         } else {
16098            if(whiteNPS >= 0) lastTickLength = 0;
16099            whiteTimeRemaining -= lastTickLength;
16100            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16101 //         if(pvInfoList[forwardMostMove].time == -1)
16102                  pvInfoList[forwardMostMove].time =
16103                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16104         }
16105         flagged = CheckFlags();
16106     }
16107     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16108     CheckTimeControl();
16109
16110     if (flagged || !appData.clockMode) return;
16111
16112     switch (gameMode) {
16113       case MachinePlaysBlack:
16114       case MachinePlaysWhite:
16115       case BeginningOfGame:
16116         if (pausing) return;
16117         break;
16118
16119       case EditGame:
16120       case PlayFromGameFile:
16121       case IcsExamining:
16122         return;
16123
16124       default:
16125         break;
16126     }
16127
16128     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16129         if(WhiteOnMove(forwardMostMove))
16130              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16131         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16132     }
16133
16134     tickStartTM = now;
16135     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16136       whiteTimeRemaining : blackTimeRemaining);
16137     StartClockTimer(intendedTickLength);
16138 }
16139
16140
16141 /* Stop both clocks */
16142 void
16143 StopClocks()
16144 {
16145     long lastTickLength;
16146     TimeMark now;
16147
16148     if (!StopClockTimer()) return;
16149     if (!appData.clockMode) return;
16150
16151     GetTimeMark(&now);
16152
16153     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16154     if (WhiteOnMove(forwardMostMove)) {
16155         if(whiteNPS >= 0) lastTickLength = 0;
16156         whiteTimeRemaining -= lastTickLength;
16157         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16158     } else {
16159         if(blackNPS >= 0) lastTickLength = 0;
16160         blackTimeRemaining -= lastTickLength;
16161         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16162     }
16163     CheckFlags();
16164 }
16165
16166 /* Start clock of player on move.  Time may have been reset, so
16167    if clock is already running, stop and restart it. */
16168 void
16169 StartClocks()
16170 {
16171     (void) StopClockTimer(); /* in case it was running already */
16172     DisplayBothClocks();
16173     if (CheckFlags()) return;
16174
16175     if (!appData.clockMode) return;
16176     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16177
16178     GetTimeMark(&tickStartTM);
16179     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16180       whiteTimeRemaining : blackTimeRemaining);
16181
16182    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16183     whiteNPS = blackNPS = -1;
16184     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16185        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16186         whiteNPS = first.nps;
16187     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16188        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16189         blackNPS = first.nps;
16190     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16191         whiteNPS = second.nps;
16192     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16193         blackNPS = second.nps;
16194     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16195
16196     StartClockTimer(intendedTickLength);
16197 }
16198
16199 char *
16200 TimeString(ms)
16201      long ms;
16202 {
16203     long second, minute, hour, day;
16204     char *sign = "";
16205     static char buf[32];
16206
16207     if (ms > 0 && ms <= 9900) {
16208       /* convert milliseconds to tenths, rounding up */
16209       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16210
16211       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16212       return buf;
16213     }
16214
16215     /* convert milliseconds to seconds, rounding up */
16216     /* use floating point to avoid strangeness of integer division
16217        with negative dividends on many machines */
16218     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16219
16220     if (second < 0) {
16221         sign = "-";
16222         second = -second;
16223     }
16224
16225     day = second / (60 * 60 * 24);
16226     second = second % (60 * 60 * 24);
16227     hour = second / (60 * 60);
16228     second = second % (60 * 60);
16229     minute = second / 60;
16230     second = second % 60;
16231
16232     if (day > 0)
16233       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16234               sign, day, hour, minute, second);
16235     else if (hour > 0)
16236       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16237     else
16238       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16239
16240     return buf;
16241 }
16242
16243
16244 /*
16245  * This is necessary because some C libraries aren't ANSI C compliant yet.
16246  */
16247 char *
16248 StrStr(string, match)
16249      char *string, *match;
16250 {
16251     int i, length;
16252
16253     length = strlen(match);
16254
16255     for (i = strlen(string) - length; i >= 0; i--, string++)
16256       if (!strncmp(match, string, length))
16257         return string;
16258
16259     return NULL;
16260 }
16261
16262 char *
16263 StrCaseStr(string, match)
16264      char *string, *match;
16265 {
16266     int i, j, length;
16267
16268     length = strlen(match);
16269
16270     for (i = strlen(string) - length; i >= 0; i--, string++) {
16271         for (j = 0; j < length; j++) {
16272             if (ToLower(match[j]) != ToLower(string[j]))
16273               break;
16274         }
16275         if (j == length) return string;
16276     }
16277
16278     return NULL;
16279 }
16280
16281 #ifndef _amigados
16282 int
16283 StrCaseCmp(s1, s2)
16284      char *s1, *s2;
16285 {
16286     char c1, c2;
16287
16288     for (;;) {
16289         c1 = ToLower(*s1++);
16290         c2 = ToLower(*s2++);
16291         if (c1 > c2) return 1;
16292         if (c1 < c2) return -1;
16293         if (c1 == NULLCHAR) return 0;
16294     }
16295 }
16296
16297
16298 int
16299 ToLower(c)
16300      int c;
16301 {
16302     return isupper(c) ? tolower(c) : c;
16303 }
16304
16305
16306 int
16307 ToUpper(c)
16308      int c;
16309 {
16310     return islower(c) ? toupper(c) : c;
16311 }
16312 #endif /* !_amigados    */
16313
16314 char *
16315 StrSave(s)
16316      char *s;
16317 {
16318   char *ret;
16319
16320   if ((ret = (char *) malloc(strlen(s) + 1)))
16321     {
16322       safeStrCpy(ret, s, strlen(s)+1);
16323     }
16324   return ret;
16325 }
16326
16327 char *
16328 StrSavePtr(s, savePtr)
16329      char *s, **savePtr;
16330 {
16331     if (*savePtr) {
16332         free(*savePtr);
16333     }
16334     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16335       safeStrCpy(*savePtr, s, strlen(s)+1);
16336     }
16337     return(*savePtr);
16338 }
16339
16340 char *
16341 PGNDate()
16342 {
16343     time_t clock;
16344     struct tm *tm;
16345     char buf[MSG_SIZ];
16346
16347     clock = time((time_t *)NULL);
16348     tm = localtime(&clock);
16349     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16350             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16351     return StrSave(buf);
16352 }
16353
16354
16355 char *
16356 PositionToFEN(move, overrideCastling)
16357      int move;
16358      char *overrideCastling;
16359 {
16360     int i, j, fromX, fromY, toX, toY;
16361     int whiteToPlay;
16362     char buf[MSG_SIZ];
16363     char *p, *q;
16364     int emptycount;
16365     ChessSquare piece;
16366
16367     whiteToPlay = (gameMode == EditPosition) ?
16368       !blackPlaysFirst : (move % 2 == 0);
16369     p = buf;
16370
16371     /* Piece placement data */
16372     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16373         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16374         emptycount = 0;
16375         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16376             if (boards[move][i][j] == EmptySquare) {
16377                 emptycount++;
16378             } else { ChessSquare piece = boards[move][i][j];
16379                 if (emptycount > 0) {
16380                     if(emptycount<10) /* [HGM] can be >= 10 */
16381                         *p++ = '0' + emptycount;
16382                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16383                     emptycount = 0;
16384                 }
16385                 if(PieceToChar(piece) == '+') {
16386                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16387                     *p++ = '+';
16388                     piece = (ChessSquare)(DEMOTED piece);
16389                 }
16390                 *p++ = PieceToChar(piece);
16391                 if(p[-1] == '~') {
16392                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16393                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16394                     *p++ = '~';
16395                 }
16396             }
16397         }
16398         if (emptycount > 0) {
16399             if(emptycount<10) /* [HGM] can be >= 10 */
16400                 *p++ = '0' + emptycount;
16401             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16402             emptycount = 0;
16403         }
16404         *p++ = '/';
16405     }
16406     *(p - 1) = ' ';
16407
16408     /* [HGM] print Crazyhouse or Shogi holdings */
16409     if( gameInfo.holdingsWidth ) {
16410         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16411         q = p;
16412         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16413             piece = boards[move][i][BOARD_WIDTH-1];
16414             if( piece != EmptySquare )
16415               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16416                   *p++ = PieceToChar(piece);
16417         }
16418         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16419             piece = boards[move][BOARD_HEIGHT-i-1][0];
16420             if( piece != EmptySquare )
16421               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16422                   *p++ = PieceToChar(piece);
16423         }
16424
16425         if( q == p ) *p++ = '-';
16426         *p++ = ']';
16427         *p++ = ' ';
16428     }
16429
16430     /* Active color */
16431     *p++ = whiteToPlay ? 'w' : 'b';
16432     *p++ = ' ';
16433
16434   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16435     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16436   } else {
16437   if(nrCastlingRights) {
16438      q = p;
16439      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16440        /* [HGM] write directly from rights */
16441            if(boards[move][CASTLING][2] != NoRights &&
16442               boards[move][CASTLING][0] != NoRights   )
16443                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16444            if(boards[move][CASTLING][2] != NoRights &&
16445               boards[move][CASTLING][1] != NoRights   )
16446                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16447            if(boards[move][CASTLING][5] != NoRights &&
16448               boards[move][CASTLING][3] != NoRights   )
16449                 *p++ = boards[move][CASTLING][3] + AAA;
16450            if(boards[move][CASTLING][5] != NoRights &&
16451               boards[move][CASTLING][4] != NoRights   )
16452                 *p++ = boards[move][CASTLING][4] + AAA;
16453      } else {
16454
16455         /* [HGM] write true castling rights */
16456         if( nrCastlingRights == 6 ) {
16457             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16458                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16459             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16460                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16461             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16462                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16463             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16464                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16465         }
16466      }
16467      if (q == p) *p++ = '-'; /* No castling rights */
16468      *p++ = ' ';
16469   }
16470
16471   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16472      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16473     /* En passant target square */
16474     if (move > backwardMostMove) {
16475         fromX = moveList[move - 1][0] - AAA;
16476         fromY = moveList[move - 1][1] - ONE;
16477         toX = moveList[move - 1][2] - AAA;
16478         toY = moveList[move - 1][3] - ONE;
16479         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16480             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16481             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16482             fromX == toX) {
16483             /* 2-square pawn move just happened */
16484             *p++ = toX + AAA;
16485             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16486         } else {
16487             *p++ = '-';
16488         }
16489     } else if(move == backwardMostMove) {
16490         // [HGM] perhaps we should always do it like this, and forget the above?
16491         if((signed char)boards[move][EP_STATUS] >= 0) {
16492             *p++ = boards[move][EP_STATUS] + AAA;
16493             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16494         } else {
16495             *p++ = '-';
16496         }
16497     } else {
16498         *p++ = '-';
16499     }
16500     *p++ = ' ';
16501   }
16502   }
16503
16504     /* [HGM] find reversible plies */
16505     {   int i = 0, j=move;
16506
16507         if (appData.debugMode) { int k;
16508             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16509             for(k=backwardMostMove; k<=forwardMostMove; k++)
16510                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16511
16512         }
16513
16514         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16515         if( j == backwardMostMove ) i += initialRulePlies;
16516         sprintf(p, "%d ", i);
16517         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16518     }
16519     /* Fullmove number */
16520     sprintf(p, "%d", (move / 2) + 1);
16521
16522     return StrSave(buf);
16523 }
16524
16525 Boolean
16526 ParseFEN(board, blackPlaysFirst, fen)
16527     Board board;
16528      int *blackPlaysFirst;
16529      char *fen;
16530 {
16531     int i, j;
16532     char *p, c;
16533     int emptycount;
16534     ChessSquare piece;
16535
16536     p = fen;
16537
16538     /* [HGM] by default clear Crazyhouse holdings, if present */
16539     if(gameInfo.holdingsWidth) {
16540        for(i=0; i<BOARD_HEIGHT; i++) {
16541            board[i][0]             = EmptySquare; /* black holdings */
16542            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16543            board[i][1]             = (ChessSquare) 0; /* black counts */
16544            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16545        }
16546     }
16547
16548     /* Piece placement data */
16549     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16550         j = 0;
16551         for (;;) {
16552             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16553                 if (*p == '/') p++;
16554                 emptycount = gameInfo.boardWidth - j;
16555                 while (emptycount--)
16556                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16557                 break;
16558 #if(BOARD_FILES >= 10)
16559             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16560                 p++; emptycount=10;
16561                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16562                 while (emptycount--)
16563                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16564 #endif
16565             } else if (isdigit(*p)) {
16566                 emptycount = *p++ - '0';
16567                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16568                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16569                 while (emptycount--)
16570                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16571             } else if (*p == '+' || isalpha(*p)) {
16572                 if (j >= gameInfo.boardWidth) return FALSE;
16573                 if(*p=='+') {
16574                     piece = CharToPiece(*++p);
16575                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16576                     piece = (ChessSquare) (PROMOTED piece ); p++;
16577                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16578                 } else piece = CharToPiece(*p++);
16579
16580                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16581                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16582                     piece = (ChessSquare) (PROMOTED piece);
16583                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16584                     p++;
16585                 }
16586                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16587             } else {
16588                 return FALSE;
16589             }
16590         }
16591     }
16592     while (*p == '/' || *p == ' ') p++;
16593
16594     /* [HGM] look for Crazyhouse holdings here */
16595     while(*p==' ') p++;
16596     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16597         if(*p == '[') p++;
16598         if(*p == '-' ) p++; /* empty holdings */ else {
16599             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16600             /* if we would allow FEN reading to set board size, we would   */
16601             /* have to add holdings and shift the board read so far here   */
16602             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16603                 p++;
16604                 if((int) piece >= (int) BlackPawn ) {
16605                     i = (int)piece - (int)BlackPawn;
16606                     i = PieceToNumber((ChessSquare)i);
16607                     if( i >= gameInfo.holdingsSize ) return FALSE;
16608                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16609                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16610                 } else {
16611                     i = (int)piece - (int)WhitePawn;
16612                     i = PieceToNumber((ChessSquare)i);
16613                     if( i >= gameInfo.holdingsSize ) return FALSE;
16614                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16615                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16616                 }
16617             }
16618         }
16619         if(*p == ']') p++;
16620     }
16621
16622     while(*p == ' ') p++;
16623
16624     /* Active color */
16625     c = *p++;
16626     if(appData.colorNickNames) {
16627       if( c == appData.colorNickNames[0] ) c = 'w'; else
16628       if( c == appData.colorNickNames[1] ) c = 'b';
16629     }
16630     switch (c) {
16631       case 'w':
16632         *blackPlaysFirst = FALSE;
16633         break;
16634       case 'b':
16635         *blackPlaysFirst = TRUE;
16636         break;
16637       default:
16638         return FALSE;
16639     }
16640
16641     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16642     /* return the extra info in global variiables             */
16643
16644     /* set defaults in case FEN is incomplete */
16645     board[EP_STATUS] = EP_UNKNOWN;
16646     for(i=0; i<nrCastlingRights; i++ ) {
16647         board[CASTLING][i] =
16648             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16649     }   /* assume possible unless obviously impossible */
16650     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16651     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16652     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16653                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16654     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16655     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16656     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16657                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16658     FENrulePlies = 0;
16659
16660     while(*p==' ') p++;
16661     if(nrCastlingRights) {
16662       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16663           /* castling indicator present, so default becomes no castlings */
16664           for(i=0; i<nrCastlingRights; i++ ) {
16665                  board[CASTLING][i] = NoRights;
16666           }
16667       }
16668       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16669              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16670              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16671              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16672         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16673
16674         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16675             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16676             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16677         }
16678         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16679             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16680         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16681                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16682         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16683                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16684         switch(c) {
16685           case'K':
16686               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16687               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16688               board[CASTLING][2] = whiteKingFile;
16689               break;
16690           case'Q':
16691               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16692               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16693               board[CASTLING][2] = whiteKingFile;
16694               break;
16695           case'k':
16696               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16697               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16698               board[CASTLING][5] = blackKingFile;
16699               break;
16700           case'q':
16701               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16702               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16703               board[CASTLING][5] = blackKingFile;
16704           case '-':
16705               break;
16706           default: /* FRC castlings */
16707               if(c >= 'a') { /* black rights */
16708                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16709                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16710                   if(i == BOARD_RGHT) break;
16711                   board[CASTLING][5] = i;
16712                   c -= AAA;
16713                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16714                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16715                   if(c > i)
16716                       board[CASTLING][3] = c;
16717                   else
16718                       board[CASTLING][4] = c;
16719               } else { /* white rights */
16720                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16721                     if(board[0][i] == WhiteKing) break;
16722                   if(i == BOARD_RGHT) break;
16723                   board[CASTLING][2] = i;
16724                   c -= AAA - 'a' + 'A';
16725                   if(board[0][c] >= WhiteKing) break;
16726                   if(c > i)
16727                       board[CASTLING][0] = c;
16728                   else
16729                       board[CASTLING][1] = c;
16730               }
16731         }
16732       }
16733       for(i=0; i<nrCastlingRights; i++)
16734         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16735     if (appData.debugMode) {
16736         fprintf(debugFP, "FEN castling rights:");
16737         for(i=0; i<nrCastlingRights; i++)
16738         fprintf(debugFP, " %d", board[CASTLING][i]);
16739         fprintf(debugFP, "\n");
16740     }
16741
16742       while(*p==' ') p++;
16743     }
16744
16745     /* read e.p. field in games that know e.p. capture */
16746     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16747        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16748       if(*p=='-') {
16749         p++; board[EP_STATUS] = EP_NONE;
16750       } else {
16751          char c = *p++ - AAA;
16752
16753          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16754          if(*p >= '0' && *p <='9') p++;
16755          board[EP_STATUS] = c;
16756       }
16757     }
16758
16759
16760     if(sscanf(p, "%d", &i) == 1) {
16761         FENrulePlies = i; /* 50-move ply counter */
16762         /* (The move number is still ignored)    */
16763     }
16764
16765     return TRUE;
16766 }
16767
16768 void
16769 EditPositionPasteFEN(char *fen)
16770 {
16771   if (fen != NULL) {
16772     Board initial_position;
16773
16774     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16775       DisplayError(_("Bad FEN position in clipboard"), 0);
16776       return ;
16777     } else {
16778       int savedBlackPlaysFirst = blackPlaysFirst;
16779       EditPositionEvent();
16780       blackPlaysFirst = savedBlackPlaysFirst;
16781       CopyBoard(boards[0], initial_position);
16782       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16783       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16784       DisplayBothClocks();
16785       DrawPosition(FALSE, boards[currentMove]);
16786     }
16787   }
16788 }
16789
16790 static char cseq[12] = "\\   ";
16791
16792 Boolean set_cont_sequence(char *new_seq)
16793 {
16794     int len;
16795     Boolean ret;
16796
16797     // handle bad attempts to set the sequence
16798         if (!new_seq)
16799                 return 0; // acceptable error - no debug
16800
16801     len = strlen(new_seq);
16802     ret = (len > 0) && (len < sizeof(cseq));
16803     if (ret)
16804       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16805     else if (appData.debugMode)
16806       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16807     return ret;
16808 }
16809
16810 /*
16811     reformat a source message so words don't cross the width boundary.  internal
16812     newlines are not removed.  returns the wrapped size (no null character unless
16813     included in source message).  If dest is NULL, only calculate the size required
16814     for the dest buffer.  lp argument indicats line position upon entry, and it's
16815     passed back upon exit.
16816 */
16817 int wrap(char *dest, char *src, int count, int width, int *lp)
16818 {
16819     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16820
16821     cseq_len = strlen(cseq);
16822     old_line = line = *lp;
16823     ansi = len = clen = 0;
16824
16825     for (i=0; i < count; i++)
16826     {
16827         if (src[i] == '\033')
16828             ansi = 1;
16829
16830         // if we hit the width, back up
16831         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16832         {
16833             // store i & len in case the word is too long
16834             old_i = i, old_len = len;
16835
16836             // find the end of the last word
16837             while (i && src[i] != ' ' && src[i] != '\n')
16838             {
16839                 i--;
16840                 len--;
16841             }
16842
16843             // word too long?  restore i & len before splitting it
16844             if ((old_i-i+clen) >= width)
16845             {
16846                 i = old_i;
16847                 len = old_len;
16848             }
16849
16850             // extra space?
16851             if (i && src[i-1] == ' ')
16852                 len--;
16853
16854             if (src[i] != ' ' && src[i] != '\n')
16855             {
16856                 i--;
16857                 if (len)
16858                     len--;
16859             }
16860
16861             // now append the newline and continuation sequence
16862             if (dest)
16863                 dest[len] = '\n';
16864             len++;
16865             if (dest)
16866                 strncpy(dest+len, cseq, cseq_len);
16867             len += cseq_len;
16868             line = cseq_len;
16869             clen = cseq_len;
16870             continue;
16871         }
16872
16873         if (dest)
16874             dest[len] = src[i];
16875         len++;
16876         if (!ansi)
16877             line++;
16878         if (src[i] == '\n')
16879             line = 0;
16880         if (src[i] == 'm')
16881             ansi = 0;
16882     }
16883     if (dest && appData.debugMode)
16884     {
16885         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16886             count, width, line, len, *lp);
16887         show_bytes(debugFP, src, count);
16888         fprintf(debugFP, "\ndest: ");
16889         show_bytes(debugFP, dest, len);
16890         fprintf(debugFP, "\n");
16891     }
16892     *lp = dest ? line : old_line;
16893
16894     return len;
16895 }
16896
16897 // [HGM] vari: routines for shelving variations
16898 Boolean modeRestore = FALSE;
16899
16900 void
16901 PushInner(int firstMove, int lastMove)
16902 {
16903         int i, j, nrMoves = lastMove - firstMove;
16904
16905         // push current tail of game on stack
16906         savedResult[storedGames] = gameInfo.result;
16907         savedDetails[storedGames] = gameInfo.resultDetails;
16908         gameInfo.resultDetails = NULL;
16909         savedFirst[storedGames] = firstMove;
16910         savedLast [storedGames] = lastMove;
16911         savedFramePtr[storedGames] = framePtr;
16912         framePtr -= nrMoves; // reserve space for the boards
16913         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16914             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16915             for(j=0; j<MOVE_LEN; j++)
16916                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16917             for(j=0; j<2*MOVE_LEN; j++)
16918                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16919             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16920             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16921             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16922             pvInfoList[firstMove+i-1].depth = 0;
16923             commentList[framePtr+i] = commentList[firstMove+i];
16924             commentList[firstMove+i] = NULL;
16925         }
16926
16927         storedGames++;
16928         forwardMostMove = firstMove; // truncate game so we can start variation
16929 }
16930
16931 void
16932 PushTail(int firstMove, int lastMove)
16933 {
16934         if(appData.icsActive) { // only in local mode
16935                 forwardMostMove = currentMove; // mimic old ICS behavior
16936                 return;
16937         }
16938         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16939
16940         PushInner(firstMove, lastMove);
16941         if(storedGames == 1) GreyRevert(FALSE);
16942         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16943 }
16944
16945 void
16946 PopInner(Boolean annotate)
16947 {
16948         int i, j, nrMoves;
16949         char buf[8000], moveBuf[20];
16950
16951         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16952         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16953         nrMoves = savedLast[storedGames] - currentMove;
16954         if(annotate) {
16955                 int cnt = 10;
16956                 if(!WhiteOnMove(currentMove))
16957                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16958                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16959                 for(i=currentMove; i<forwardMostMove; i++) {
16960                         if(WhiteOnMove(i))
16961                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16962                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16963                         strcat(buf, moveBuf);
16964                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16965                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16966                 }
16967                 strcat(buf, ")");
16968         }
16969         for(i=1; i<=nrMoves; i++) { // copy last variation back
16970             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16971             for(j=0; j<MOVE_LEN; j++)
16972                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16973             for(j=0; j<2*MOVE_LEN; j++)
16974                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16975             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16976             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16977             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16978             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16979             commentList[currentMove+i] = commentList[framePtr+i];
16980             commentList[framePtr+i] = NULL;
16981         }
16982         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16983         framePtr = savedFramePtr[storedGames];
16984         gameInfo.result = savedResult[storedGames];
16985         if(gameInfo.resultDetails != NULL) {
16986             free(gameInfo.resultDetails);
16987       }
16988         gameInfo.resultDetails = savedDetails[storedGames];
16989         forwardMostMove = currentMove + nrMoves;
16990 }
16991
16992 Boolean
16993 PopTail(Boolean annotate)
16994 {
16995         if(appData.icsActive) return FALSE; // only in local mode
16996         if(!storedGames) return FALSE; // sanity
16997         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16998
16999         PopInner(annotate);
17000         if(currentMove < forwardMostMove) ForwardEvent(); else
17001         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17002
17003         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17004         return TRUE;
17005 }
17006
17007 void
17008 CleanupTail()
17009 {       // remove all shelved variations
17010         int i;
17011         for(i=0; i<storedGames; i++) {
17012             if(savedDetails[i])
17013                 free(savedDetails[i]);
17014             savedDetails[i] = NULL;
17015         }
17016         for(i=framePtr; i<MAX_MOVES; i++) {
17017                 if(commentList[i]) free(commentList[i]);
17018                 commentList[i] = NULL;
17019         }
17020         framePtr = MAX_MOVES-1;
17021         storedGames = 0;
17022 }
17023
17024 void
17025 LoadVariation(int index, char *text)
17026 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17027         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17028         int level = 0, move;
17029
17030         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17031         // first find outermost bracketing variation
17032         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17033             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17034                 if(*p == '{') wait = '}'; else
17035                 if(*p == '[') wait = ']'; else
17036                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17037                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17038             }
17039             if(*p == wait) wait = NULLCHAR; // closing ]} found
17040             p++;
17041         }
17042         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17043         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17044         end[1] = NULLCHAR; // clip off comment beyond variation
17045         ToNrEvent(currentMove-1);
17046         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17047         // kludge: use ParsePV() to append variation to game
17048         move = currentMove;
17049         ParsePV(start, TRUE, TRUE);
17050         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17051         ClearPremoveHighlights();
17052         CommentPopDown();
17053         ToNrEvent(currentMove+1);
17054 }
17055